From a3556297f8cc2853f260ad08c087afb8535a26b4 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 17 Sep 2026 20:13:33 +0000 Subject: [PATCH 1/8] fix(pipeline): gate redesign integrity before success and handoff MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit buildFactoryExecutionPlan pushed RedesignIntegrityReceiptStage after ReleaseReceiptFinalizerStage and HandoffEmitterStage. For an end-to-end REDESIGN_IMPROVE run the emitted order was: ... visual-qa, release-receipt-finalizer, handoff-emitter, redesign-integrity-receipt, terminal-convergence The finalizer writes status: "succeeded" to the release receipt and the emitter performs the external POST to SEO-Bot plus recordWebsiteRelease (externalMutation: true). A gate in that position can only reject a run that has already been recorded as successful and handed off — it cannot prevent one. An invalid redesign was therefore publishable. The gate now runs after visual-qa, whose ctx.qualityEvidence.visualQa it reads, and before the finalizer and emitter. It stays outside the end-to-end guard on purpose: REDESIGN_ADDED_MANDATORY makes redesign-integrity-receipt mandatory in every mode, so folding it into that branch would make requireMandatoryConvergence throw on plan, local-proof and publish-proof redesign runs. No stage, receipt type, or handoff contract is added — this is a reorder. The gate reads only context produced upstream of its new position and declares no evidence block, so it imposes no runner ordering constraint. The COPY end-to-end stage list is unchanged; the existing deepEqual assertion in tests/unit/factory-execution-plan.test.ts passes unmodified. Two intended consequences: - On the visual-QA-not-passed path a redesign end-to-end run now fails as REDESIGN_PIPELINE_INCOMPLETE rather than RELEASE_EVIDENCE_INCOMPLETE. That is the fail-closed-earlier behaviour, not a regression. - executed_stages in the emitted receipt no longer lists release-receipt-finalizer or handoff-emitter, since it snapshots ctx.stageResults at emit time. Nothing validates that field. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_019Hmr3SKRhYQ56omTzNFZ4L --- src/pipeline/FactoryExecutionPlan.ts | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/pipeline/FactoryExecutionPlan.ts b/src/pipeline/FactoryExecutionPlan.ts index 45f2acab..0b962d67 100644 --- a/src/pipeline/FactoryExecutionPlan.ts +++ b/src/pipeline/FactoryExecutionPlan.ts @@ -305,14 +305,18 @@ export function buildFactoryExecutionPlan( stages.push(new ClientSourcePublishStage()); if (options.mode === "end-to-end") stages.push(new VercelDeployStage()); stages.push(new ReleaseReceiptStage()); - if (options.mode === "end-to-end") - stages.push( - new SEOBaselineStage(), - new VisualQAStage(), - new ReleaseReceiptFinalizerStage(), - new HandoffEmitterStage(), - ); + if (options.mode === "end-to-end") stages.push(new SEOBaselineStage(), new VisualQAStage()); + // The redesign integrity gate must veto BEFORE success is recorded and before + // anything leaves the process. ReleaseReceiptFinalizerStage writes + // status: "succeeded" and HandoffEmitterStage performs the external POST to + // SEO-Bot, so a gate placed after them can only reject a run that has already + // been published. It stays outside the end-to-end guard because + // REDESIGN_ADDED_MANDATORY makes redesign-integrity-receipt mandatory in every + // mode — folding it inside would make requireMandatoryConvergence throw on + // plan, local-proof and publish-proof redesign runs. if (redesign) stages.push(new RedesignIntegrityReceiptStage()); + if (options.mode === "end-to-end") + stages.push(new ReleaseReceiptFinalizerStage(), new HandoffEmitterStage()); stages.push( new TerminalConvergenceStage(options.mode, mandatory, REQUIRED_EVIDENCE[options.mode]), ); From b6605f66baad62faf688085836d6560000332fd0 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 17 Sep 2026 20:13:51 +0000 Subject: [PATCH 2/8] fix(inngest): pass buildIntent into the durable pipeline's execution plan The durable path computed ctx.buildIntent from the spec and then built its execution plan without it, so buildFactoryExecutionPlan always derived redesign === false there. A REDESIGN_IMPROVE spec submitted through Inngest therefore ran the COPY topology: no seo-build-intelligence-preflight, no competitive-intelligence, the legacy content/schema stages instead of the redesign authority chain, and no redesign-integrity-receipt stage at all. The build context said one thing and the plan built another. This also bounds the preceding commit. Reordering the integrity gate ahead of the finalizer and emitter fixes the plan builder, but on this path the gate was never in the plan to begin with, so that fix would have been inert here. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_019Hmr3SKRhYQ56omTzNFZ4L --- src/inngest/website-pipeline.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/inngest/website-pipeline.ts b/src/inngest/website-pipeline.ts index 29821154..e5107fd6 100644 --- a/src/inngest/website-pipeline.ts +++ b/src/inngest/website-pipeline.ts @@ -91,6 +91,11 @@ export const websitePipeline = inngest.createFunction( mode, specPath: data.specPath, provision: shouldProvision, + // Without this the plan builder always saw buildIntent undefined, so a + // REDESIGN_IMPROVE spec ran the COPY topology on the durable path: no + // preflight, no competitive intelligence, no redesign authority chain, + // and no redesign-integrity-receipt gate at all. + buildIntent: ctx.buildIntent, }); compensation.register("release-evidence", async () => { From 6906e3f3ef31dcc0454b55807e06e29c51469837 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 17 Sep 2026 20:14:35 +0000 Subject: [PATCH 3/8] test(pipeline): lock the redesign gate ahead of the success receipt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There was no regression coverage for the terminal ordering of a redesign plan. The two existing REDESIGN_IMPROVE tests assert only the head of the pipeline (preflight before competitive-intelligence, competitive-intelligence before design-intelligence), and redesign-integrity-receipt.test.ts asserts that the stage is present, not where. All of them passed with the gate sitting after the finalizer and the external handoff. This asserts the chain that actually matters: visual-qa < redesign-integrity-receipt < release-receipt-finalizer < handoff-emitter < terminal-convergence Verified to discriminate: against the parent commit's plan builder it fails (8 pass / 1 fail); against the reordered one it passes (9 pass). A test that cannot fail proves nothing, so it was run both ways before being committed. The COPY end-to-end deepEqual assertion is deliberately left untouched — it is the byte-identity guard proving the reorder did not disturb the non-redesign topology, and editing it would destroy the evidence. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_019Hmr3SKRhYQ56omTzNFZ4L --- tests/unit/factory-execution-plan.test.ts | 34 +++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/tests/unit/factory-execution-plan.test.ts b/tests/unit/factory-execution-plan.test.ts index 1ebbb26e..d6822192 100644 --- a/tests/unit/factory-execution-plan.test.ts +++ b/tests/unit/factory-execution-plan.test.ts @@ -134,6 +134,40 @@ test("REDESIGN preflight precedes competitive intelligence", () => { ); }); +test("redesign integrity gate runs before success finalization and handoff", () => { + const plan = buildFactoryExecutionPlan({ + mode: "end-to-end", + specPath: "fixtures/ci-test-spec.yaml", + buildIntent: "REDESIGN_IMPROVE", + }); + const names = plan.stages.map((stage) => stage.name); + const at = (name: string) => { + const index = names.indexOf(name); + assert.ok(index > -1, `${name} must be present in an end-to-end redesign plan`); + return index; + }; + // ReleaseReceiptFinalizerStage records status "succeeded" and + // HandoffEmitterStage performs the external POST. A gate after either can + // only reject a run that has already been published, so ordering here IS + // the guarantee — nothing else enforces it. + assert.ok( + at("visual-qa") < at("redesign-integrity-receipt"), + "redesign-integrity-receipt reads ctx.qualityEvidence.visualQa and must follow visual-qa", + ); + assert.ok( + at("redesign-integrity-receipt") < at("release-receipt-finalizer"), + "an invalid redesign must be rejected before the release is recorded as succeeded", + ); + assert.ok( + at("release-receipt-finalizer") < at("handoff-emitter"), + "handoff follows finalization", + ); + assert.ok( + at("handoff-emitter") < at("terminal-convergence"), + "terminal-convergence stays last", + ); +}); + test("COPY intent (legacy default) keeps the original stage topology", () => { const plan = buildFactoryExecutionPlan({ mode: "local-proof", From ecc2786357212a02dc6b0bfdb90b827daeaca9c7 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 17 Sep 2026 20:14:55 +0000 Subject: [PATCH 4/8] chore(golden): correct the hard-coded stage tail in the fixture builder MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit executed_stages and the STAGES list that drives synthetic checkpoint timestamps both hard-code the pipeline's terminal order as static data rather than deriving it from buildFactoryExecutionPlan. They still carried the pre-fix tail, with redesign-integrity-receipt trailing the finalizer and the handoff emitter. Nothing fails as a result — the golden oracle checks a relative ordered subsequence that omits both of those stages, so it is satisfied by either order. That is precisely the problem: the fixtures would have gone on describing an ordering the code no longer has, and a reviewer re-deriving a fixture would have been misled by it. Data-only; no generator logic changes. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_019Hmr3SKRhYQ56omTzNFZ4L --- scripts/golden-safehaven/fixtures/build-fixtures.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/golden-safehaven/fixtures/build-fixtures.mjs b/scripts/golden-safehaven/fixtures/build-fixtures.mjs index 06e9a460..fe460a28 100644 --- a/scripts/golden-safehaven/fixtures/build-fixtures.mjs +++ b/scripts/golden-safehaven/fixtures/build-fixtures.mjs @@ -161,9 +161,9 @@ writeJson(path.join(ASSETS, "redesign-integrity-receipt.json"), { "release-receipt", "seo-baseline", "visual-qa", + "redesign-integrity-receipt", "release-receipt-finalizer", "handoff-emitter", - "redesign-integrity-receipt", "terminal-convergence", ], competitive_landscape: { @@ -807,9 +807,9 @@ const STAGES = [ "release-receipt", "seo-baseline", "visual-qa", + "redesign-integrity-receipt", "release-receipt-finalizer", "handoff-emitter", - "redesign-integrity-receipt", "terminal-convergence", ]; for (let i = 0; i < STAGES.length; i++) { From 67e357cad99b8c324f7b944311bbd521dca3f485 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 17 Sep 2026 20:21:32 +0000 Subject: [PATCH 5/8] fix(repo): stop site:validate dirtying a tracked report MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit validation/validation_report.yaml is per-run output of scripts/validation-executor.ts, but it was tracked. Every `npm run site:validate` — and therefore every `verify:all` and every `make pr` — rewrote it with a fresh run_id, fresh timestamps, the local absolute target_roots and the running user's git identity. That is not cosmetic. It hard-blocks the sanctioned publish path: the L4 release receipt is bound to a tree digest, so `make pr` runs `pr-check`, `pr-check` dirties this file, and the L4 remote check then rejects the push as a stale receipt. The gate invalidates the authorization it is about to verify, and no amount of re-authorizing escapes it, because the next run dirties the tree again. Untracking it is the repo's own established convention, not a new one: .gitignore already ignores validation/launch_env_report.json and validation/deploy_secrets_report.json, which are the same class of per-run artifact from the same directory. Nothing consumes the committed copy. scripts/validation-executor.ts only writes it, no test or script reads it, and build-and-validate.yml generates its own at the repository root and uploads that as a workflow artifact. The file stays on disk; only the index entry is removed. As a side benefit the committed copy stops leaking one contributor's worktree path and email address into the repository. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_019Hmr3SKRhYQ56omTzNFZ4L --- .gitignore | 1 + validation/validation_report.yaml | 433 ------------------------------ 2 files changed, 1 insertion(+), 433 deletions(-) delete mode 100644 validation/validation_report.yaml diff --git a/.gitignore b/.gitignore index ab18dc01..bf0acbbc 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,7 @@ website-bot.db-wal # Generated launch-env / deploy-secret reports (per-run artifacts, not source) validation/launch_env_report.json validation/deploy_secrets_report.json +validation/validation_report.yaml astro_template/validation/*.jsonl # Test coverage artifacts (generated by c8/node --test, not source) diff --git a/validation/validation_report.yaml b/validation/validation_report.yaml deleted file mode 100644 index fe05df04..00000000 --- a/validation/validation_report.yaml +++ /dev/null @@ -1,433 +0,0 @@ -run_metadata: - run_id: 8a3153ad-c205-4d63-9dac-38301fc4d4c3 - report_schema_version: 1.0.0 - started_at: 2026-09-02T20:23:41.177Z - ended_at: 2026-09-02T20:23:41.443Z - duration: 266 - last_completed_stage: audit_report_generation -execution_context: - target_roots: - - /Users/ib-mac/.l9/website-bot-worktrees/replay-launch-env-20260902T012107Z - source_revision: efc53128be341a1bddb1178839c7265dd61eff35 - running_revision: null - target_environment: local - environment_type: local - active_identity: ib718@icloud.com - preflight_commands: - - node -e "require('node:fs').accessSync('astro_template/package.json')" - e2e_commands: - - npm --prefix astro_template run verify:rollback - test_runner: node:test - test_runner_version: v22.15.0 - configuration_sources: &a1 - - package.json - - tsconfig.json - - .env.example - - config/launch-env.required.yaml - - .github/workflows/ci.yml - - Makefile - - justfile - required_services: - - postgresql - - sqlite - target_endpoints: - - http://localhost:4321 - - http://localhost:3000 - required_dependencies: - - "@quantum-l9/bot-interop" - - "@quantum-l9/graphiti-memory-client" - - "@quantum-l9/infisical-config" - - "@quantum-l9/llm-router" - - better-sqlite3 - - drizzle-orm - - inngest - - pg - - pino - - undici - - yaml - - "@types/better-sqlite3" - - "@types/node" - - "@types/pg" - - pino-pretty - - playwright - - tsx - - typescript - required_credentials: - - NODE_AUTH_TOKEN - - PERPLEXITY_API_KEY - - OPENROUTER_API_KEY - - CLIENT_ID - - MONTHLY_BUDGET_PER_CLIENT - - WEEKLY_BUDGET_TARGET - - WEEKLY_BUDGET_HARD_CEILING - - LLM_PROVIDER_TIMEOUT_MS - - SITE_URL - - SITE_OUTPUT_DIR - - SITE_BUILD_TIMEOUT_MS - - SITE_BUILD_IGNORE_SCRIPTS - - ASTRO_TELEMETRY_DISABLED - - CLIENT_GITHUB_REPO - - CLIENT_GITHUB_REPO_ID - - CLIENT_SOURCE_BRANCH - - GITHUB_SITE_TOKEN - - VERCEL_TOKEN - - VERCEL_TEAM_ID - - VERCEL_TARGET - - WEBSITE_BOT_ALLOW_PRODUCTION - - VERCEL_POLL_INTERVAL_MS - - VERCEL_MAX_POLLS - - CLIENT_VERCEL_PROJECT_ID - - CLIENT_VERCEL_DEPLOY_HOOK - - PUBLIC_POSTHOG_KEY - - POSTHOG_HOST - - POSTHOG_PROJECT_ID - - POSTHOG_PERSONAL_API_KEY - - DATAFORSEO_LOGIN - - DATAFORSEO_PASSWORD - - SEO_BOT_URL - - SEO_BOT_API_KEY - - PROJECT_LICENSE - - LICENSE_CHOICE_REQUIRED - - SUPPORT_CONTACT_EMAIL - - SUPPORT_CONTACT_URL - - SECURITY_CONTACT_EMAIL - - SECURITY_DISCLOSURE_URL - - PUBLIC_SITE_URL - - PRODUCTION_DOMAIN - - DOMAIN_VERIFICATION_REQUIRED - - FORM_PROVIDER - - FORM_ENDPOINT_URL - - FORM_WEBHOOK_SECRET - - LEAD_NOTIFICATION_EMAIL - - ANALYTICS_PROVIDER - - ANALYTICS_MEASUREMENT_ID - - ANALYTICS_CONVERSION_EVENT - - ANALYTICS_THANK_YOU_EVENT - - LEGAL_DISCLAIMER_APPROVED - - LEGAL_DISCLAIMER_VERSION - - LEGAL_REVIEW_OWNER - - LICENSE_DISPLAY_REQUIRED - - PROFESSIONAL_LICENSE_NUMBER - - PROFESSIONAL_LICENSE_STATE - - PROFESSIONAL_LICENSE_TYPE - - CRM_PROVIDER - - CRM_API_BASE_URL - - CRM_CLIENT_ID - - CRM_CLIENT_SECRET - - CRM_API_TOKEN - - CRM_TEST_MODE - - GITHUB_PROVISION_TOKEN - - SEO_BOT_SITE_GITHUB_TOKEN - - SEO_BOT_SITE_VERCEL_DEPLOY_HOOK - - L9_MEMORY_MODE - - L9_MEMORY_URL - - L9_MEMORY_TOKEN - - L9_MEMORY_TOKEN_BUDGET - - L9_MEMORY_MAX_RECORDS - - GEMINI_API_KEY - - IMAGE_GENERATION_MODEL - - IMAGE_GENERATION_BUDGET_USD - evidence_root: build/evidence -authority_sources: - - source: package.json - revision_or_version: efc53128be341a1bddb1178839c7265dd61eff35 - applicable_scope: execution_configuration - precedence: 1 - verification_status: verified - - source: tsconfig.json - revision_or_version: efc53128be341a1bddb1178839c7265dd61eff35 - applicable_scope: execution_configuration - precedence: 2 - verification_status: verified - - source: .env.example - revision_or_version: current - applicable_scope: execution_configuration - precedence: 3 - verification_status: verified - - source: config/launch-env.required.yaml - revision_or_version: efc53128be341a1bddb1178839c7265dd61eff35 - applicable_scope: execution_configuration - precedence: 4 - verification_status: verified - - source: .github/workflows/ci.yml - revision_or_version: efc53128be341a1bddb1178839c7265dd61eff35 - applicable_scope: execution_configuration - precedence: 5 - verification_status: verified - - source: Makefile - revision_or_version: efc53128be341a1bddb1178839c7265dd61eff35 - applicable_scope: execution_configuration - precedence: 6 - verification_status: verified - - source: justfile - revision_or_version: current - applicable_scope: execution_configuration - precedence: 7 - verification_status: verified -discovery_inventory: - preflight_checks: &a2 - - check_id: astro-template-present - check_name: Astro template package present - blocking: true - command: node -e "require('node:fs').accessSync('astro_template/package.json')" - working_directory: /Users/ib-mac/.l9/website-bot-worktrees/replay-launch-env-20260902T012107Z - status: Passed - exit_code: 0 - termination_signal: null - started_at: 2026-09-02T20:23:41.235Z - ended_at: 2026-09-02T20:23:41.263Z - duration: 28 - primary_failure_classification: null - contributing_causes: [] - root_cause_group: null - evidence_references: - - preflight_astro-template-present_stdout - - preflight_astro-template-present_stderr - - preflight_astro-template-present_execution_trace - e2e_suites: - - Astro Template Site Validation - required_e2e_tests: &a3 - - suite_id: site-template - suite_name: Astro Template Site Validation - test_id: verify-rollback - test_name: rollback site-template validation - attempt: 1 - command_or_invocation: npm --prefix astro_template run verify:rollback - status: Passed - started_at: 2026-09-02T20:23:41.265Z - ended_at: 2026-09-02T20:23:41.440Z - duration: 175 - assertion_or_error: null - exit_code_or_runner_result: 0 - primary_failure_classification: null - contributing_causes: [] - root_cause_group: null - evidence_references: - - e2e_verify-rollback_stdout - - e2e_verify-rollback_stderr - - e2e_verify-rollback_execution_trace - dynamic_inventory_items: - - service-dependency:postgresql - - service-dependency:sqlite - - credential-dependent:NODE_AUTH_TOKEN - - credential-dependent:PERPLEXITY_API_KEY - - credential-dependent:OPENROUTER_API_KEY - - credential-dependent:CLIENT_ID - - credential-dependent:MONTHLY_BUDGET_PER_CLIENT - - credential-dependent:WEEKLY_BUDGET_TARGET - - credential-dependent:WEEKLY_BUDGET_HARD_CEILING - - credential-dependent:LLM_PROVIDER_TIMEOUT_MS - - credential-dependent:SITE_URL - - credential-dependent:SITE_OUTPUT_DIR - - credential-dependent:SITE_BUILD_TIMEOUT_MS - - credential-dependent:SITE_BUILD_IGNORE_SCRIPTS - - credential-dependent:ASTRO_TELEMETRY_DISABLED - - credential-dependent:CLIENT_GITHUB_REPO - - credential-dependent:CLIENT_GITHUB_REPO_ID - - credential-dependent:CLIENT_SOURCE_BRANCH - - credential-dependent:GITHUB_SITE_TOKEN - - credential-dependent:VERCEL_TOKEN - - credential-dependent:VERCEL_TEAM_ID - - credential-dependent:VERCEL_TARGET - - credential-dependent:WEBSITE_BOT_ALLOW_PRODUCTION - - credential-dependent:VERCEL_POLL_INTERVAL_MS - - credential-dependent:VERCEL_MAX_POLLS - - credential-dependent:CLIENT_VERCEL_PROJECT_ID - - credential-dependent:CLIENT_VERCEL_DEPLOY_HOOK - - credential-dependent:PUBLIC_POSTHOG_KEY - - credential-dependent:POSTHOG_HOST - - credential-dependent:POSTHOG_PROJECT_ID - - credential-dependent:POSTHOG_PERSONAL_API_KEY - - credential-dependent:DATAFORSEO_LOGIN - - credential-dependent:DATAFORSEO_PASSWORD - - credential-dependent:SEO_BOT_URL - - credential-dependent:SEO_BOT_API_KEY - - credential-dependent:PROJECT_LICENSE - - credential-dependent:LICENSE_CHOICE_REQUIRED - - credential-dependent:SUPPORT_CONTACT_EMAIL - - credential-dependent:SUPPORT_CONTACT_URL - - credential-dependent:SECURITY_CONTACT_EMAIL - - credential-dependent:SECURITY_DISCLOSURE_URL - - credential-dependent:PUBLIC_SITE_URL - - credential-dependent:PRODUCTION_DOMAIN - - credential-dependent:DOMAIN_VERIFICATION_REQUIRED - - credential-dependent:FORM_PROVIDER - - credential-dependent:FORM_ENDPOINT_URL - - credential-dependent:FORM_WEBHOOK_SECRET - - credential-dependent:LEAD_NOTIFICATION_EMAIL - - credential-dependent:ANALYTICS_PROVIDER - - credential-dependent:ANALYTICS_MEASUREMENT_ID - - credential-dependent:ANALYTICS_CONVERSION_EVENT - - credential-dependent:ANALYTICS_THANK_YOU_EVENT - - credential-dependent:LEGAL_DISCLAIMER_APPROVED - - credential-dependent:LEGAL_DISCLAIMER_VERSION - - credential-dependent:LEGAL_REVIEW_OWNER - - credential-dependent:LICENSE_DISPLAY_REQUIRED - - credential-dependent:PROFESSIONAL_LICENSE_NUMBER - - credential-dependent:PROFESSIONAL_LICENSE_STATE - - credential-dependent:PROFESSIONAL_LICENSE_TYPE - - credential-dependent:CRM_PROVIDER - - credential-dependent:CRM_API_BASE_URL - - credential-dependent:CRM_CLIENT_ID - - credential-dependent:CRM_CLIENT_SECRET - - credential-dependent:CRM_API_TOKEN - - credential-dependent:CRM_TEST_MODE - - credential-dependent:GITHUB_PROVISION_TOKEN - - credential-dependent:SEO_BOT_SITE_GITHUB_TOKEN - - credential-dependent:SEO_BOT_SITE_VERCEL_DEPLOY_HOOK - - credential-dependent:L9_MEMORY_MODE - - credential-dependent:L9_MEMORY_URL - - credential-dependent:L9_MEMORY_TOKEN - - credential-dependent:L9_MEMORY_TOKEN_BUDGET - - credential-dependent:L9_MEMORY_MAX_RECORDS - - credential-dependent:GEMINI_API_KEY - - credential-dependent:IMAGE_GENERATION_MODEL - - credential-dependent:IMAGE_GENERATION_BUDGET_USD - authoritative_skips: [] - inventory_sources: *a1 - inventory_status: complete -preflight_summary: - discovered: 1 - executable: 1 - executed: 1 - passed: 1 - failed: 0 - errors: 0 - timeouts: 0 - blocked: 0 - authoritatively_skipped: 0 - not_executed: 0 - unknown: 0 - blocking_total: 1 - blocking_passed: 1 - gate_status: Passed -preflight_results: *a2 -preflight_gate: - status: Passed - blocking_failures: [] - blocking_unknowns: [] - e2e_authorized: true - decision_evidence: - - preflight_astro-template-present_result -e2e_summary: - discovered_suites: 1 - discovered_required_tests: 1 - executed_unique_tests: 1 - execution_attempts: 1 - passed: 1 - failed: 0 - errors: 0 - timeouts: 0 - blocked_by_preflight_gate: 0 - blocked_by_authoritative_fail_fast: 0 - blocked: 0 - not_executed: 0 - authoritatively_skipped: 0 - unknown: 0 - runner_crashes: 0 - gate_status: Passed -e2e_results: *a3 -coverage_reconciliation: - preflight_discovered_total: 1 - preflight_accounted_total: 1 - preflight_reconciled: true - e2e_required_discovered_total: 1 - e2e_required_accounted_total: 1 - e2e_reconciled: true - dynamic_inventory_reconciliation: complete - retry_accounting: complete - unaccounted_items: [] - status: complete -root_cause_groups: [] -defects: [] -regressions: [] -unknowns: [] -evidence_manifest: - - evidence_id: preflight_astro-template-present - evidence_type: command_execution - file_path: preflight_astro-template-present.json - checksum: 448038322ae63f76cfbd5a49452bbe150fdad3ec6bc43051766fc6d30eef3229 - file_size: 597 - created_at: 2026-09-02T20:23:41.264Z - integrity_validated: true - - evidence_id: e2e_verify-rollback_attempt_1 - evidence_type: command_execution - file_path: e2e_verify-rollback_attempt_1.json - checksum: 0b7fc2597a3ce6658cd428a261ab72e6b834a6de3b67bc1eb3a2294eeb311a1f - file_size: 592 - created_at: 2026-09-02T20:23:41.441Z - integrity_validated: true -validation_gates: - execution_context_resolved: - status: Passed - evidence_references: - - execution_context_validation_8a3153ad-c205-4d63-9dac-38301fc4d4c3 - authoritative_inventory_resolved: - status: Passed - evidence_references: - - inventory_validation_8a3153ad-c205-4d63-9dac-38301fc4d4c3 - preparation_passed: - status: Passed - evidence_references: - - preparation_validation_8a3153ad-c205-4d63-9dac-38301fc4d4c3 - preflight_inventory_complete: - status: Passed - evidence_references: [] - preflight_passed: - status: Passed - evidence_references: - - preflight_gate_evaluation_6838455d-8a03-4bda-a131-7a4803e0de1c - - preflight_astro-template-present_result - e2e_gate_enforced: - status: Passed - evidence_references: [] - e2e_inventory_complete: - status: Passed - evidence_references: [] - e2e_tests_passed: - status: Passed - evidence_references: - - e2e_gate_evaluation_8456a3b2-ed7f-43d4-8873-415e25ee3ed9 - - e2e_verify-rollback_result - no_unauthorized_skips: - status: Passed - evidence_references: [] - no_result_replacement: - status: Passed - evidence_references: [] - evidence_complete: - status: Passed - evidence_references: - - evidence_integrity_8a3153ad-c205-4d63-9dac-38301fc4d4c3 - failure_classification_complete: - status: Passed - evidence_references: [] - regression_evidence_valid: - status: Passed - evidence_references: [] - coverage_reconciled: - status: Passed - evidence_references: [] - target_unchanged: - status: Passed - evidence_references: [] - report_schema_valid: - status: Passed - evidence_references: [] - overall_validation_run: - status: Passed - evidence_references: [] -final_verdict: - status: PASS - preflight_status: Passed - e2e_status: Passed - coverage_status: Passed - evidence_status: Passed - required_failure_count: 0 - blocking_defect_ids: [] - unknown_count: 0 - verdict_reason: All required validation gates passed and no test failures detected -minimum_safe_next_action: - action: NoActionRequired - blocker_or_failure: None - expected_evidence: Validation completed successfully From cf4492e364510188ba127f1ddd80774027be3738 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 17 Sep 2026 19:13:34 +0000 Subject: [PATCH 6/8] chore(gitignore): ignore non-npm lockfiles MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This repo is npm-only: .github/workflows/l9-lint-test.yml and l9-lint-test-node.yml both pin PACKAGE_MANAGER="npm", regen-lockfile.yml knows only `npm install --package-lock-only`, and four package-lock.json files are tracked. .gitignore carried no lockfile entries at all, so a stray pnpm-lock.yaml sat untracked and committable. It is container debris — never tracked on any ref, and its mtime matches every other file in the clone rather than any commit. Left unignored it is one `git add -A` away from landing, and the dormant pnpm/yarn arms of the lint workflows would treat it as load-bearing the moment PACKAGE_MANAGER moved. Appended at end of file rather than in the secrets block: PR #207 deletes .gitignore:41, and a hunk there would overlap one written nearby. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_019Hmr3SKRhYQ56omTzNFZ4L (cherry picked from commit 3bf384caca440a43d4e5466957c671b35c353057) --- .gitignore | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.gitignore b/.gitignore index bf0acbbc..05abfb2c 100644 --- a/.gitignore +++ b/.gitignore @@ -60,3 +60,11 @@ reports/test-runs/**/*.pem reports/test-runs/**/*.key reports/test-runs/**/*secret* reports/test-runs/**/*credential* + +# Non-npm lockfiles — this repo is npm-only (CI pins PACKAGE_MANAGER=npm and +# tracks package-lock.json). A stray lockfile from another package manager is +# container debris, and the l9-lint-test workflows' pnpm/yarn arms would treat +# it as load-bearing the moment PACKAGE_MANAGER changed. +pnpm-lock.yaml +yarn.lock +bun.lockb From da0da51ae2c9966f2534c230ed5f5336b0e69099 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 17 Sep 2026 20:27:31 +0000 Subject: [PATCH 7/8] fix(normalize-spec): cover both committed pairs and make overrides atomic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two defects in one CLI, both reachable today. Coverage. The default check and write were hardcoded to examples/supplemental-insurance-pros, so the second committed pair was verified by nothing. It duly rotted: examples/quantum-ai-partners was last written in #174, the v1.1 semantic compiler landed in #196, and the committed IR is six top-level keys behind it — routes (missing purpose, template and priority on every route), seo_contract (missing route_targets and schema_application), and business_facts, conversion_authority, content_guardrails and semantic_provenance absent entirely. An artifact nothing verifies is an artifact that drifts. Both pairs are now checked and regenerated together, and the regenerated quantum IR lands in this commit because the script alone would leave the gate red. Pairing. A source and its normalized output are one unit, but a one-sided override filled the missing half from the first committed pair: { in: inArg ?? COMMITTED_SPECS[0].in, out: outArg ?? COMMITTED_SPECS[0].out } So `--in examples/quantum-ai-partners/domain_spec.source.yaml` with no --out compiled quantum and wrote it over supplemental's committed artifact — the wrong client's file, overwritten while the invocation looked entirely deliberate. An override now names both sides, or one side that resolves to exactly one committed pair; anything else is an error rather than a guess. Also pins stringify lineWidth: 0. The committed artifacts were generated unfolded while the writer kept the yaml package's 80-column default, so a regeneration rewrapped an in-sync file by +76 bytes and the gate stayed green through it — it compares parsed objects, not bytes. Verified inert: after this change `npm run normalize-spec` leaves the supplemental artifact byte-identical. The three-function split that the superseded #198 used for this is deliberately not carried over: a loop over the pair list is the same behaviour at roughly a quarter of the diff. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_019Hmr3SKRhYQ56omTzNFZ4L --- .../domain_spec.normalized.yaml | 166 ++++++++++++++++-- scripts/normalize-spec.ts | 82 +++++++-- 2 files changed, 220 insertions(+), 28 deletions(-) diff --git a/examples/quantum-ai-partners/domain_spec.normalized.yaml b/examples/quantum-ai-partners/domain_spec.normalized.yaml index 0e026a0a..be2de44a 100644 --- a/examples/quantum-ai-partners/domain_spec.normalized.yaml +++ b/examples/quantum-ai-partners/domain_spec.normalized.yaml @@ -27,6 +27,9 @@ routes: - process - faq - final_cta + purpose: Position the company, route the three audience segments, show the layered services model, and capture email conversions. + template: homepage + priority: 1 - slug: /services title: Services components: @@ -36,6 +39,9 @@ routes: - process - faq - cta + purpose: Explain the three service lines and how they compose into one layered engagement. + template: services_page + priority: 2 - slug: /approach title: Approach components: @@ -45,6 +51,9 @@ routes: - stack - faq - cta + purpose: Explain the layered systems methodology — strategy, build, production hardening — with real depth, no hype. + template: approach_page + priority: 3 - slug: /about title: About components: @@ -52,18 +61,27 @@ routes: - positioning - trust_bar - cta + purpose: State who Quantum AI Partners is and what it stands for, without unverified credentials. + template: about_page + priority: 4 - slug: /faq title: FAQ components: - hero - faq - cta + purpose: Answer cost, process, technology, security, and engagement questions. + template: faq_page + priority: 5 - slug: /contact title: Contact components: - hero - contact_options - cta + purpose: Primary conversion page — email and intro-call path. + template: contact_page + priority: 6 seo_contract: site_url: https://www.quantumaipartners.com target_keywords: @@ -83,10 +101,46 @@ seo_contract: - AI consulting engagement model - Quantum AI Partners - AI consultancy Quantum AI Partners + route_targets: + - cluster_name: ai_consulting + target_page: /services + intent: service_provider + keywords: + - AI consulting + - AI consulting services + - AI consulting firm + - enterprise AI consulting + - cluster_name: ai_implementation + target_page: /approach + intent: service_provider + keywords: + - AI implementation + - LLM integration + - custom AI development + - AI agent development + - cluster_name: ai_strategy + target_page: /services + intent: service_provider + keywords: + - AI strategy + - AI adoption roadmap + - AI readiness assessment + - cluster_name: ai_faq + target_page: /faq + intent: informational + keywords: + - AI consulting FAQ + - how much does AI consulting cost + - AI consulting engagement model + - cluster_name: brand + target_page: /about + intent: informational + keywords: + - Quantum AI Partners + - AI consultancy Quantum AI Partners metadata_rules: title_pattern: "{Primary Keyword} | Quantum AI Partners" - description_pattern: Engineering-grade {Topic} for {Audience} — AI strategy, - custom systems, and production hardening without the hype. + description_pattern: Engineering-grade {Topic} for {Audience} — AI strategy, custom systems, and production hardening without the hype. schema_rules: - Organization - ProfessionalService @@ -108,6 +162,102 @@ seo_contract: - thin_city_pages_before_national_strategy_is_approved - guaranteed_outcome_claim_pages wom_flags: [] +business_facts: + legal_name: Quantum AI Partners + tagline: Engineering-grade AI systems for business. + brand_positioning: An AI consultancy and delivery partner for strategy, custom AI/LLM systems, and production hardening — engineering-first, without hype. + primary_value: Take organizations from AI intent to working, evaluated, production-grade systems through a layered systems discipline. + competitive_angle: Layered-systems AI engineering — strategy, build, and production hardening as one continuous discipline, expressed without hype. + core_offer: AI strategy, custom AI and LLM systems, and production hardening. + offer_type: service + pricing_visibility: engagement_based + deliverables: + - strategy_roadmap + - working_prototype + - production_system + - evaluation_suite + - knowledge_transfer + decision_makers: + - cto + - vp_engineering + - head_of_product + - founder + pain_points: + - llm_adoption_uncertainty + - integration_risk + - talent_gap + - evaluation_gaps + - cost_uncertainty + - ai_hype_fatigue + - data_privacy_concerns + trust_requirements: + - engineering_depth + - honest_scoping + - security_first + - no_hype_language + - clear_pricing_signals + service_area_model: remote_first + service_lines: + - AI Strategy & Roadmaps + - Custom AI & LLM Systems + - AI Modernization & MLOps +conversion_authority: + primary_action: Book an Intro Call + secondary_actions: + - Email Us + - Explore Services + cta_library: + - Book an Intro Call + - Email Us + - Explore Services + - See the Approach +content_guardrails: + forbidden_claims: + - agi_claims + - fake_benchmarks + - guaranteed_roi + - hallucination_free_guarantees + - invented_case_studies + - invented_client_names + - performance_guarantees + - trusted_by_claims +semantic_provenance: + source_spec_version: 1.0.0 + compiler_version: 1.1.0 + runtime_authority_paths: + - assets + - audience + - authority + - build_intent + - client_vision + - content + - conversion + - design + - design_references + - geography + - identity + - market + - offer + - seo + gate_paths: + - authority + - compliance + - design + - validation + provenance_paths: + - authority + - content + - deployment + - design + - experiments + - future_outputs + - geography + - identity + - integrations + - market + - metadata + - notes + - validation assets: generation: enabled: true @@ -117,12 +267,9 @@ assets: build_intent: REDESIGN_IMPROVE client_vision: desired_outcomes: - - Position Quantum AI Partners as a distinctive, engineering-deep AI partner - — never a generic AI consultancy template. - - Convert visitors into intro-call and email inquiries through a clear, - low-friction path. - - Communicate layered-systems depth (strategy to build to production) - without hype. + - Position Quantum AI Partners as a distinctive, engineering-deep AI partner — never a generic AI consultancy template. + - Convert visitors into intro-call and email inquiries through a clear, low-friction path. + - Communicate layered-systems depth (strategy to build to production) without hype. brand_attributes: - distinctive - graphically_rich @@ -200,8 +347,7 @@ design_references: negative: [] - reference_id: baseten url: https://www.baseten.co/ - selection_reason: "Client likes: cool graphics; shows layers; layers apply to L9 - and Quantum AI Partners." + selection_reason: "Client likes: cool graphics; shows layers; layers apply to L9 and Quantum AI Partners." evidence_refs: - likes_dislikes_table_2026-09-01 principles: diff --git a/scripts/normalize-spec.ts b/scripts/normalize-spec.ts index 6c3869c8..17e03a79 100644 --- a/scripts/normalize-spec.ts +++ b/scripts/normalize-spec.ts @@ -376,31 +376,77 @@ function carryStructuredAssets(ds: any, flat: DomainSpec): void { } } +/** + * Every committed source -> normalized IR pair. The default gate used to cover + * only the first, so the second rotted silently the moment the v1.1 compiler + * began emitting route semantics: an artifact nothing verifies is an artifact + * that drifts. Both are checked and regenerated together. + */ +const COMMITTED_SPECS: ReadonlyArray<{ in: string; out: string }> = [ + { + in: "examples/supplemental-insurance-pros/domain_spec.source.yaml", + out: "examples/supplemental-insurance-pros/domain_spec.normalized.yaml", + }, + { + in: "examples/quantum-ai-partners/domain_spec.source.yaml", + out: "examples/quantum-ai-partners/domain_spec.normalized.yaml", + }, +]; + +/** + * A source and its normalized output are one unit, so an override names either + * both sides or neither. Filling the missing half from a default let + * `--in ` write over a DIFFERENT client's committed artifact while + * looking entirely deliberate; a one-sided flag now resolves only against a + * committed pair, and an unrecognised path is an error rather than a guess. + */ +function resolveTargets(inArg?: string, outArg?: string): ReadonlyArray<{ in: string; out: string }> { + if (inArg === undefined && outArg === undefined) return COMMITTED_SPECS; + if (inArg !== undefined && outArg !== undefined) return [{ in: inArg, out: outArg }]; + const side = inArg !== undefined ? "in" : "out"; + const value = (inArg ?? outArg) as string; + const matches = COMMITTED_SPECS.filter((pair) => pair[side] === value); + if (matches.length !== 1) { + throw new Error( + `--${side} ${value} does not identify exactly one committed spec pair ` + + `(${matches.length} matched). Pass both --in and --out, or name a committed ` + + `--${side} path: ${COMMITTED_SPECS.map((pair) => pair[side]).join(", ")}`, + ); + } + return matches; +} + function main() { const args = process.argv.slice(2); const check = args.includes("--check"); - const inPath = getArg(args, "--in") ?? "examples/supplemental-insurance-pros/domain_spec.source.yaml"; - const outPath = getArg(args, "--out") ?? "examples/supplemental-insurance-pros/domain_spec.normalized.yaml"; - const flat = buildFlatSpec(parse(readFileSync(inPath, "utf-8"))); - validateDomainSpec(flat, `${inPath} (normalized)`); - if (check) { - const committed = parse(readFileSync(outPath, "utf-8")); - if (!deepEqual(flat, committed)) { - console.error(`normalize-spec --check FAILED: ${outPath} is stale.\nRegenerate with: tsx scripts/normalize-spec.ts`); - for (const key of Object.keys(flat)) { - if (!deepEqual((flat as any)[key], (committed as any)?.[key])) { - console.error(` first diff at key: ${key}`); - break; + const targets = resolveTargets(getArg(args, "--in"), getArg(args, "--out")); + for (const { in: inPath, out: outPath } of targets) { + const flat = buildFlatSpec(parse(readFileSync(inPath, "utf-8"))); + validateDomainSpec(flat, `${inPath} (normalized)`); + if (check) { + const committed = parse(readFileSync(outPath, "utf-8")); + if (!deepEqual(flat, committed)) { + console.error(`normalize-spec --check FAILED: ${outPath} is stale.\nRegenerate with: tsx scripts/normalize-spec.ts`); + for (const key of Object.keys(flat)) { + if (!deepEqual((flat as any)[key], (committed as any)?.[key])) { + console.error(` first diff at key: ${key}`); + break; + } } + process.exit(1); } - process.exit(1); + console.log(`normalize-spec --check OK: ${outPath} matches normalize(${inPath}).`); + continue; } - console.log(`normalize-spec --check OK: ${outPath} matches normalize(${inPath}).`); - return; + mkdirSync(dirname(outPath), { recursive: true }); + // lineWidth: 0 disables folding. The committed artifacts were generated + // unfolded while the writer kept the yaml package's 80-column default, so + // without this pin a regeneration rewraps an in-sync file by +76 bytes and + // the gate stays green through the churn — it compares parsed objects, not + // bytes. + writeFileSync(outPath, stringify(flat, { lineWidth: 0 }), "utf-8"); + console.log(`Wrote ${outPath} from ${inPath}.`); } - mkdirSync(dirname(outPath), { recursive: true }); - writeFileSync(outPath, stringify(flat), "utf-8"); - console.log(`Wrote ${outPath} from ${inPath}.`); } /** From 02a3a75bf913dc44cc91b998505d69cbf46e51cb Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 17 Sep 2026 20:27:39 +0000 Subject: [PATCH 8/8] test(normalize-spec): pin atomic override pairing and both-pair coverage Four tests, each verified to fail against the parent commit's script and pass against this one (14 pass / 4 fail before, 18 pass after). The load-bearing one asserts that `--check --in ` is judged against quantum's OWN output and leaves supplemental's committed artifact byte-identical and untouched by mtime. That is the exact shape of the defect: the old resolution would have compared quantum's compile against supplemental's artifact, and without --check would have overwritten it. The others cover the mirror case (a one-sided --out), the failure case (a one-sided path matching no committed pair must error rather than pick one), and the preservation obligation that the no-flag default still names every committed pair, so the gate cannot quietly narrow back to one client. These reuse the spawnSync + mtime/body snapshot harness already used by the entry-point-guard test, which is the only existing test that exercises the CLI rather than importing buildFlatSpec. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_019Hmr3SKRhYQ56omTzNFZ4L --- tests/unit/normalize-spec.test.ts | 59 +++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/tests/unit/normalize-spec.test.ts b/tests/unit/normalize-spec.test.ts index b96485e5..932603d2 100644 --- a/tests/unit/normalize-spec.test.ts +++ b/tests/unit/normalize-spec.test.ts @@ -294,3 +294,62 @@ test("importing the normalizer does not run its CLI writer", () => { ); assert.equal(readFileSync(outPath, "utf-8"), bodyBefore); }); + +const SUPPLEMENTAL_OUT = "examples/supplemental-insurance-pros/domain_spec.normalized.yaml"; +const QUANTUM_SOURCE = "examples/quantum-ai-partners/domain_spec.source.yaml"; +const QUANTUM_OUT = "examples/quantum-ai-partners/domain_spec.normalized.yaml"; + +function runCli(args: string[]) { + return spawnSync(process.execPath, ["--import", "tsx", "scripts/normalize-spec.ts", ...args], { + encoding: "utf-8", + }); +} + +test("a one-sided --in resolves its own committed pair, never another client's output", () => { + // The defect this guards: filling the missing half from COMMITTED_SPECS[0] + // paired one client's source with a different client's artifact, so + // `--in ` compiled quantum and wrote it over supplemental's + // committed IR while looking entirely deliberate. + const bodyBefore = readFileSync(SUPPLEMENTAL_OUT, "utf-8"); + const mtimeBefore = statSync(SUPPLEMENTAL_OUT).mtimeMs; + + const result = runCli(["--check", "--in", QUANTUM_SOURCE]); + + assert.equal(result.status, 0, result.stderr); + assert.match( + result.stdout + result.stderr, + new RegExp(QUANTUM_OUT.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")), + "a one-sided --in must be judged against its own pair's output", + ); + assert.equal(readFileSync(SUPPLEMENTAL_OUT, "utf-8"), bodyBefore); + assert.equal(statSync(SUPPLEMENTAL_OUT).mtimeMs, mtimeBefore); +}); + +test("a one-sided --out resolves its own committed pair", () => { + const result = runCli(["--check", "--out", QUANTUM_OUT]); + assert.equal(result.status, 0, result.stderr); + assert.match( + result.stdout, + new RegExp(QUANTUM_SOURCE.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")), + "a one-sided --out must compile its own pair's source", + ); +}); + +test("a one-sided override naming no committed pair fails instead of guessing", () => { + const result = runCli(["--check", "--in", "examples/does-not-exist/domain_spec.source.yaml"]); + assert.notEqual(result.status, 0, "an unrecognised one-sided path must not silently pick a pair"); + assert.match(result.stderr, /does not identify exactly one committed spec pair/); +}); + +test("the default gate covers every committed source-to-normalized pair", () => { + // Preservation obligation: the check must not narrow back to one client. + const result = runCli(["--check"]); + assert.equal(result.status, 0, result.stderr); + for (const outPath of [SUPPLEMENTAL_OUT, QUANTUM_OUT]) { + assert.match( + result.stdout, + new RegExp(outPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")), + `${outPath} must be covered by the default check`, + ); + } +});