fix(pipeline): gate redesign integrity before success and handoff - #208
Merged
Merged
Conversation
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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Hmr3SKRhYQ56omTzNFZ4L
…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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019Hmr3SKRhYQ56omTzNFZ4L
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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Hmr3SKRhYQ56omTzNFZ4L
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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019Hmr3SKRhYQ56omTzNFZ4L
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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019Hmr3SKRhYQ56omTzNFZ4L
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019Hmr3SKRhYQ56omTzNFZ4L (cherry picked from commit 3bf384caca440a43d4e5466957c671b35c353057)
This was referenced Sep 17, 2026
Both hard-coded stage lists in the safehaven fixture builder went seo-build-intelligence-preflight -> competitive-intelligence, skipping design-reference-acquisition. The real REDESIGN_IMPROVE plan puts it between those two (FactoryExecutionPlan.ts:275-279), and it is listed in REDESIGN_ADDED_MANDATORY, so it is mandatory for every redesign run in every mode. The fixture declares mode: "end-to-end" with build_intent: "REDESIGN_IMPROVE" and claims to represent a green run of exactly that plan, so a stage the plan cannot omit was missing from the record. executed_stages fed the emitted receipt and STAGES drives the synthetic checkpoint files and the SQLite stage_runs rows, so both the receipt and the simulated stage history described a run that could not occur. Verified against the plan builder rather than by eye: an end-to-end REDESIGN_IMPROVE plan emits ["seo-build-intelligence-preflight", "design-reference-acquisition", "competitive-intelligence"] at that point, which is now what both lists contain. Found by Copilot's review on the stacked PR #210. It is a pre-existing gap rather than one the preceding fixture commit introduced — that commit corrected the tail of these lists — but the file is this PR's to get right, and a fixture that misdescribes the pipeline is worse than no fixture. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019Hmr3SKRhYQ56omTzNFZ4L
|
20 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Summary
Closes audit
website-bot-core-pipeline-2026-09-17finding F1: the redesign integrity gate ran after the release was recorded as succeeded and after the external handoff POST, so it could only reject a run that had already been published. It now runs aftervisual-qaand beforerelease-receipt-finalizerandhandoff-emitter. Also fixes a defect the audit did not find, which would have made F1's fix inert on the durable path.Type of change
type:bugfix— bug fixArea
area:backend— src/, packages/Checklist
npm run verify:allpasses locallynpx tsc --noEmitpasses (no type errors)VALIDATION.mdupdated if validation artifacts changed — not applicableDECISION_LOG.mdupdated if relevant — not applicableRelated issues / ADRs / contracts
website-bot-core-pipeline-2026-09-17findings F1 (INVARIANT, High) and W2 (new)docs/adr/ADR-0018-website-build-blueprint-v2-single-authority.mddocs/architecture/WEBSITE_BUILD_BLUEPRINT_V2_INVARIANTS.mdCommits
a355629b6605f6buildIntentinto the durable pipeline's execution plan6906e3fecc278667e357csite:validatedirtying a tracked reportWhat the reorder does and does not change
ReleaseReceiptFinalizerStagewritesstatus: "succeeded";HandoffEmitterStageperforms the external POST to SEO-Bot (externalMutation: true). A gate positioned after them cannot prevent an invalid redesign being published, only complain afterwards.The gate deliberately stays outside the
end-to-endguard:REDESIGN_ADDED_MANDATORYmakesredesign-integrity-receiptmandatory in every mode, so folding it into that branch would makerequireMandatoryConvergencethrow onplan,local-proofandpublish-proofredesign runs.Two intended behavioural consequences:
REDESIGN_PIPELINE_INCOMPLETErather thanRELEASE_EVIDENCE_INCOMPLETE. That is fail-closed-earlier, not a regression.executed_stagesin the emitted receipt no longer listsrelease-receipt-finalizerorhandoff-emitter, since it snapshotsctx.stageResultsat emit time. Nothing validates that field.The defect the audit missed
src/inngest/website-pipeline.tscomputedctx.buildIntentfrom the spec and then built its execution plan without passing it, soredesignwas alwaysfalsethere. AREDESIGN_IMPROVEspec submitted through Inngest ran the COPY topology — no preflight, no competitive intelligence, no redesign authority chain, and noredesign-integrity-receiptstage at all. Reordering the gate fixes the plan builder, but on that path the gate was never in the plan, so F1's fix would have been inert.Why a
.gitignorechange is in a pipeline PRvalidation/validation_report.yamlis per-run output ofscripts/validation-executor.tsbut was tracked, so everysite:validate— and therefore everyverify:alland everymake pr— rewrote it. That hard-blocks the publish path: the release receipt is bound to a tree digest, so the gate dirtied the tree it was about to verify and refused its own push. Untracking follows the repo's existing convention (.gitignorealready ignoresvalidation/launch_env_report.jsonandvalidation/deploy_secrets_report.json). Nothing reads the committed copy; CI generates and uploads its own.Verification
npm run verify:all— 588 tests, all passing, on67e357c.Two
rendered-site-browser.test.tscases initially failed on a missing Playwright browser (the container shipschromium_headless_shell-1194; the pinned Playwright 1.62.1 wants build1234). That was fixed by installing the correct browser build so the tests genuinely run — they were not skipped, weakened or mocked.The new ordering assertion was verified to discriminate: it fails against the parent commit's plan builder (8 pass / 1 fail) and passes against the reordered one (9 pass). The pre-existing COPY end-to-end
assert.deepEqualis deliberately untouched and passes unmodified — it is the evidence that the reorder did not disturb the non-redesign topology.Publication note
make prran the full gate — L4 release check, main-bound ancestry, PR overlap probe (disjoint from #207), andpr-check— and pushed this branch. It could not compose the PR body itself:open_pr_after_gate.shrequiresops/config/root-file-protection.jsonunder the workspace root, and Website-Bot has noops/directory. That is a consumer-side governance wiring gap, reported separately rather than worked around by inventing a root-file policy for this repo.Generated by Claude Code