Repository navigation
fix(scripts): lint-coverage's ratchet banner stops reciting which other gates carry the closing-keyword shape (objectui#9906) - #9937
Conversation
…er gates carry the closing-keyword shape The rule-2 DEBT ratchet banner in `scripts/check-lint-coverage.mjs` said the closing-keyword spelling "still stands in scripts/check-type-check-coverage.mjs and scripts/check-action-forward-parity.mjs". Measured with the `CLOSING_TRIGGER` the closing-keyword pins share: 0 hits in the first, 1 in the second and that one a noun in a source comment. Both halves false. The banner keeps the trade-off it exists to explain — keep the instruction, lose the keyword, spell the anchor `objectui#` rather than a bare `#` — and keeps the scope record of the repair that wrote it, now as history. What it no longer does is answer, from here, which other gates carry the shape today: that is a reading of a population that moves without this file, taken by someone who is not looking at it, and re-derived never. The gate's own pin is extended to cover the sentence rather than only the keyword: the rule-2 region may cite no gate but this one and its pin, in any of the three spellings a banner reaches for (source file, pin file, package script), with the directory prefix optional. A control restores the removed sentence and asserts the scan returns exactly the three gates it named, and a slice guard fails if the scanned region stops reaching the banner. No tree is scanned and none may be. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015h79niBMyoB1xcaQje3uiz
…e pin that owns it The scan reads the rule-2 ratchet region only, which is the boundary the `CLOSING_TRIGGER` source leg beside it already had. The gate's leading docblock names its sibling in its opening sentence; that is a standing architectural relation rather than a reading of the sibling's contents, so it stays — but nothing covers a rotting claim written there, and a bounded guarantee is worth only what it says about its own edges. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015h79niBMyoB1xcaQje3uiz
…lace it with the mechanism The repair's replacement prose asserted that the removed sentence "was false in one half the day it was written". Re-measured with the shared CLOSING_TRIGGER over each named file as of each merge commit, reading the blobs rather than the pull-request titles: ff29450 (the commit that wrote the sentence) check-action-forward-parity.mjs 2 hits, both emitted messages check-type-check-coverage.mjs 3 hits, two emitters plus one noun 2414e37 check-action-forward-parity.mjs -> 0 first half goes false fb7dfed check-type-check-coverage.mjs -> 1 second half goes false Both halves were TRUE the day the sentence was written. The clause was a dated reading with its date left implicit, asserted in the one place nothing re-reads it, and it was wrong on its first day — which is the argument this change is built on, arriving by being committed rather than by being made. The banner now states the mechanism and stops there: such a sentence does not stay true, it only waits, and what stood here is on the card. The corrected reading moves to the pin's header, where a person deciding how to word the banner will be looking, with the miss recorded beside it. The changeset carried the same false clause and is corrected with it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015h79niBMyoB1xcaQje3uiz
Review round 1 — a false clause in this pull request's OWN new prose, corrected in
|
| commit | merged | check-action-forward-parity.mjs |
check-type-check-coverage.mjs |
the sentence |
|---|---|---|---|---|
ff29450a9 (objectui#9595) — wrote the sentence |
2026-09-16T15:29:06Z |
2 (both emitted messages) | 3 (two emitters + the objectstack#4074 noun) |
both halves TRUE |
2414e3751 (objectui#9755) |
2026-09-18T02:20:13Z |
0 | 3 | first half goes false |
fb7dfedbb (objectui#9898) |
2026-09-18T15:18:23Z |
0 | 1 (the noun only) | second half goes false |
So the sentence was true when written and went stale in two steps — the first about 35 hours later, the second the same day after that — each step a different pull request, and ⛔ neither of them the one that wrote it. The card's own reading ("has been false since objectui#9755 landed") matches this; the draft clause contradicted the card it was fixing.
⭐ Note what this does to the argument this change rests on. The route was chosen because a dated reading rots and a timestamp does not repair the shape it stamps. The clause written in its place was a dated reading with its date left implicit, asserted in the one place nothing re-reads it, and it was wrong on its first day. The strongest evidence for the rule arrived by being committed rather than by being argued.
What changed in 2139d4fc2
The banner now states the mechanism and stops, carrying no reading of anything:
-// never. The one that stood here was false in one half the day it was written
-// (objectui#9906). Pinned by
+// never. Such a sentence does not stay true; it only waits. The one that stood
+// here, and how it went stale, are on objectui#9906. Pinned byThe corrected reading — which is still dated, and says so — moves to the pin's header, where someone deciding how to word the banner is actually looking, with the miss recorded beside it. The changeset body carried the same false clause and is corrected with it.
The pin, re-run against the new wording
Both legs from the committed state (HEAD blob 93e8f8883), mutation proved on disk, restore proved by the blob returning and git diff HEAD empty:
| leg | result |
|---|---|
| baseline (patched wording) | Tests 6 passed (6) |
| A' — the removed sentence put back, on the patched wording | Tests 1 failed | 5 passed — still red-first-capable |
| E — the false history clause itself, restored verbatim | Tests 6 passed (6) — green |
fa7d4f9c1, the previous commit's exact bytes, which is incidental proof the restoration was byte-identical.)
Re-verification at 2139d4fc2
Affected tests through the shared lock: Test Files 12 passed (12), Tests 347 passed (347). Gates, exit codes captured by redirect-then-capture: check-lint-coverage itself, check-changeset-presence, check:control-bytes, check:new-line-citations, check:changeset-claims, check:pending-changeset-literals, type-check:scripts — all exit 0. ESLint narrowed to the 2 changed source files: 0 errors, 0 warnings, count from --format json. Control-byte self-scan over the three changed files: no match.
⛔ Nothing widened: same scripts/ + .changeset/ surface, no force-push (a commit was added), the five CLOSING_TRIGGER copies and the leading docblock's sibling citation both still untouched.
Generated by Claude Code
…the corrected clause The previous commit fixed the banner, the changeset and this pin's header, and left the false clause standing inside the assertion's own failure message — the text a person reads only once the pin has already fired, when they are trying to work out what went wrong and are least able to check it. The header two hundred lines up documents that a first draft got this backwards; the message was that first draft. The message now says why naming another gate is wrong and stops there: the sentence that stood in the banner went stale without anyone touching this file. Measured before being written, rather than reasoned: neither of the two commits that falsified it touches scripts/check-lint-coverage.mjs, and the last commit to touch that file before this branch is the one that wrote the sentence. No date and no half-count, because the accurate reading already lives in this file's header and on the card. A correction can land in the narrative and miss the emitter, and a failure message is the most emitter-like text a test file has. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015h79niBMyoB1xcaQje3uiz
Review round 2 — the correction had missed the emitter. Fixed in
|
| spelling | hits |
|---|---|
false in one half |
0 |
one half was false |
0 |
was false when the sentence was written |
0 |
half was false |
0 |
went false · two days later · when the sibling was repaired |
0 |
the day it was written |
1 — scripts/check-lint-coverage.mjs:16, ✅ legitimate: pre-existing leading-docblock prose about the rule enumeration (objectui#3261 / #3279), a different sentence, out of this round, left alone |
already false |
1 — check-lint-coverage-closing-keyword.test.ts:145, ✅ legitimate: the header sentence that records the first draft getting it backwards |
still stands |
3 — all ✅ legitimate: the changeset, the pin header and the control leg's byte-for-byte restoration, each quoting the removed sentence as its subject |
Pin, from the committed state
HEAD blob for the gate 93e8f8883; mutation proved on disk, restore proved by the blob returning with git diff HEAD empty.
| leg | result |
|---|---|
| baseline | Tests 6 passed (6) |
| A′ — removed sentence put back, on the current wording | Tests 1 failed | 5 passed — still red-first-capable, failing through the corrected message above |
Re-verification at 72d3cf65e
Affected tests through the shared lock: Test Files 12 passed (12), Tests 347 passed (347). Gates all exit 0: check-lint-coverage itself, check-changeset-presence, check:control-bytes, check:new-line-citations, check:changeset-claims, check:pending-changeset-literals, type-check:scripts. ESLint narrowed to the 2 changed source files: 0 errors, 0 warnings. Control-byte self-scan: no match.
⛔ Assertion not widened, banner and changeset untouched, same scripts/ + .changeset/ surface, no force-push — a commit was added.
Generated by Claude Code
|
| shard | duration | result |
|---|---|---|
| 1/4 | 20m16s | cancelled |
| 3/4 | 18m06s | success |
| 2/4 | 17m56s | success |
| 4/4 | 13m28s | success |
The control — the base branch passes by seconds
Test (shard 1/4) on the six most recent main commits:
| commit | shard 1/4 | margin under the 20m ceiling |
|---|---|---|
02bc27b08 |
1189s | 11s |
00c4df509 |
1175s | 25s |
fc12bc8c6 |
1163s | 37s |
4d7d322aa |
1150s | 50s |
f5e2fcb4a |
896s | 304s |
0fe29cc23 |
794s | 406s |
⇒ the shard is not failing because of anything on this branch. It passes on main with a margin as thin as 11 seconds, and this head drew 1216s. ⛔ 「Flake」 is not the diagnosis either — this is a distribution whose upper tail has already crossed the ceiling.
Why the diff cannot be the cause
This pull request changes one comment paragraph in scripts/check-lint-coverage.mjs, one test file, and a changeset. It adds no test to any shard's population and touches nothing any shard imports. There is no mechanism by which it adds twenty minutes to a shard.
⛔ What this seat will NOT do
- ⛔ Not skip, disable, retry-wrap or quarantine the shard. A test made to stop failing is not a test.
- ⛔ Not port the fix, although one exists. The repair for this is PR objectui#9584 — one
Testaggregator, shards 4 → 8. ⛔ It is ⛔ not portable into this pull request: it edits.github/workflows/ci.yml, which is outside this card's declared surface, and objectui#9584 carries its own landing hazard (it must ⛔ not be enqueued until the fourTest (shard n/4)required contexts are removed by a maintainer). Widening a comment repair into the CI workflow would be the wrong act twice over. - ⛔ Not push an empty commit or close-and-reopen to get a fresh run.
What this seat will do
Spend the one re-run this case allows, and nothing further. If the re-run lands under the ceiling, the pull request is green on its own merits; if it crosses again, that is a second reading of the same systemic condition, ⛔ not a new fact about this branch.
🧑 For the maintainer: this is the standing objectui#9584 item, and it is no longer hypothetical. The shard sharding is now measurably past its budget — a green pull request took a red check for it, and main itself is passing by as little as 11 seconds. Every pull request in this repository is currently one slow run away from the same red.
domain:devx @ objectui execution PM seat · session_015h79niBMyoB1xcaQje3uiz · readings taken 2026-09-18T19:2xZ from the check-run API
Generated by Claude Code
Fixes #9906
The claim, and what it measured
scripts/check-lint-coverage.mjs's rule-2 DEBT ratchet banner said, in the present tense, that the closing-keyword shape "still stands inscripts/check-type-check-coverage.mjsandscripts/check-action-forward-parity.mjs". Re-measured on this branch's base with theCLOSING_TRIGGERthe closing-keyword pins share byte-for-byte:scripts/check-action-forward-parity.mjsscripts/check-type-check-coverage.mjsthe fix objectstack#4074 applied in `types`, a noun, not an emitterBoth halves false. The first went false when the sibling repair before this one landed; the second when objectui#9898 landed. Neither could be noticed, because nothing read the sentence.
The route taken, and why
Triage named two acceptable repairs: restate the claim as a point-in-time reading carrying its own timestamp, or restate it as something that cannot rot. This takes the second.
A timestamp does not fix the shape it stamps. Root
AGENTS.mdcommandment #9 is explicit that the remedy for a derived-once figure is to "point at the instrument that re-derives it — the script, the gate, the test — and never write down its answer"; a dated reading is still the answer written down, and #9's own warning is that a figure that is still correct is the dangerous case — a reader who spot-checks a dated sentence confirms it and is still wrong the day after. So the banner now answers neither question from here:objectui#rather than a bare#so a quoted message cannot match GitHub's closing grammar.scripts/check-spec-symbol-derivation.mjsand the pin block that holds it. Nothing is lost; it is one file away, beside the code that reads it.AGENTS.mdcommandment [WIP] Update documentation for project #11).The pin now covers the sentence, not only the keyword
scripts/__tests__/check-lint-coverage-closing-keyword.test.tsalready sliced the rule-2 region out of the gate source (staleEntryRatchet). The new assertion is an extension of that read, not a new gate: within that region, no gate may be cited but this one and its own pin — in any of the three spellings a banner reaches for (gate source file, pin file,package.jsonscript key), with the directory prefix optional, because droppingscripts/is the cheapest way to reword around a scan that demands it.⛔ No tree is scanned and none may be. The 2026-09-18 ruling closed the door on a new required gate for keeping cross-file claims honest, and this is deliberately the weaker rule: not "the claim is true" but "the banner does not make the claim". That is the only version of this that needs no population to re-derive.
Seen red first, on the pre-repair wording
The new assertion was written and run before the banner was touched, against the unrepaired file:
…and four ablations, from the committed state, each proved on disk
Every leg mutated the gate, proved the mutation landed (occurrence counts both directions plus
git hash-objectdiffering from theHEADblob), ran, then restored under atrapand proved the restore by the blob hash returning tofa7d4f9c1withgit diff HEADempty.Tests 6 passed (6)Tests 2 failed | 4 passedscripts/prefixTests 2 failed | 4 passedTests 2 failed | 4 passedTests 2 failed | 4 passed, and on D the failure is the slice guard's own message:the ratchet slice no longer reaches the banner it is scanningD is the leg that answers "could this pass for the wrong reason?":
staleEntryRatchetonly proves its two markers exist in order, so a region collapsed to a few bytes would satisfy an emptiness check by containing nothing. The live leg now asserts the banner's own last sentence is inside the slice before it scans it.What the scan deliberately does not reach — stated, not assumed
CLOSING_TRIGGERsource leg already had. The gate's leading docblock names its sibling in its opening sentence; that is a standing architectural relation ("the sibling of X, for the same reason"), not a reading of the sibling's contents, so it stays. A rotting claim written there would be out of reach and is covered by nothing. Both limits are now written in the pin's own header.The falsifiable zone — every item measured ABSENT
CLOSING_TRIGGERmisses — ⛔ none. The regex covers GitHub's full keyword set (close/closes/closed,fix/fixes/fixed,resolve/resolves/resolved). Re-run with JS string-concatenation seams,\nescapes and//comment continuations collapsed, so an anchor assembled across a line break is visible:check-action-forward-parity.mjs0,check-type-check-coverage.mjs1 (the same noun). No#is built at runtime in either file — no"#", no'#', noString.fromCharCode, no%23. The nearest miss is areason:string incheck-action-forward-parity.mjsreading…a path that is already closed " +/"(objectui#3842 …— a keyword and an anchor across a concatenation seam, separated by(.CLOSING_TRIGGERdoes not match it and neither does GitHub's grammar, so the ruler and the parser agree: not a miss.scripts/__tests__/lint-workflow.test.tsdoes read this gate, on two coordinates: its leading docblock (for hand-countedobject-ui/*rule figures) and its whole text (forobject-ui/*rule names). Neither reaches the rule-2 banner's prose, and this diff introduces no rule name and no count. The only slice of the banner anything parses is the pin's own.Turned back rather than widened into
CLOSING_TRIGGERdeclarations acrossscripts/__tests__(check-action-forward-parity-closing-keyword,check-lint-coverage-closing-keyword,check-type-check-coverage-closing-keyword,spec-symbol-ledger-anchor-liveness-9537,spec-symbol-ledger-plugin-detail-7265). Not this card, not this surface, and ⛔ not consolidated here.Verification
Affected tests, through the shared verification lock —
Test Files 12 passed (12),Tests 347 passed (347)at37480e15f: the pin itself plus every test that reads the edited gate or the changeset surface (lint-workflow,check-lint-rule-coverage, the two sibling closing-keyword pins,check-test-path-roots,check-changeset-presence,check-changeset-claims,check-changeset-no-major,check-control-bytes,check-new-cross-file-line-citations,check-pending-changeset-literals).Gates, each exit code captured by redirect-then-capture and never through a pipe — all exit 0:
node scripts/check-lint-coverage.mjs(the edited gate itself,46/46 packages linted),node scripts/check-changeset-presence.mjs,check:control-bytes,check:new-line-citations(0 new citation(s)),check:changeset-claims,check:pending-changeset-literals,check:test-path-roots,check:lint-rule-coverage,check:shell-escape-residue,check:entry-guard,check:unreferenced-sources,type-check:scripts. Plus a control-byte self-scan over the three changed files: no match.ESLint, narrowed and declared as such:
eslint --no-inline-configover the two changed source files reports 2 files linted, 0 errors, 0 warnings (count read from--format json, not estimated). The narrowing is a measurement rather than a gap becauseeslint.config.jsenables no type-aware linting — noprojectService, noparserOptions.project— so this diff cannot move any untouched file's verdict. The repo-widepnpm lint(turbo run lint) over the populationeslint.config.jsdefines remains CI's run.Changeset:
.changeset/9906-lint-coverage-banner-cross-file-claim.md, empty front matter — the explicit "no release" declaration.check-changeset-presenceagrees on its own terms: "3 file(s) changed, 0 of them published source of a package the release covers … no changeset is owed", and the declaration is filed anyway so the decision is recorded once rather than inferred.Not a governed surface:
check-governed-queue-guard --testover the three changed paths exits 0 withNOT GOVERNED.🤖 Generated with Claude Code
https://claude.ai/code/session_015h79niBMyoB1xcaQje3uiz
Generated by Claude Code