diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 55daeae3..b3e7cf1f 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -443,6 +443,9 @@ jobs:
- name: Lint
run: npm --prefix apps/marketing run lint
+ - name: Capability parity
+ run: npm --prefix apps/marketing test
+
- name: Typecheck
run: npm --prefix apps/marketing run typecheck
diff --git a/apps/marketing/package.json b/apps/marketing/package.json
index 8e86fdc5..15043cfd 100644
--- a/apps/marketing/package.json
+++ b/apps/marketing/package.json
@@ -9,7 +9,8 @@
"build": "tsc -b && vite build",
"preview": "vite preview",
"lint": "eslint .",
- "typecheck": "tsc -b --noEmit"
+ "typecheck": "tsc -b --noEmit",
+ "test": "node --test tests/capability-parity.test.mjs"
},
"dependencies": {
"clsx": "^2.1.1",
diff --git a/apps/marketing/src/App.tsx b/apps/marketing/src/App.tsx
index ac2cb3f4..16d7fb40 100644
--- a/apps/marketing/src/App.tsx
+++ b/apps/marketing/src/App.tsx
@@ -21,7 +21,7 @@ import { useLandingTheme } from '@/hooks/useLandingTheme';
*
* The two hero calls to action open full-screen panels over the page rather
* than navigating away: "See how it works" runs a scripted walkthrough of a
- * real analysis, and "Analyze a Repository" shows how to run PARTHA against
+ * fictional interface and proposed checks, and "Analyze a Repository" shows how to run PARTHA against
* your own code. Closing either returns the reader exactly where they were.
*/
export function App() {
diff --git a/apps/marketing/src/components/DemoModal.tsx b/apps/marketing/src/components/DemoModal.tsx
index 98308b3f..9c5fc950 100644
--- a/apps/marketing/src/components/DemoModal.tsx
+++ b/apps/marketing/src/components/DemoModal.tsx
@@ -70,11 +70,11 @@ export function DemoModal({ onClose }: { onClose: () => void }) {
Scripted simulation · sample repository
- See what a PARTHA analysis produces
+ Explore the review interface and proposed checks
A scripted walkthrough of a made-up sample repository ({SAMPLE_REPO.name}) -- not a live analysis of any
- real code. It uses PARTHA's actual finding categories and output shape.
+ real code. The four checks below are proposed and unimplemented. Categories describe current coverage; all metrics and source spans are fictional.
@@ -102,8 +102,7 @@ export function DemoModal({ onClose }: { onClose: () => void }) {
{phase === 'idle' && (
- Run the simulation to watch PARTHA walk through a sample repository and produce Engineering Review
- and Repository Insights output.
+ Run the interface simulation to explore fictional metrics and proposed review checks.
void }) {
- {CATEGORY_LABELS[finding.category]}
+ {CATEGORY_LABELS[finding.category]} · Proposed check (not implemented)
{finding.explanation}
diff --git a/apps/marketing/src/data/sampleAnalysis.ts b/apps/marketing/src/data/sampleAnalysis.ts
index 7d1c8882..0077face 100644
--- a/apps/marketing/src/data/sampleAnalysis.ts
+++ b/apps/marketing/src/data/sampleAnalysis.ts
@@ -1,14 +1,6 @@
-/**
- * Canned data for the scripted product simulation (#382).
- *
- * Every category id, severity level, and field name here matches the real
- * product's actual Engineering Review / Repository Insights response shape
- * (apps/backend/app/schemas -- cross-checked against
- * apps/frontend/src/shared/services/api/generated.ts, the generated OpenAPI
- * types) and the real category labels
- * (apps/backend/app/review/review_service.py's `_CATEGORY_LABELS`). The
- * repository, findings, and numbers themselves are entirely made up for a
- * fictional sample repo -- nothing here is captured from a real analysis.
+/** Fictional interface simulation. Category labels and coverage states mirror
+ * the current review contract; the explicitly proposed checks are unimplemented.
+ * Metrics, source spans and repository details are made up, not analyzer output.
*/
export const SAMPLE_REPO = {
@@ -22,7 +14,7 @@ export const SIMULATION_STEPS = [
'Extracting structural facts (Python, TypeScript)',
'Resolving module and dependency relationships',
'Sealing the ri.v1 snapshot',
- 'Running Engineering Review and Repository Insights',
+ 'Showing proposed review checks and fictional Repository Insights',
] as const;
export type ReviewSeverity = 'info' | 'low' | 'medium' | 'high' | 'critical';
@@ -53,6 +45,7 @@ export const CATEGORY_LABELS: Record = {
export interface SampleFinding {
id: string;
+ implementation: 'proposed';
title: string;
category: ReviewCategoryId;
severity: ReviewSeverity;
@@ -63,9 +56,11 @@ export interface SampleFinding {
endLine: number;
}
+// These proposed checks are not emitted by the current Engineering Review rules.
export const SAMPLE_FINDINGS: SampleFinding[] = [
{
id: 'finding-1',
+ implementation: 'proposed',
title: 'Payment adapter imports directly from the checkout domain layer',
category: 'architecture_boundaries',
severity: 'medium',
@@ -79,6 +74,7 @@ export const SAMPLE_FINDINGS: SampleFinding[] = [
},
{
id: 'finding-2',
+ implementation: 'proposed',
title: 'Declared dependency pin resolves to a version not requested anywhere',
category: 'dependency_declarations',
severity: 'low',
@@ -91,11 +87,12 @@ export const SAMPLE_FINDINGS: SampleFinding[] = [
},
{
id: 'finding-3',
+ implementation: 'proposed',
title: 'Session cookie is issued without an explicit SameSite attribute',
category: 'authentication_evidence',
severity: 'high',
explanation:
- 'src/auth/session.py sets the session cookie without a SameSite attribute, so browsers fall back to a permissive default that varies by browser rather than an explicit, reviewable policy.',
+ 'src/auth/session.py sets the session cookie without a SameSite attribute, so browser defaults apply rather than an explicit, reviewable policy.',
remediationGuidance: 'Set SameSite explicitly (Lax or Strict) when issuing the session cookie.',
path: 'src/auth/session.py',
startLine: 47,
@@ -103,6 +100,7 @@ export const SAMPLE_FINDINGS: SampleFinding[] = [
},
{
id: 'finding-4',
+ implementation: 'proposed',
title: 'Two modules independently resolve the same order-total calculation',
category: 'relationship_resolution',
severity: 'info',
@@ -124,13 +122,13 @@ export interface SampleCategoryAssessment {
}
export const SAMPLE_CATEGORIES: SampleCategoryAssessment[] = [
- { id: 'architecture_boundaries', label: CATEGORY_LABELS.architecture_boundaries, state: 'assessed', findingCount: 1, explanation: 'Module boundaries were assessed from resolved import relationships.' },
- { id: 'relationship_resolution', label: CATEGORY_LABELS.relationship_resolution, state: 'assessed', findingCount: 1, explanation: 'Cross-module relationships were assessed from resolved facts.' },
+ { id: 'architecture_boundaries', label: CATEGORY_LABELS.architecture_boundaries, state: 'partially_assessed', findingCount: 0, explanation: 'Structural relationships are supported; domain boundary checks are proposed.' },
+ { id: 'relationship_resolution', label: CATEGORY_LABELS.relationship_resolution, state: 'assessed', findingCount: 0, explanation: 'Cross-module relationships were assessed from resolved facts.' },
{ id: 'source_extraction', label: CATEGORY_LABELS.source_extraction, state: 'assessed', findingCount: 0, explanation: 'No source-extraction diagnostics were raised for this snapshot.' },
- { id: 'dependency_declarations', label: CATEGORY_LABELS.dependency_declarations, state: 'assessed', findingCount: 1, explanation: 'Direct declarations and lockfile pins were assessed for this snapshot.' },
+ { id: 'dependency_declarations', label: CATEGORY_LABELS.dependency_declarations, state: 'partially_assessed', findingCount: 0, explanation: 'Dependency inventory is supported; lockfile reconciliation is a proposed check.' },
{ id: 'security_vulnerability_scanning', label: CATEGORY_LABELS.security_vulnerability_scanning, state: 'not_assessed', findingCount: 0, explanation: 'Vulnerability scanning is not implemented; this category is not assessed.' },
- { id: 'authentication_evidence', label: CATEGORY_LABELS.authentication_evidence, state: 'assessed', findingCount: 1, explanation: 'The supported Python/FastAPI authentication subgraph was assessed.' },
- { id: 'repository_structure', label: CATEGORY_LABELS.repository_structure, state: 'assessed', findingCount: 0, explanation: 'No repository-structure diagnostics were raised for this snapshot.' },
+ { id: 'authentication_evidence', label: CATEGORY_LABELS.authentication_evidence, state: 'partially_assessed', findingCount: 0, explanation: 'Authentication inventory is partial; cookie attribute checks are proposed.' },
+ { id: 'repository_structure', label: CATEGORY_LABELS.repository_structure, state: 'partially_assessed', findingCount: 0, explanation: 'No repository-structure diagnostics were raised for this snapshot.' },
{ id: 'analysis_integrity', label: CATEGORY_LABELS.analysis_integrity, state: 'assessed', findingCount: 0, explanation: 'The analysis completed with no integrity diagnostics.' },
];
diff --git a/apps/marketing/tests/capability-parity.test.mjs b/apps/marketing/tests/capability-parity.test.mjs
new file mode 100644
index 00000000..4b750519
--- /dev/null
+++ b/apps/marketing/tests/capability-parity.test.mjs
@@ -0,0 +1,25 @@
+import { URL } from 'node:url';
+import { Buffer } from 'node:buffer';
+import assert from 'node:assert/strict';
+import { readFileSync } from 'node:fs';
+import test from 'node:test';
+import ts from 'typescript';
+const source = readFileSync(new URL('../src/data/sampleAnalysis.ts', import.meta.url), 'utf8');
+const compiled = ts.transpileModule(source, { compilerOptions: { module: ts.ModuleKind.ESNext } }).outputText;
+const { SAMPLE_FINDINGS, SAMPLE_CATEGORIES } = await import(`data:text/javascript;base64,${Buffer.from(compiled).toString('base64')}`);
+test('unsupported demo checks are explicitly proposed and never counted as analyzer findings', () => {
+ assert.equal(SAMPLE_FINDINGS.length, 4);
+ for (const finding of SAMPLE_FINDINGS) assert.equal(finding.implementation, 'proposed');
+ for (const category of SAMPLE_CATEGORIES) assert.equal(category.findingCount, 0);
+ const component = readFileSync(new URL('../src/components/DemoModal.tsx', import.meta.url), 'utf8');
+ assert.match(component, /Proposed check \(not implemented\)/);
+ assert.match(component, /four checks below are proposed and unimplemented/);
+});
+test('limited categories retain current backend assessment states', () => {
+ const backend = readFileSync(new URL('../../backend/app/review/review_service.py', import.meta.url), 'utf8');
+ for (const id of ['architecture_boundaries', 'dependency_declarations', 'authentication_evidence', 'repository_structure']) {
+ assert.equal(SAMPLE_CATEGORIES.find(category => category.id === id).state, 'partially_assessed');
+ assert.match(backend, new RegExp(String.raw`"${id}": \([\s\S]{0,100}"partially_assessed"`));
+ }
+ assert.equal(SAMPLE_CATEGORIES.find(category => category.id === 'security_vulnerability_scanning').state, 'not_assessed');
+});