test(claude-lane-incident): narrow the table-vocabulary backstop's scope instead of widening its pattern - #368
Conversation
…ope instead of widening its pattern The guard was defective both ways. It ran against the whole flattened body while pinning one sentence of step 4, so editing the prologue's "PRs in the repositories below" to "all repositories below" turned the suite red for a reason that had nothing to do with the counting rule. And it was keyed on a quantifier immediately followed by `repositor(y|ies)`, so deictic-first order, `repo` shorthand, capitals, loose adjacency, and positional words other than above/below all sailed past: 5 of 20 defective counting rules caught. Widening the pattern again could only trade one direction for the other. Correct copy has to name the table in order to disclaim it, defective copy names it to endorse it, and no vocabulary check tells those apart — so the span it reads has to exclude the disclaimer rather than the pattern having to recognize it. Scoped to the sentence that states when a cycle counts, where naming the table is always wrong, the check can be broad and case-insensitive and key on no phrasing at all: 20 of 20 caught, and the prologue edit is inert. Mutation-proven in both directions: the deictic-first and `repo`-shorthand defects go red where the previous guard stayed green, and the prologue edit stays green where it previously went red. The extraction is asserted non-null and anchored before use, so copy that moves the sentence fails loudly rather than emptying the scope and passing on nothing — the vacuum this test exists to have escaped. Also drops two false comments, and says plainly that `coverageGap` is the pin and this regex is a backstop whose silence proves nothing. Closes #366
|
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 finished @kyle-sexton's task in 3m 24s —— View job Review complete
SummaryThis is a test-only change (production I traced the new extraction logic (
I was not able to execute Two minor, non-blocking findings posted inline:
No correctness, architecture, or coverage issues found otherwise. The narrowed-scope approach is sound: it's provably immune to the false positive that broke |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9e1591fefe
ℹ️ 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".
|
Claude has reviewed this PR 1 time. The lane skips further automatic reviews after 5; deleting this comment resets the count. |
…bulary that reads correctly Review found two real defects in the first cut, both in the direction the change was supposed to fix. The extractor treated every period as a sentence terminator, so a clarification hidden behind a dotted abbreviation fell outside the scope: `..., i.e. every row above.` truncated at `i.` and left the assertion inspecting a string that no longer contained the table reference. A period now ends the sentence only when a capital follows, and the extracted rule is anchored at both ends so an extraction that stops early fails instead of quietly narrowing the scope. The vocabulary was too broad to be safe. `listed` and `shown` read correctly of the durable index — "every repository listed in the tracked index" is not a defect — and `following`, `earlier`, and `later` read temporally. All three classes are gone; every defective phrasing they covered reaches the rendered report through a word that remains. Correct-copy phrasings are now fixtures too, because a backstop that fires on them teaches the next editor to loosen the check rather than fix the copy. Also asserts non-null on the second extraction, matching the discipline the rest of the file insists on, so a null there surfaces as that failure rather than an argument-type error. Mutation-proven: the abbreviation-hidden defect goes red, and the prologue edit that turns origin/main red stays green here.
…nd stop claiming the vocabulary trade was free Verification found a defective clarification that shipped green: "it polled every repository listed here — that is, it polled every repository the incident TRACKS…". It keeps the canonical clause intact, so every positive assertion and both scope anchors pass, and it points at the report through `listed`/`here` — two words the previous commit had dropped. The comment asserting that exclusion was lossless was therefore false, in the same way the comment this PR set out to fix was false. Pointing at the report does require naming a target, but `here`, `this report`, and the bare noun `list` were not in the set, so seven phrasings had no target to be caught by. They are in it now, and the comment states the membership RULE and admits the trade instead of claiming there is none. The escapes are positive fixtures and the index-naming phrasings that justify excluding `listed` and `shown` are negative fixtures, so neither direction rests on an assertion in prose. Also fixes the sentence terminator: it required a capital, so a sentence opening on a backticked identifier — idiomatic in this copy — failed to terminate the one before it and pulled the legitimate disclaimer into scope. Any non-lowercase character now terminates, which keeps the `i.e.` hole closed. Mutation-proven: the escape goes red here and stays green on origin/main.
…pattern-matching it Three vocabulary attempts, three refutations. Verification found six defective clarifications shipping green on the last one — "shown in the report", "named in the body", "each visible repository", "you can see", and more — and a NEW false positive from the `list` I had just added, because "every repository the tracked index lists" is correct copy. The set was also contradicting its own stated membership rule, which made the comment false for the third time in this PR. The diagnosis is that 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. That is not a tuning problem; it is the wrong instrument. So the sentence is pinned to its exact text. No vocabulary, no synonyms 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 instead of silently checking the wrong span. Byte comparison is already this repo's discipline for text that cannot afford to drift. The cost is deliberate and stated: any edit to that sentence fails here, including a good one, leaving the editor beside the `coverageGap` assertions having to confirm the new wording still states what the gate requires — the review this copy never got. Only the one sentence carrying the gate condition is pinned; the disclaimer prose after it stays free, and a contradiction planted there remains out of reach of text comparison, which is why `coverageGap` is the pin. The replacement test drives the extractor rather than trusting it: exact match on the real body, all six escapes rejected, the `i.e.` case kept inside the fence, and a renumbered step failing as a loud null. Mutation-proven: five of the six escapes are GREEN on origin/main and RED here, and the unrelated prologue edit that turns origin/main RED stays GREEN.
…ong mechanism Auditing these adversarially rather than charitably, since three false comments have already shipped in this PR. The terminator comment claimed a dotted abbreviation "cannot" cut the scope short. It can: an abbreviation followed by a capital truncates the span. Verified. What actually makes that safe is the equality check, which fails on a truncated span exactly as it fails on a reworded one, so the terminator only decides how legible the failure is. The comment now says that instead of taking the credit. The fixture comment claimed all three set sizes are asserted against the state. Two are; the rendered cap is compared as a literal, and raising the cap leaves this test green. Corrected to say which is which and why the literal is enough here. The opening comment now records why the vocabulary approach was abandoned — two rounds of independent review demonstrated escapes and false positives — and states what equality does and does not judge, rather than asserting a completeness no fixture can back.
…d-cap premise The comment implied its own literal comparison guards the tracked-exceeds-rendered premise. Measured: raising MAX_RENDERED_REPOSITORIES from 40 to 100 leaves this test green and fails "a fleet-wide incident renders a bounded body whose remainders tell the truth" instead. The literal is a proxy, so it now says so and names the sibling test that does the work. Not a false claim like the five before it — the comment was accurate about what the code does and loose about why it is sufficient. Recorded exactly rather than left loose, because loose justification is how the earlier ones started.
…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.
Summary
A post-merge review of #358 found its prose guard defective in both directions (#366). The
coverageGapcorrespondence 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
coverageGapgate-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
194c61aFalse 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 noiflag. Deictic-first order,reposhorthand, 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:
..., i.e. every row above.cut off ati.and left the assertion inspecting a string the defect was no longer in.following/earlier/laterread temporally.listadded 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;
countingRuleOfextracts 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, thei.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
coverageGapassertions, 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.
coverageGapand 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 onorigin/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.cjsbyte-identical toorigin/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:origin/mainreposhorthand: "every repo listed above"Five defective clarifications that ship green on
maintoday are caught here, and the false positive is gone.Closes #366
Related
coverageGapassertion is retained unchanged.