Skip to content

fix(gitignore): drop remediator re-ignore of Claude local settings - #207

Merged
cryptoxdog merged 2 commits into
mainfrom
agent/cursor/drop-settings-local
Sep 19, 2026
Merged

cryptoxdog merged 2 commits into
mainfrom
agent/cursor/drop-settings-local

Conversation

@cryptoxdog

Copy link
Copy Markdown
Collaborator

Summary

Type of change

  • type:feature — new feature or capability — not measured by open_pr_after_gate.sh — do not treat as verified
  • type:bugfix — bug fix — not measured by open_pr_after_gate.sh — do not treat as verified
  • type:refactor — refactor, no behavior change — not measured by open_pr_after_gate.sh — do not treat as verified
  • type:docs — documentation only — not measured by open_pr_after_gate.sh — do not treat as verified
  • type:ci — CI / workflow changes — not measured by open_pr_after_gate.sh — do not treat as verified
  • type:security — security fix or hardening — not measured by open_pr_after_gate.sh — do not treat as verified

Area

  • area:backend — src/, packages/ — not measured by open_pr_after_gate.sh — do not treat as verified
  • area:frontend — UI — not measured by open_pr_after_gate.sh — do not treat as verified
  • area:infrastructure — docker, deployment, config — not measured by open_pr_after_gate.sh — do not treat as verified
  • area:workflows — .github/, CI — not measured by open_pr_after_gate.sh — do not treat as verified
  • area:llm-router — @quantum-l9/llm-router consumption (src/services/llm.ts) — not measured by open_pr_after_gate.sh — do not treat as verified

Checklist

  • npm run verify:all passes locally — not measured by open_pr_after_gate.sh — do not treat as verified
  • npx tsc --noEmit passes (no type errors) — not measured by open_pr_after_gate.sh — do not treat as verified
  • No secrets or credentials committed — not measured by open_pr_after_gate.sh — do not treat as verified
  • ADR added/updated if this is an architectural decision — not measured by open_pr_after_gate.sh — do not treat as verified
  • VALIDATION.md updated if validation artifacts changed — not measured by open_pr_after_gate.sh — do not treat as verified
  • DECISION_LOG.md updated if relevant — not measured by open_pr_after_gate.sh — do not treat as verified

Related issues / ADRs / contracts


Commits

  • fix(gitignore): drop remediator re-ignore of Claude local settings

Test plan

  • make pr local gate receipt present
  • L4 release receipt present (release_authorized)
  • CI green — not measured by open_pr_after_gate.sh — do not treat as verified

Changed files

  • M .gitignore

Mechanical evidence

gate-receipt.json present: schema=l9.pr_gate_receipt.v2 head=None passed_at=2026-09-15T14:46:19Z
L4 receipt present: phase=release_authorized head=67abe9c249c4eaac0bc8dff3351d40fa4d31f380

Website-Bot must match the other undoA repos: session-injected Claude paths stay unignored. The #188 remediator line put .claude/settings.local.json back.

Co-authored-by: Cursor <cursoragent@cursor.com>
Copilot AI lite review requested due to automatic review settings September 15, 2026 14:46

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot wasn't able to review any files in this pull request.


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 67abe9c249

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .gitignore
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-15T14:49:39.577270Z 67abe9c PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

cryptoxdog pushed a commit that referenced this pull request Sep 17, 2026
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)

Copy link
Copy Markdown
Collaborator Author

Note before merge — audit finding F1

Recording this on the PR because it is about to be merged and the finding should not live only in a chat transcript.

Audit website-bot-all-open-prs-2026-09-17 (finding F1, Medium) says of this PR:

#207 makes machine-local Claude settings trackable. Current main explicitly classifies .claude/settings.local.json as a local machine override that should never be committed. #207 deletes that exclusion. Keep the ignore line; simplest disposition is do not merge #207.

Verified directly: .gitignore on main and on #208/#210/#211 contains the .claude/settings.local.json entry; at this PR's head it does not. Once this lands, that file is no longer ignored and is one git add -A away from being committed — and it is the file that carries machine-local Claude configuration.

The repository operator was shown this finding and elected to merge #207 as written. That is their call, and this comment is the record rather than an objection.

If the ignore is wanted back later, it is one line restored under the "Local secrets and Claude Code machine overrides — never commit" block in .gitignore.


Generated by Claude Code

Restore the machine-override exclusion this branch deleted so workstation
Claude settings cannot be staged by a routine git add.

Remediation-Cycle: #207/cycle-1
@cryptoxdog

Copy link
Copy Markdown
Collaborator Author

PR Remediation — Cycle 1 Summary

Commit: none | Findings processed: 1 | CI gates: Unknown

Fixed (1)

Finding File Change
Keep Claude settings.local.json ignored .gitignore Restored .claude/settings.local.json under the machine-override block

Deferred (0)

none

Acknowledged (0)

none

Disagreed (0)

none


Local verify: Unknown | Threads resolved: 1/1

@sonarqubecloud

Copy link
Copy Markdown

@cryptoxdog
cryptoxdog merged commit ca08116 into main Sep 19, 2026
20 checks passed
@cryptoxdog
cryptoxdog deleted the agent/cursor/drop-settings-local branch September 19, 2026 15:18
cryptoxdog added a commit that referenced this pull request Sep 19, 2026
* fix(pipeline): gate redesign integrity before success and handoff

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

* 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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Hmr3SKRhYQ56omTzNFZ4L

* test(pipeline): lock the redesign gate ahead of the success receipt

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

* chore(golden): correct the hard-coded stage tail in the fixture builder

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

* fix(repo): stop site:validate dirtying a tracked report

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

* chore(gitignore): ignore non-npm lockfiles

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)

* fix(golden): add the missing mandatory redesign stage to the fixture

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
cryptoxdog added a commit that referenced this pull request Sep 19, 2026
…omic (#209)

* fix(pipeline): gate redesign integrity before success and handoff

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

* 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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Hmr3SKRhYQ56omTzNFZ4L

* test(pipeline): lock the redesign gate ahead of the success receipt

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

* chore(golden): correct the hard-coded stage tail in the fixture builder

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

* fix(repo): stop site:validate dirtying a tracked report

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

* chore(gitignore): ignore non-npm lockfiles

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)

* fix(normalize-spec): cover both committed pairs and make overrides atomic

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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Hmr3SKRhYQ56omTzNFZ4L

* 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 <quantum source>` 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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Hmr3SKRhYQ56omTzNFZ4L

---------

Co-authored-by: Claude <noreply@anthropic.com>
cryptoxdog added a commit that referenced this pull request Sep 19, 2026
…re rule (#210)

* fix(pipeline): gate redesign integrity before success and handoff

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

* 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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Hmr3SKRhYQ56omTzNFZ4L

* test(pipeline): lock the redesign gate ahead of the success receipt

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

* chore(golden): correct the hard-coded stage tail in the fixture builder

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

* fix(repo): stop site:validate dirtying a tracked report

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

* chore(gitignore): ignore non-npm lockfiles

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)

* fix(gitignore): anchor evidence/ so it stops matching real source

`evidence/` was unanchored, so it matched a directory of that name at any
depth. `git ls-files -ci --exclude-standard` therefore reported 255 tracked
files as ignored, and 148 of those were not debris at all:

  - src/pipeline/evidence/*.ts — 21 files, the pipeline's evidence layer
    (EvidenceStore, ReleaseReceipt, RedesignExecutionIntegrityReceipt, ...)
  - docs/reports_and_test_results/**/evidence/** — 127 files, dated report
    bundles someone committed deliberately

Nothing was broken while they stayed tracked, because .gitignore does not
apply to files already in the index. The hazard is the cleanup: a
mechanically derived `git rm --cached $(git ls-files -ci)` would have
untracked the pipeline's evidence layer, and local validation would still
have passed because the files remain on disk. Only a fresh clone would have
failed.

Anchoring to /evidence/ confines the rule to the repository root, which is
where the Safe Haven per-run evidence it was written for actually lives. The
count drops 255 -> 107, and every remaining entry is genuine generated
output.

This lands as its own commit, before any untracking, so the delete list in
the next commit can be derived mechanically from a rule that is already
correct.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Hmr3SKRhYQ56omTzNFZ4L

* chore(repo): untrack generated build, evidence and per-run artifacts

105 files whose directories .gitignore already names, but which were added
to the index before or despite those rules. Ignore rules do not apply to
tracked files, so they kept accumulating:

  - build/**  99 files across 61 golden run directories (~9.2 MB): 62
    pipeline.log files and 20 PNG captures, several over 1 MB. .gitignore:12
    says `build/` and README.md:193 says the same in prose.
  - evidence/**  4 files. .gitignore:45 names the directory as per-run Safe
    Haven output.
  - astro_template/validation/*.jsonl  2 files, named at .gitignore:23.
    Write-only: nothing reads them.

The list was derived mechanically from `git ls-files -ci --exclude-standard`
rather than hand-written, which is what makes a 105-file delete reviewable —
it is exactly the set git itself reports as tracked-but-ignored. That is only
safe because the preceding commit anchored `evidence/`; run against the old
rule the same command would have removed src/pipeline/evidence/*.ts.

Two entries are excluded deliberately, both tracked on purpose:
  - .l9/ci.json, re-included by `!.l9/ci.json` as the consumer-owned CI class
    declaration org-ci.yml reads
  - tests/golden/safehaven/fixtures/positive-receipt.json, which
    tests/golden/safehaven/RUNBOOK.md documents as a built fixture

Index-only: every file stays on disk and regenerates from its own tooling.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Hmr3SKRhYQ56omTzNFZ4L

* chore(repo): remove orphaned root run-artifacts and a duplicated doc

Four files, none referenced by any script, test, workflow or document.

Root run-artifacts, captured from one local run and committed:

  - validation_node_check.txt — 0 bytes, the only empty tracked file in the
    repository
  - validation_command_results.txt — 30 bytes, entirely
    `verify_launch_env_exit_code=1`
  - launch_env_fail_closed_output.json — a dated per-run report
    (timestamp_utc 2026-06-13). .gitignore:21 already ignores
    validation/launch_env_report.json, the same class of artifact from the
    same tool; this one just happened to be written at the repository root
    instead.

docs/recursive_optimization_report.md — a duplicate. docs/archive/ holds a
copy with different content that was archived deliberately in #125 and is
listed in docs/archive/README.md against ADR-0017. This root copy carries no
archive banner, is referenced by nothing, arrived incidentally in an
unrelated llm-router commit, and opens by naming a path that does not exist
in this repository:

    ## Artifact Group: /home/ubuntu/l9-website-bot (full repo)

The archived copy is the one the index points at and is untouched.

ROOT_FILE_VALIDATION.md is deliberately NOT updated. Its "Files in final
pack: 19" counts a curated documentation pack rather than tracked root files
(the real root count was already 35 before this commit), and none of the
files removed here are enumerated in it. It is a dated point-in-time record;
editing its numbers to match today would falsify it rather than correct it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Hmr3SKRhYQ56omTzNFZ4L

* fix(golden): add the missing mandatory redesign stage to the fixture

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

* fix(docs): retain the golden report's cited proof artifacts

Audit finding F2 of website-bot-all-open-prs-2026-09-17, against this PR.

docs/reports_and_test_results/08-18-2026/GOLDEN_E2E_FINAL_REPORT.md is a
retained report that cites two artifacts as its evidence — and calls them
"never hand-edited" and "never edited by hand", which is the whole point of
citing them:

  build/golden/golden-20260825-r61/pipeline.log
  build/golden/golden-20260825-r61/golden-oracle-result.json

This PR untracked both as generated output under build/, so the report's
evidence trail resolved to nothing. Before untracking I checked that no
script, test or workflow READS those files, and none does. I never checked
whether a retained document CITES them, which is exactly what a golden-run
proof report does. That was the gap.

Both are relocated verbatim from origin/main into evidence/ beside the report
that depends on them, and the three citations are repointed. Byte-identity is
the property that matters here, so it was verified rather than assumed: the
recovered files hash to 3b52732 and eeba265, the same blobs origin/main
carries. A regenerated substitute would have satisfied the link and destroyed
the claim.

docs/reports_and_test_results/**/evidence/ is the existing home for this
(08-15-2026 already uses it) and is tracked again since this PR anchored the
`evidence/` rule, so no new evidence location is introduced.

.gitignore needed one addition: `*.log` at line 11 matches at any depth, so
the relocated pipeline.log was silently not staged. `git add -f` would have
tracked it while leaving it ignored — precisely the tracked-but-ignored state
this PR exists to eliminate, and how 255 files accumulated in the first place.
The negation instead follows the repo's own precedent for retained logs
(!reports/test-runs/**/*.log) and is scoped to report evidence: build/,
root evidence/ and astro_template/ stay ignored, and `git ls-files -ci`
still returns only the two deliberate re-inclusions.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Hmr3SKRhYQ56omTzNFZ4L

---------

Co-authored-by: Claude <noreply@anthropic.com>
cryptoxdog added a commit that referenced this pull request Sep 19, 2026
* fix(pipeline): gate redesign integrity before success and handoff

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

* 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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Hmr3SKRhYQ56omTzNFZ4L

* test(pipeline): lock the redesign gate ahead of the success receipt

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

* chore(golden): correct the hard-coded stage tail in the fixture builder

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

* fix(repo): stop site:validate dirtying a tracked report

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

* chore(gitignore): ignore non-npm lockfiles

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)

* fix(gitignore): anchor evidence/ so it stops matching real source

`evidence/` was unanchored, so it matched a directory of that name at any
depth. `git ls-files -ci --exclude-standard` therefore reported 255 tracked
files as ignored, and 148 of those were not debris at all:

  - src/pipeline/evidence/*.ts — 21 files, the pipeline's evidence layer
    (EvidenceStore, ReleaseReceipt, RedesignExecutionIntegrityReceipt, ...)
  - docs/reports_and_test_results/**/evidence/** — 127 files, dated report
    bundles someone committed deliberately

Nothing was broken while they stayed tracked, because .gitignore does not
apply to files already in the index. The hazard is the cleanup: a
mechanically derived `git rm --cached $(git ls-files -ci)` would have
untracked the pipeline's evidence layer, and local validation would still
have passed because the files remain on disk. Only a fresh clone would have
failed.

Anchoring to /evidence/ confines the rule to the repository root, which is
where the Safe Haven per-run evidence it was written for actually lives. The
count drops 255 -> 107, and every remaining entry is genuine generated
output.

This lands as its own commit, before any untracking, so the delete list in
the next commit can be derived mechanically from a rule that is already
correct.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Hmr3SKRhYQ56omTzNFZ4L

* chore(repo): untrack generated build, evidence and per-run artifacts

105 files whose directories .gitignore already names, but which were added
to the index before or despite those rules. Ignore rules do not apply to
tracked files, so they kept accumulating:

  - build/**  99 files across 61 golden run directories (~9.2 MB): 62
    pipeline.log files and 20 PNG captures, several over 1 MB. .gitignore:12
    says `build/` and README.md:193 says the same in prose.
  - evidence/**  4 files. .gitignore:45 names the directory as per-run Safe
    Haven output.
  - astro_template/validation/*.jsonl  2 files, named at .gitignore:23.
    Write-only: nothing reads them.

The list was derived mechanically from `git ls-files -ci --exclude-standard`
rather than hand-written, which is what makes a 105-file delete reviewable —
it is exactly the set git itself reports as tracked-but-ignored. That is only
safe because the preceding commit anchored `evidence/`; run against the old
rule the same command would have removed src/pipeline/evidence/*.ts.

Two entries are excluded deliberately, both tracked on purpose:
  - .l9/ci.json, re-included by `!.l9/ci.json` as the consumer-owned CI class
    declaration org-ci.yml reads
  - tests/golden/safehaven/fixtures/positive-receipt.json, which
    tests/golden/safehaven/RUNBOOK.md documents as a built fixture

Index-only: every file stays on disk and regenerates from its own tooling.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Hmr3SKRhYQ56omTzNFZ4L

* chore(repo): remove orphaned root run-artifacts and a duplicated doc

Four files, none referenced by any script, test, workflow or document.

Root run-artifacts, captured from one local run and committed:

  - validation_node_check.txt — 0 bytes, the only empty tracked file in the
    repository
  - validation_command_results.txt — 30 bytes, entirely
    `verify_launch_env_exit_code=1`
  - launch_env_fail_closed_output.json — a dated per-run report
    (timestamp_utc 2026-06-13). .gitignore:21 already ignores
    validation/launch_env_report.json, the same class of artifact from the
    same tool; this one just happened to be written at the repository root
    instead.

docs/recursive_optimization_report.md — a duplicate. docs/archive/ holds a
copy with different content that was archived deliberately in #125 and is
listed in docs/archive/README.md against ADR-0017. This root copy carries no
archive banner, is referenced by nothing, arrived incidentally in an
unrelated llm-router commit, and opens by naming a path that does not exist
in this repository:

    ## Artifact Group: /home/ubuntu/l9-website-bot (full repo)

The archived copy is the one the index points at and is untouched.

ROOT_FILE_VALIDATION.md is deliberately NOT updated. Its "Files in final
pack: 19" counts a curated documentation pack rather than tracked root files
(the real root count was already 35 before this commit), and none of the
files removed here are enumerated in it. It is a dated point-in-time record;
editing its numbers to match today would falsify it rather than correct it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Hmr3SKRhYQ56omTzNFZ4L

* docs: point every numbered-ADR reference at docs/adr

Audit finding F4. The numbered ADR series lives in docs/adr/, and
ARCHITECTURE.md:7 says so — "That directory is the only canonical ADR
location". Eighteen references across ten files said otherwise, and fourteen
of them were not merely misleading but dead: every
docs/architecture/ADR-XXXX-*.md target was verified absent. docs/architecture/
holds four design notes and no numbered ADR at all.

Dead links repointed (docs/architecture/ADR-* -> docs/adr/ADR-*): the five
archived-doc banner lines under docs/archive/, the five-row extraction table in
docs/archive/README.md, TODO.md:49 and :51, and MANIFEST.md:30 — the last
being the worst of them, since MANIFEST.md presents that block as the overlay's
file inventory and so asserted the existence of a file the repository does not
contain. All nine distinct targets now resolve; zero dead.

Prose pointers corrected:

  - ARCHITECTURE.md:67 contradicted its own line 7, sixty lines apart
  - AGENTS.md:5 — the highest blast radius of the set: the Mission line calls
    itself "binding guidance for AI coding agents", so every agent entering
    this repository was handed the wrong ADR home in its first instruction,
    while AGENTS.md:74 cited docs/adr correctly
  - README.md:198 named docs/architecture as the ADR home and omitted
    docs/adr from the repository layout entirely, so a README reader could
    not learn the canonical directory exists
  - docs/archive/README.md:4 said "the numbered ADRs under docs/architecture/"
  - MANIFEST.md:12
  - docs/adr/README.md:4 forbade adding ADRs under `adr/`, which reads as
    forbidding the very directory it declares canonical; the intended referent
    is SEO-Bot's adr/ series, now named explicitly

Also indexes ADR-0019-client-design-reference-...md, an accepted 8.7 KB
decision record that was missing from docs/adr/README.md's table and therefore
undiscoverable from the canonical index. It collides on number with
ADR-0019-retire-five-push-campaign-pack.md; the collision is flagged in place
rather than resolved, because renumbering is an ADR decision and the losing
document's inbound references move with it.

Pointers only. No ADR is moved, renumbered or created, and no third authority
location is introduced.

Nothing in the verify:all chain validates documentation links, which is how
eighteen of them drifted undetected. Worth adding.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Hmr3SKRhYQ56omTzNFZ4L

* fix(golden): add the missing mandatory redesign stage to the fixture

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

* docs: correct the ADR-0019 index date and the docs/adr layout wording

Both from Copilot's review on #211, both correct.

The index row for ADR-0019-client-design-reference-... carried "—" for its
date. The ADR declares `## Date 2026-09-03`. I indexed that file without
reading its date field, and an index that invents an unknown where the source
states a fact is worse than the omission it replaced.

README.md:198 read "numbered Architecture Decision Records — the canonical
decision record", which describes a directory as though it were a single
record. It is the canonical ADR directory.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Hmr3SKRhYQ56omTzNFZ4L

* fix(docs): retain the golden report's cited proof artifacts

Audit finding F2 of website-bot-all-open-prs-2026-09-17, against this PR.

docs/reports_and_test_results/08-18-2026/GOLDEN_E2E_FINAL_REPORT.md is a
retained report that cites two artifacts as its evidence — and calls them
"never hand-edited" and "never edited by hand", which is the whole point of
citing them:

  build/golden/golden-20260825-r61/pipeline.log
  build/golden/golden-20260825-r61/golden-oracle-result.json

This PR untracked both as generated output under build/, so the report's
evidence trail resolved to nothing. Before untracking I checked that no
script, test or workflow READS those files, and none does. I never checked
whether a retained document CITES them, which is exactly what a golden-run
proof report does. That was the gap.

Both are relocated verbatim from origin/main into evidence/ beside the report
that depends on them, and the three citations are repointed. Byte-identity is
the property that matters here, so it was verified rather than assumed: the
recovered files hash to 3b52732 and eeba265, the same blobs origin/main
carries. A regenerated substitute would have satisfied the link and destroyed
the claim.

docs/reports_and_test_results/**/evidence/ is the existing home for this
(08-15-2026 already uses it) and is tracked again since this PR anchored the
`evidence/` rule, so no new evidence location is introduced.

.gitignore needed one addition: `*.log` at line 11 matches at any depth, so
the relocated pipeline.log was silently not staged. `git add -f` would have
tracked it while leaving it ignored — precisely the tracked-but-ignored state
this PR exists to eliminate, and how 255 files accumulated in the first place.
The negation instead follows the repo's own precedent for retained logs
(!reports/test-runs/**/*.log) and is scoped to report evidence: build/,
root evidence/ and astro_template/ stay ignored, and `git ls-files -ci`
still returns only the two deliberate re-inclusions.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Hmr3SKRhYQ56omTzNFZ4L

---------

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants