Skip to content

fix(claude-lane-incident): state the real coverage condition and re-pin the stale label comment - #350

Merged
kyle-sexton merged 4 commits into
mainfrom
fix/aggregator-copy-and-pin
Aug 5, 2026
Merged

fix(claude-lane-incident): state the real coverage condition and re-pin the stale label comment#350
kyle-sexton merged 4 commits into
mainfrom
fix/aggregator-copy-and-pin

Conversation

@kyle-sexton

@kyle-sexton kyle-sexton commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Summary

Two findings against the claude-lane incident watchdog, both left over from #331,
fixed in one PR because neither is worth a branch of its own.

#348 — the write job's label comment was stale in both halves

The pinned comment claimed claude-lane-incident "does not exist in this
repository's label taxonomy yet, and that taxonomy is managed by github-iac
(Labels.cs)". It exists: github-iac declares it in GovernedRepositories.cs
under this repository's ExtraLabels, and it is live on this repository with
that description. Labels.cs owns the shared core taxonomy and is not where a
per-repository extra label is declared, so the new comment cites
GovernedRepositories.cs instead. The consequence half is rewritten too: the
next apply keeps the label rather than pruning it.

#344 — the coverage copy named the rendered table, and was untested

renderIssueBody told the operator that a clean cycle counts only if it polled
"every repository listed above" (and "below" in the header bullet). The gate is
coverageGap, which reads the TRACKED index — MAX_TRACKED_REPOSITORIES = 60 —
and repositoriesSeen, while the table is capped separately at
MAX_RENDERED_REPOSITORIES = 40 and a character budget. So the copy was right
for a small incident and wrong in exactly the fleet-wide case it was written for.
Measured on this branch: a 120-repository incident tracks 60 and renders 40 rows
at short names, 23 at the longest repository name that can exist.

Naming the tracked index alone would have replaced one overpromise with another:
coverageGap also holds on unlisted > 0, so an incident wider than the tracked
cap never gets a counting clean cycle however much was polled. Both sentences now
state the whole condition — every tracked repository polled, AND the index
accounting for everything seen — and the header bullet defers to step 4 rather
than compressing the rule into a parenthetical that would misstate it.

Step 4 also attributed a permanent hold to a gone repository alone. coverageGap
gates on three, so the other two now appear in the durable artifact rather than
only in the run warning: an incident wider than the tracked cap, and an index
this watchdog can no longer read. The second is stated as the gate condition
(namesNothing) rather than as one of its triggers — a hand-edited state block
and one written by an older schema degrade identically, and a
STATE_SCHEMA_VERSION bump is how that reaches every open incident at once, not
the only way an operator meets it.

The copy was also untested: the issue's second finding is that reverting it left
the suite green. A rendered-body substring assertion over the fleet-wide state now
covers it, following the convention the suite already uses.

How the re-pin was done

The repo's pin discipline is byte-exact text comparison, not a digest.
claude-lane-incident-write-gate.cjs compares the workflow from the marker
# THE ONLY WRITE-SCOPED JOB. to end of file against
claude-lane-incident-write-job.pinned.yml, and the aggregator test additionally
asserts workflow.endsWith(pinned). Every fixture that carries the write job
carries the same bytes, so the comment could not be fixed in one file.

Rather than re-syncing the pinned tail over each fixture — which would have made
the deliberately-broken write-job fixtures conformant and turned their CORPUS
assertions green-on-nothing — a script substituted the same five comment lines in
place, once per file, failing loudly on any file where the block did not appear
exactly once. 49 files changed in one commit; workflow-not-a-mapping.yml is the
one fixture carrying no write job and was correctly skipped.
claude-lane-incident-mint-step.pinned.yml is a separate pinned region and is
untouched.

Verification

  • node --test .github/scripts/*.test.cjs — 505 pass, 0 fail (504 on origin/main
    plus the one assertion this PR adds).
  • Regression proof for claude-lane-incident: renderIssueBody coverage copy names the rendered table instead of the tracked index #344: reverting claude-lane-incident.cjs alone, with the
    new test retained, turns exactly that test red (43 pass, 1 fail). A green suite
    would not have been evidence — that is the finding.
  • npx @biomejs/biome@2.5.4 ci --config-path=fixtures/typescript/good/biome.json --error-on-warnings .github/scripts — clean, matching what CI's biome job runs.
  • The pin is the workflow's byte-exact tail, checked directly
    (workflow.slice(marker) === pinned) as well as by the test.
  • The github-iac declaration and the live label were both read at authoring time
    rather than taken from the issue text, and the "keeps rather than prunes" claim
    was traced through Labels.Apply, which unions _core with the repository's
    ExtraLabels for every spec with ManagedLabels (default true, which
    ci-workflows takes).

All 38 CI checks on the head commit are green.

Closes #344
Closes #348

Related

kyle-sexton and others added 3 commits August 4, 2026 11:15
…abel now exists

Both halves of the write job's `labels` comment were stale. `claude-lane-incident`
is declared in github-iac (`GovernedRepositories.cs`, this repository's
`ExtraLabels`) and exists live on this repository with that description, so the
next taxonomy apply keeps it rather than pruning it.

The comment sits inside the byte-pinned write job, so the workflow, its pin, and
every fixture carrying the block are re-pinned in this one commit: 49 files, one
identical five-line substitution each, applied by script so the pin stays the
workflow's byte-exact tail. Each fixture's deliberate single-property deviation
is untouched — the block is substituted in place rather than the pinned tail
being re-synced over it.

Closes #348

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…y, and test it

Both coverage sentences pointed the operator at the rendered table. The gate is
`coverageGap`, which reads the TRACKED index (`MAX_TRACKED_REPOSITORIES` = 60)
plus `repositoriesSeen`; the table is capped separately at
`MAX_RENDERED_REPOSITORIES` = 40 and a character budget. The copy was therefore
correct only for small incidents and wrong in the fleet-wide case it was written
for — a 120-repository incident tracks 60 and renders 40 rows at short names, 24
at the longest name that can exist.

Step 4 also attributed a permanent hold to a gone repository alone. `coverageGap`
documents three standing causes; the other two — an incident wider than the
tracked cap, and a state-schema bump — are now in the durable artifact rather
than only in the run warning.

Adds a rendered-body substring assertion over the fleet-wide state, following the
suite's existing convention. Reverting the copy alone turns that test red and
leaves the other 43 green, which is the regression the issue asked for.

Closes #344

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…st the tracked index

Naming the tracked index still overpromised. `coverageGap` holds on `unlisted > 0`
as well — `repositoriesSeen` above what the index names — so an incident wider
than `MAX_TRACKED_REPOSITORIES` never gets a counting clean cycle however much
was polled. Both sentences now carry the whole condition: every tracked
repository polled, AND the index accounting for everything seen.

The header bullet defers to step 4 rather than restating a partial rule, since
one parenthetical cannot hold the condition without misstating it.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
@cursor

cursor Bot commented Aug 4, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@claude

claude Bot commented Aug 4, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 1m 13s —— View job


Security review

Scope: all 51 files changed in this PR (diffed against origin/main).

Findings: none.

This PR is a pure documentation/copy change with one accompanying test — there is no security-relevant surface here:

  • 48 fixture files + claude-lane-incident-write-job.pinned.yml + claude-lane-incident-aggregator.yml: identical 5-line swap of a # comment block above the labels: key (example). Spot-checked several fixtures across different scenarios (secret-tojson-dump.yml, write-gate-inverted.yml, yaml-anchor.yml) and confirmed the diffs are byte-identical to each other and touch only comment text — no permissions:, labels: value, trigger, secret handling, or step logic changed in any fixture.
  • claude-lane-incident.cjs: only string-literal changes inside renderIssueBody (the auto-close coverage explanation text). No control-flow, gating logic (coverageGap itself is untouched), token/permission handling, or injection-relevant code changed.
  • claude-lane-incident.test.cjs: additive test only, asserting on rendered body substrings; no production code path affected.

No pull_request_target/workflow_run usage, no permissions: changes, no unpinned/loosened action refs, no new run: blocks interpolating the github context, and no secret/token handling anywhere in this diff. Everything zizmor would statically flag (trigger safety, pin hygiene, permission scope) is unchanged from origin/main.

@claude

claude Bot commented Aug 4, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 3m 46s —— View job


Code review

  • Gather context (PR body, diff, CLAUDE.md)
  • Review claude-lane-incident.cjs copy/logic changes
  • Review claude-lane-incident.test.cjs new test
  • Review the pinned-comment re-sync across claude-lane-incident-write-job.pinned.yml, the aggregator workflow, and the 48 fixtures
  • Post findings

Findings: none. This PR is copy-accuracy work plus one regression test, and I traced every claim it makes back to the code rather than trusting the description.

coverageGap claim checked against the implementation (claude-lane-incident.cjs#L388-L410): the new step-4 copy says a clean cycle counts only when it polled every repository the incident tracks and the tracked index accounts for every repository seen. That's exactly unobserved/unlisted, both of which independently keep coverageGap non-null. The old copy ("polled every repository listed above") pointed at the rendered table, which is capped by a different constant (MAX_RENDERED_REPOSITORIES = 40) than the gate (MAX_TRACKED_REPOSITORIES = 60) — so the fix is real, not cosmetic.

Third standing-hold cause checked: parseStateBlock returns null on any v !== STATE_SCHEMA_VERSION (L564), and coverageGap on a null previous returns namesNothing: true (L389-L392), which holds every open incident regardless of scope. The new epilogue text naming a schema bump as a standing cause is accurate.

New test (claude-lane-incident.test.cjs): reuses the existing fleetWideTally helper rather than inventing a new fixture pattern, and asserts both the presence of the new copy and the absence of the old (every repository (?:listed above|below)), so a regression back to the stale wording fails loudly — which is what #344 needed (the PR's claim that reverting the .cjs change alone turns this test red, while everything else stays green, is plausible from reading the assertions, but I did not execute the suite myself — see below).

Label-comment re-pin: diffed claude-lane-incident-write-job.pinned.yml against the aggregator workflow and five spot-checked fixtures (base.yml, secret-tojson-dump.yml, write-gate-inverted.yml, write-job-script-edited.yml, yaml-anchor.yml) — all five carry the byte-identical 5-line swap. git diff --name-only confirms exactly 47 of the 48 files under fixtures/claude-lane-incident-write-gate/ changed, and the one untouched — workflow-not-a-mapping.yml — genuinely carries no claude-lane-incident/write-job text to fix, matching the PR's claim about why it was skipped. The pin mechanism the description relies on is real: claude-lane-incident-write-gate.cjs:65 defines the # THE ONLY WRITE-SCOPED JOB. marker, and claude-lane-incident-aggregator.test.cjs:481 does assert workflow.endsWith(pinned).

Not independently verified (author-claimed only): the npm test / node --test results (505 pass), the biome run, and the github-iac (GovernedRepositories.cs) declaration — this review lane's sandbox blocks executing repo scripts (node/npm invocations require approval that isn't available here), consistent with this repo's CLAUDE.md rule against running PR-authored code in a review job, and I have no access to the external github-iac repo to confirm the label declaration. Everything I could verify by reading — the gating logic, the new test's assertions against that logic, and the mechanical fixture sync — checks out.

No architecture, error-handling, or maintainability concerns. Security scope for this PR was already covered by the security lane above (no security-relevant surface — pure comment/copy changes).

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown

Claude has reviewed this PR 1 time. The lane skips further automatic reviews after 5; deleting this comment resets the count.

@kyle-sexton

Copy link
Copy Markdown
Contributor Author

Independent review: outstanding items before merge

A fresh-context reviewer cold-read this branch at 04a3d14; commit 484e765 (already pushed) resolves its main finding — the copy now states the whole coverageGap condition (tracked index polled AND unlisted === 0). Verdict on the mechanical half: ccaabc7 (#348) is correct and well-evidenced — 48-fixture exact-violation-array comparison against the old gate showed zero drift, and the pin remains the workflow's byte-exact tail.

Still outstanding (a cold agent can pick these up):

  1. Test pins wording, not copy↔gate correspondence. The new test greps the rendered body but never calls coverageGap, so the committed wording passed the committed test even when the wording was still wrong (demonstrated empirically by the reviewer). Fix: from the same 120-repo fleet-wide state, assert coverageGap({ previous: state, polledRepositories: <all 60 tracked names> }) is non-null with unlisted === 60, then assert the body states that condition. Also widen the negative guard — /every repository (?:listed above|below)/u catches only the two exact old phrasings, not e.g. "every repository in the table above".
  2. Stale measurement "24" in 04a3d14's commit message and this PR body. "renders 40 rows at short names, 24 at the longest name that can exist" is the pre-change number; the committed code renders 23 at the worst case (the epilogue growth cost one row; the +N more remainder absorbs it truthfully, 12 → 13). Amend the body before squash-merge — it becomes main's permanent commit message.
  3. LOW copy nits (fix or consciously decline here): namesNothing also fires on a hand-edited/unparseable state block, not only a STATE_SCHEMA_VERSION bump; and "closed by hand once you have confirmed recovery" has nothing to recover in the schema-bump case.

Cross-repo note: the contradiction between this repo's "a pruned label degrades a sanity query and nothing else" and github-iac GovernedRepositories.cs "an issue cannot be opened carrying a label that does not exist" is already filed as melodic-software/github-iac#263 — do not re-file. The reviewer's observation that peter-evans/create-issue-from-file@v6 creates via POST /issues, which auto-creates missing labels, belongs there.

@kyle-sexton

Copy link
Copy Markdown
Contributor Author

Addendum from a second independent reviewer (narrow copy-focused pass at HEAD 484e765)

Upgrades and additions to the outstanding-items list above:

  1. namesNothing is not a LOW nit — it is the claude-lane-incident: renderIssueBody coverage copy names the rendered table instead of the tracked index #344 defect class surviving in a narrower case. Step 4's two clauses map correctly to unobserved and unlisted > 0, but when the index names nothing (implicated.length === 0) both clauses read as vacuously satisfied — polling the empty set, an empty index accounting for zero seen — so the copy tells the operator cycles are counting exactly while coverageGap holds. Also: the permanent-hold list names the schema-bump route into namesNothing but not the parse-failure route (hand-edited or older-schema state block, degraded to null by parseStateBlock). Non-blocking (impact confined to hand-edited/schema-bumped state) but a finding against the PR's own stated goal.
  2. The new test's fleet-wide fixture is inert. All five assertions pass unchanged against a one-repository state; the asserted strings are static literals, so fleetWideTally(120, 40) exercises nothing and the test's own comment ("The three sets differ exactly when the copy matters") is false about the test it annotates. Reinforces item 1 of the previous comment: assert against coverageGap from that state, not prose.
  3. "24" → 23 confirmed at HEAD, self-inflicted: 484e765's added step-4 copy consumed the body budget row. 24 was accurate at 04a3d14; the PR body claims the branch and is stale.
  4. New observation: longest-name 120×10 now renders at 65433 of 65536 characters — 103 characters of headroom. Any further copy growth in this region should re-measure against the 65536 assertion.

Independently re-verified clean: 505 tests pass; 49 pinned files = 47 fixtures + workflow + pinned (five-line substitutions exactly); caps 60/40; reflow-safety of the assertions holds.

…not just a schema bump

`namesNothing` is the gate condition, and `coverageGap` reaches it whenever the
index names nothing this watchdog can read — a hand-edited state block and one
written by an older schema degrade identically. A `STATE_SCHEMA_VERSION` bump is
how that happens to every open incident at once, not the only way it happens, so
the copy named the cause by one of its triggers and left the operator of a
corrupted block with no matching entry.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
@claude

claude Bot commented Aug 4, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 58s —— View job


Security review

Scope: all 51 files changed in this PR, diffed against origin/main at HEAD 9681fee.

  • Gather context (diff against origin/main, CLAUDE.md security rules)
  • Review claude-lane-incident.cjs copy/logic changes
  • Review claude-lane-incident.test.cjs new test
  • Review pinned-comment re-sync (write-job pinned file, aggregator workflow, 48 fixtures)
  • Post findings

Findings: none.

This PR is a pure documentation/copy change plus one additive regression test — there is no security-relevant surface here.

  • claude-lane-incident.cjs: only string-literal edits inside renderIssueBody (the auto-close coverage explanation and the epilogue naming the three standing causes of a permanent hold). coverageGap, parseStateBlock, and every other gating/control-flow function are byte-identical to origin/main — confirmed by diff, not by trusting the PR description. No token/permission handling, no interpolation of untrusted input, no change to what gets written to the issue body's data fields, only to the prose around them.
  • claude-lane-incident.test.cjs: additive test asserting on rendered-body substrings; no production code path or execution surface affected.
  • The 48 fixture files + claude-lane-incident-write-job.pinned.yml + claude-lane-incident-aggregator.yml: identical 5-line #-comment swap above the labels: key, revising which github-iac file governs the claude-lane-incident label (GovernedRepositories.cs instead of Labels.cs) and that the label now survives an apply instead of being pruned. I mechanically verified this holds for the entire fixture set, not a spot check: git diff origin/main...HEAD across all 48 fixtures, filtered to added/removed lines that are not #-prefixed comment lines, produced zero output. Every changed line in every fixture is a comment; no permissions:, labels: value, trigger (pull_request/pull_request_target/workflow_run), secret handling, checkout ref, or step logic changed anywhere in this diff.

No pull_request_target/workflow_run usage, no permissions: changes, no unpinned/loosened action refs (SHA pins untouched), no new or edited run: blocks interpolating the github context, no secret/token/credential handling anywhere in this diff. Everything zizmor's advisory lane already covers statically — trigger safety, pin hygiene, permission scope, template injection — is unchanged from origin/main, so per this lane's scope it's out of bounds here too.

@kyle-sexton
kyle-sexton merged commit 89a38ab into main Aug 5, 2026
40 checks passed
@kyle-sexton
kyle-sexton deleted the fix/aggregator-copy-and-pin branch August 5, 2026 18:20
kyle-sexton added a commit that referenced this pull request Aug 6, 2026
… its own wording (#358)

## Summary

#350 closed #344 by fixing the copy and adding a test. The test greps
the rendered body, so it pins the copy against its own wording — which
is the #344 defect one level up. This makes the assertion check the copy
against the **gate** it describes.

Two failure modes survived the merge, both found by the pre-merge
reviewers and confirmed here at #350's merged head `9681fee`:

- **The fleet-wide fixture was inert.** All five assertions passed
unchanged against a one-repository state, so `fleetWideTally(120, 40)`
exercised nothing. The asserted strings were static literals,
insensitive to all three of the numbers the fixture exists to make
disagree.
- **The negative guard was pinned to two literal sentences.** `/every
repository (?:listed above|below)/u` caught only the phrasings #331
happened to ship. An equivalent rewording would have reintroduced #344
with a green suite.

## What changed

Only `.github/scripts/claude-lane-incident.test.cjs`. No production copy
or logic is touched.

**The gate is now the assertion.** From the same fleet-wide state the
test renders, it derives the tracked index and calls `coverageGap` over
every tracked repository — which is still non-null, with `unobserved:
[]` and `unlisted === 60`. That is the condition the copy claims:
complete coverage needs the polled scope **and** the index's own
accounting, so an incident wider than the tracked cap never gets a
counting clean cycle however much was polled. Copy and gate are held to
one predicate instead of two independent restatements of it.

**The fixture is no longer inert.** `repositoriesSeen === 120`,
`tracked.length === 60`, and `tracked.length > 40` are asserted against
the state rather than assumed, and `unlisted` is derived
(`repositoriesSeen - tracked.length`) rather than hardcoded.

**The guard is widened to the defect's shape** — a counting rule tied to
a position in the document rather than to the tracked index — and a
second test holds it to the paraphrases a future editor would plausibly
reach for. It is bounded to a single sentence so the copy's own
contrastive "not the table above", a separate sentence, stays legal.

**The false comment is corrected.** The old annotation claimed "The
three sets differ exactly when the copy matters", which was true of the
fixture and false of the assertions it annotated.

## Verification

- `node --test .github/scripts/*.test.cjs` — 513 pass, 0 fail on the
branch; 512 pass, 0 fail on `origin/main`, both measured directly rather
than carried over.
- `npx @biomejs/biome@2.5.4 ci
--config-path=fixtures/typescript/good/biome.json --error-on-warnings
.github/scripts` — clean, matching CI's biome job at the version its
action pins.
- `comment-hygiene` scan — 12 violations with and without this change:
an unchanged pre-existing baseline, zero delta from this PR.

**Mutation-tested, because a green suite is exactly what the old test
proved and it was not evidence:**

- Collapsing the fixture to `fleetWideTally(1, 1)` now **fails** (`1 !==
120`). Against the merged head's test, the identical collapse passed all
five assertions.
- Rewording the copy to "it polled every repository in the table above"
now **fails**. Checked directly against that mutated body, the old guard
returns `false` and the widened guard returns `true` — the exact
phrasing the reviewer named as the hole.

## Body budget

No copy changed, so the 65536 budget is untouched. Recording the
re-measure #350 owed but never wrote down, since the question will come
up again:

`fixedLength` (`claude-lane-incident.cjs:737-742`) deducts the actual
prologue and epilogue length from the table allowance, so copy growth
costs rendered rows rather than headroom — the bound is structural, not
a margin to be spent. Re-measured across 8,568 incident shapes at #350's
two heads: zero over-limit bodies, and the worst case moved 65486 →
65487 of 65536 across the step-4 copy growth that prompted the concern.

Two notes for whoever edits this copy next. The reviewer's "65433 at
120×10, 103 characters of headroom" did not reproduce in the shapes
swept here (120×10 measures 64364, or 64786 at nine-digit pull numbers);
hostile `statusCounts` crossed with that shape was not swept, so the
figure is not contradicted, just unconfirmed. Separately, the
body-budget test drives three shapes — 40×10, 120×40, 200×300 — and none
is near the true worst case, which sits at 65487 of 65536 (49 characters
of headroom) at 54×14 with nine-digit pull numbers. Tightening that test
is out of scope here and is tracked in #360, together with the two copy
gaps whose fixes would grow this region.

Closes #357

## Related

- #350 — merged the copy fix and the assertion this PR replaces.
- [Independent review: outstanding items before
merge](#350 (comment)),
item 1.
- [Addendum from a second independent
reviewer](#350 (comment)),
item 2.
- #344 — the original defect, whose class this test now actually
detects.

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
kyle-sexton added a commit that referenced this pull request Aug 6, 2026
…ope instead of widening its pattern (#368)

## Summary

A post-merge review of #358 found its prose guard defective in both
directions (#366). The `coverageGap` correspondence assertion #358 added
is sound and untouched — it remains the pin. This replaces the broken
backstop beside it.

**Decision: the vocabulary-membership regex backstop is removed, not
repaired.** Two rounds of independent verification demonstrated that its
enumeration cannot be closed — every synonym for the rendered report is
a fresh escape, and widening far enough to close them fires on copy that
correctly names the durable index. Patching the blacklist went 0 for 3.
What remains is what is load-bearing and proven: the `coverageGap`
gate-correspondence assertion, and an **exact-string pin** on the step-4
counting-rule copy, whose forced test edit is the human review seam. No
completeness is claimed anywhere.

Getting there took three refuted attempts, and those refutations are the
argument for the final shape.

## What was wrong at `194c61a`

**False positive.** The assertion ran against the whole flattened body
while the claim it pinned belongs to one sentence of step 4. Editing the
prologue's "PRs in the repositories below" to "all repositories below" —
unrelated to the counting rule — turned the suite red.

**False negatives.** Keyed on a quantifier immediately followed by the
singular-or-plural noun, then `above|below`, with no `i` flag.
Deictic-first order, `repo` shorthand, capitals, loose adjacency, and
positional words outside that pair all escaped.

**Inert fixtures.** All eleven breadth fixtures were written to the
regex's own model, so they could confirm the pattern was not narrower
than itself but never that the model was too narrow.

**Two false comments**, including one claiming `[^.]` bounded a sentence
when it bounds a period-free run.

## Why the fix is a pin and not a better pattern

Narrowing the scope to step 4's first sentence and broadening the
vocabulary was the first attempt. Review refuted it three times:

1. A dotted abbreviation truncated the scope, so `..., i.e. every row
above.` cut off at `i.` and left the assertion inspecting a string the
defect was no longer in.
2. The broadened vocabulary fired on correct copy — "every repository
listed in the tracked index" is not a defect, and
`following`/`earlier`/`later` read temporally.
3. Narrowing the vocabulary to fix that reopened the escapes: six
defective clarifications shipped green ("shown in the report", "named in
the body", "each visible repository", "you can see", …), and the `list`
added along the way false-fired on "every repository the tracked index
lists". The comment asserting the trade was lossless was false — the
third false comment in this PR.

The pattern is the wrong instrument. A blacklist here enumerates an
**open set**: every synonym for the rendered report is a fresh escape,
and widening far enough to close them starts firing on copy that
correctly names the durable index. Vocabulary cannot satisfy both
directions.

Exact equality has neither failure mode. No synonym to chase, no
completeness to overclaim, and no way to be vacuous — a clarification
appended to the counting rule changes the string and fails, and an
extraction that runs long or stops short fails too rather than silently
checking the wrong span. Byte-exact comparison is already this repo's
discipline for text that cannot afford to drift.

## What changed

Only `.github/scripts/claude-lane-incident.test.cjs`. No production copy
or logic, so the body budget is untouched.

The pinned sentence is a constant; `countingRuleOf` extracts step 4's
first sentence and the test asserts equality. The replacement test
drives the extractor rather than trusting it: exact match against the
real body, all six verified escapes rejected, the `i.e.` case kept
inside the fence, step 4 asserted to continue past the pin so the
disclaimer prose is demonstrably *not* frozen, and a renumbered step
failing as a loud null.

**The cost is deliberate and documented in the code.** Any edit to that
sentence fails here, including a good one — which leaves the editor
standing beside the `coverageGap` assertions, having to confirm the new
wording still states what the gate requires. That review is what this
copy never got. Only the one sentence carrying the gate condition is
pinned; the disclaimer sentences after it stay free.

**Residual, stated rather than papered over:** a contradiction planted
in a *later* sentence of step 4 is out of reach of text comparison,
because that is where naming the rendered table is correct. Catching it
needs meaning. `coverageGap` and the gate assertions are what hold the
copy to the gate; this fences the sentence that states the rule.

## Verification

- `node --test .github/scripts/*.test.cjs` — 514 pass, 0 fail on the
branch; 514 on `origin/main` (this replaces assertions rather than
adding tests).
- `npx @biomejs/biome@2.5.4 ci
--config-path=fixtures/typescript/good/biome.json --error-on-warnings
.github/scripts` — clean, at the version the action pins.
- `claude-lane-incident.cjs` byte-identical to `origin/main`.

**Mutation battery** — each defect applied to the real production copy,
then the suite run against both this branch's test file and
`origin/main`'s:

| mutation to the counting rule | `origin/main` | this PR |
|---|---|---|
| deictic-first: "the table above lists every repository a cycle polls"
| red | **red** |
| `repo` shorthand: "every repo listed above" | red | **red** |
| "every repository listed here — that is, …" | **green (escaped)** |
**red** |
| "every repository shown in the report — that is, …" | **green
(escaped)** | **red** |
| "every repository named in the body — that is, …" | **green
(escaped)** | **red** |
| "each visible repository — that is, …" | **green (escaped)** | **red**
|
| "every repository you can see — that is, …" | **green (escaped)** |
**red** |
| "the output shows every repository polled" | red | **red** |
| CONTROL — unrelated prologue edit | **red (false positive)** | green
(inert) |

Five defective clarifications that ship green on `main` today are caught
here, and the false positive is gone.

Closes #366

## Related

- #358 — introduced the guard this replaces; its `coverageGap` assertion
is retained unchanged.
- #357 — the issue #358 closed.
- #360 — the remaining #350 rework items (counting-rule vacuity,
recovery-instruction copy, untested body-budget worst case). No overlap:
those are product copy, this is the test.
kyle-sexton added a commit that referenced this pull request Aug 6, 2026
…e false recovery promise, and gate the body budget at the shape that binds (#369)

## Summary

Drains all three residuals from #360 — the last of the #350 rework list.
Two are production copy in `renderIssueBody`; one is the body-budget
test that never drove the shape that binds.

## 1. The counting rule was vacuously satisfiable

`coverageGap` gates on three things. The copy named two: every tracked
repository polled, and the index accounting for everything seen. When
the tracked index names nothing, both read as satisfied — polling the
empty set, an empty index accounting for zero seen — so the copy told
the operator cycles were counting exactly while the gate withheld every
one.

Reachable, not theoretical. A hand-edited `"repositories":{}` block
parses, and the gate returns `namesNothing: true`:

```
gate holds (coverageGap non-null): true | namesNothing: true
```

The rule now leads with the condition that was missing, and the sentence
after it says why that clause exists rather than leaving it looking
redundant beside the other two.

## 2. "once you have confirmed recovery" promised recovery that does not
exist

None of the three permanent-hold causes has anything to recover. A
repository that is gone (archived, or removed from the installation) is
not coming back; an index this watchdog cannot read is a corrupted
record rather than an outage; and this file's own `coverageGap`
docstring describes the third as an incident "wider than
`MAX_TRACKED_REPOSITORIES`, whose `unlisted` remainder never resolves".
#350's own rework widened the unreadable-index cause, so the promise
covered strictly more causes it could not keep.

It now says none of the three recovers on its own, and closes on the
operator having addressed the lane failures behind the incident — true
of all three, and consistent with the docstring.

**My first attempt at this got the count wrong**, claiming "two of them
leave nothing to recover" without saying which two. Review flagged the
ambiguity; checking it against the docstring showed there was no
exception to name. `f0fbead` drops the count instead of spelling out a
subset that does not exist. #360's body carries the same wrong count and
is corrected in a comment there rather than quietly edited.

## 3. The body-budget test never drove the shape that binds

The test drove 40×10, 120×40, and 200×300 at the longest legal name —
all roughly 700–1400 characters clear of the limit. A sweep finds the
real bound at ~49 characters of headroom.

**A fixed tight shape would not have fixed this**, and this PR is the
proof. The two shapes measured as tightest on `origin/main` — 40×20 at
65418 (118 spare) and 54×14 at 65487 (49 spare) — now sit at 63760 and
63672, or **1776 and 1864 characters of headroom**, because the budget
converts epilogue growth into dropped rows and the binding shape moves.
Hardcoding them would have looked like a budget gate while measuring
slack — the same inert-fixture failure this lane has been unwinding
since #357.

So the new test **sweeps** a 144-shape grid bracketing the tight region
(146 ms) and asserts the maximum fits. It also asserts at least one
shape lands in the row-dropping regime, because a grid where everything
sat comfortable would be proving nothing about the bound.

The existing three-shape test keeps its remainder-semantics role and
gains two assertions: that the body fits by **dropping rows** rather
than by happening to be short (fewer rows render than the 40-row cap
allows, so the character budget bit), and that rows plus remainder
account for every repository the incident holds.

### What the budget gate does and does not catch — measured, not assumed

| mutation | result |
|---|---|
| drop the `fixedLength` deduction | **RED** |
| budget by rows instead of characters | **RED** |
| drop `REMAINDER_LINE_RESERVE` | green (80 is slack; the remainder line
is ~35 chars) |
| add 12 lines of epilogue copy | green |

The last row is the important one: **copy growth alone cannot breach
this bound**, because rows drop to pay for it. That is the bound being
structural, not the test being weak, and the comments now say so instead
of crediting the test with protection it does not provide.

## The pin seam worked as designed

The pinned counting-rule string moves with the copy in the same commit.
That is what the pin is for — copy cannot change without someone
re-reading it against the `coverageGap` assertions beside it. Updated
deliberately, both sides together.

## Verification

- CI is the authority for the full suite and is green on this head.
- `node --test .github/scripts/claude-lane-incident.test.cjs` — **46
pass, 0 fail**, the file this PR changes.
- The full local suite measured 515 pass / 0 fail earlier in this
branch's life. It cannot be reproduced on this workstation right now: 8
tests in `claude-security-review-fail-closed.test.cjs` and 2 in the
retry-resolution suite fail locally, and they fail **identically at
`origin/main` content**, so the cause is this machine (subprocess
spawning degraded partway through the session), not this change.
Reported rather than papered over; CI on GitHub runners is unaffected
and green.
- Worst-case rendered body, measured on both sides rather than carried
over. `origin/main`: 65485 of 65536 at 54×9, 51 spare. This branch:
**65487 of 65536 at 48×17, 49 spare.** Zero over-limit across the swept
space on either side.
- The binding **shape** moved (54×9 → 48×17) while the binding **value**
did not (65485 → 65487). That is the row-drop mechanism absorbing ~260
characters of copy growth: the loose shapes each lost roughly that much
headroom — 120×40 went 1010 → 748 spare — while the tight bound stayed
pinned against the ceiling because rows drop to pay for the copy.
- Both copy fixes asserted against the rendered body at the reachable
empty-index state.

Closes #360

## Related

- #350 — the PR whose rework list these three items are the remainder
of.
- #357 / #358, #366 / #368 — the earlier items from the same list.
- [Outstanding-items
comment](#350 (comment))
and
[addendum](#350 (comment)),
where all three originate.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant