Skip to content

fix(scripts): lint-coverage's ratchet banner stops reciting which other gates carry the closing-keyword shape (objectui#9906) - #9937

Merged
os-try-charles merged 4 commits into
mainfrom
claude/issue-9906-lint-coverage-banner-claim
Sep 18, 2026
Merged

os-try-charles merged 4 commits into
mainfrom
claude/issue-9906-lint-coverage-banner-claim

Conversation

@os-try-charles

Copy link
Copy Markdown
Collaborator

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 in scripts/check-type-check-coverage.mjs and scripts/check-action-forward-parity.mjs". Re-measured on this branch's base with the CLOSING_TRIGGER the closing-keyword pins share byte-for-byte:

file the banner named hits
scripts/check-action-forward-parity.mjs 0
scripts/check-type-check-coverage.mjs 1 — the source comment the fix objectstack#4074 applied in `types`, a noun, not an emitter

Both 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.md commandment #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:

  • The trade-off it exists to explain is untouched — keep the instruction, lose the keyword, spell the anchor objectui# rather than a bare # so a quoted message cannot match GitHub's closing grammar.
  • The scope record survives as history, not as a reading of the tree: the siblings the same repair would reach were reported rather than widened into (objectui#9595).
  • The precedent moves to the pin, which already records it by the names of the things — the rule-2 stale-entry message in scripts/check-spec-symbol-derivation.mjs and the pin block that holds it. Nothing is lost; it is one file away, beside the code that reads it.
  • Everything cited is cited by name, never by line address (root AGENTS.md commandment [WIP] Update documentation for project #11).

The pin now covers the sentence, not only the keyword

scripts/__tests__/check-lint-coverage-closing-keyword.test.ts already 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.json script key), with the directory prefix optional, because dropping scripts/ 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 the banner names no OTHER gate, so it cannot recite a tree that moves
  expected [ …(3) ] to deeply equal []
  + [
  +   "scripts/check-spec-symbol-derivation.mjs",
  +   "scripts/check-type-check-coverage.mjs",
  +   "scripts/check-action-forward-parity.mjs",
  + ]

…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-object differing from the HEAD blob), ran, then restored under a trap and proved the restore by the blob hash returning to fa7d4f9c1 with git diff HEAD empty.

ablation result
baseline (committed) Tests 6 passed (6)
A — the removed sentence put back verbatim Tests 2 failed | 4 passed
B — reworded to a bare filename, no scripts/ prefix Tests 2 failed | 4 passed
C — reworded to the package script key, no filename at all Tests 2 failed | 4 passed
D — the scanned slice collapsed, claim pushed outside it Tests 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 scanning

D is the leg that answers "could this pass for the wrong reason?": staleEntryRatchet only 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

  • A gate named in prose with no filename and no script key ("the type-check coverage gate"). Closing that means a hand-maintained list of every gate's English name — a second copy of the gate roster, which is this card's defect wearing a pin's clothes.
  • Anything outside the rule-2 region — the same boundary the CLOSING_TRIGGER source 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

  • An emitter in either named file the shared CLOSING_TRIGGER misses — ⛔ 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, \n escapes and // comment continuations collapsed, so an anchor assembled across a line break is visible: check-action-forward-parity.mjs 0, check-type-check-coverage.mjs 1 (the same noun). No # is built at runtime in either file — no "#", no '#', no String.fromCharCode, no %23. The nearest miss is a reason: string in check-action-forward-parity.mjs reading …a path that is already closed " + / "(objectui#3842 … — a keyword and an anchor across a concatenation seam, separated by (. CLOSING_TRIGGER does not match it and neither does GitHub's grammar, so the ruler and the parser agree: not a miss.
  • A consumer that reads this banner's text programmatically, other than the pin — ⛔ none. scripts/__tests__/lint-workflow.test.ts does read this gate, on two coordinates: its leading docblock (for hand-counted object-ui/* rule figures) and its whole text (for object-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.
  • A reason the present-tense phrasing was deliberate — ⛔ none. No ADR, no review comment, no test asserts the sentence as written. Its provenance is the opposite: the pull request that wrote it (objectui#9595) records the sentence's content in its own "Out of scope, and deliberately not widened here" section as the result of a one-off sweep — "Lines printed, not counted" — i.e. a point-in-time measurement transcribed into prose. That is commandment 完善设计器的每一个细节 #9's rot shape by construction, not a design choice.
  • A way the extended pin goes green on a rotted banner — ⛔ none found at citation level; ablations A–D above are the evidence, and the two reaches the scan does not have are named rather than left implicit.

Turned back rather than widened into

  • Five byte-identical CLOSING_TRIGGER declarations across scripts/__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.
  • The sibling gates themselves are untouched. This branch reaches exactly one gate's banner and one pin.

Verification

Affected tests, through the shared verification lock — Test Files 12 passed (12), Tests 347 passed (347) at 37480e15f: 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-config over 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 because eslint.config.js enables no type-aware linting — no projectService, no parserOptions.project — so this diff cannot move any untouched file's verdict. The repo-wide pnpm lint (turbo run lint) over the population eslint.config.js defines remains CI's run.

Changeset: .changeset/9906-lint-coverage-banner-cross-file-claim.md, empty front matter — the explicit "no release" declaration. check-changeset-presence agrees 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 --test over the three changed paths exits 0 with NOT GOVERNED.

🤖 Generated with Claude Code

https://claude.ai/code/session_015h79niBMyoB1xcaQje3uiz


Generated by Claude Code

…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
@github-actions github-actions Bot added the tests label Sep 18, 2026
…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

Copy link
Copy Markdown
Collaborator Author

Review round 1 — a false clause in this pull request's OWN new prose, corrected in 2139d4fc2

The first draft of the replacement banner said:

The one that stood here was false in one half the day it was written (objectui#9906).

That was false, and it was a new cross-file historical claim of exactly the class this change exists to remove. Re-measured with the shared CLOSING_TRIGGER over each named file as of each merge commit — reading the blobs with git show REV:PATH, not inferring from pull-request titles:

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 by

The 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

⚠️ Leg E is reported because it is the honest answer, not a gap being hidden: the clause names no gate, and this pin keys on other gates being named. That is the boundary already declared in the pin's own header, and it is ⛔ deliberately not widened — a pin that must recognise every false historical statement in English prose is this card's defect wearing a pin's clothes. (Leg E's mutated blob hashes to 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

Copy link
Copy Markdown
Collaborator Author

Review round 2 — the correction had missed the emitter. Fixed in 72d3cf65e.

The previous round 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 above it documents that a first draft got this backwards; the message was that first draft.

⭐ Worth naming as a shape, because it is this round's own subject one level up: 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.

-        + 'never — objectui#9906, where the sentence was false in one half the day it was '
-        + 'written. Say it at that gate, or in this pin, where the words sit beside the thing '
-        + 'they describe. ⛔ Not by adding a tree scan here: the ruling of 2026-09-18 closed '
-        + 'that door, and this assertion is deliberately the weaker one.',
+        + 'never — the sentence that stood there went stale without anyone touching this '
+        + 'file, which is objectui#9906. Say it at that gate, or in this pin, where the words '
+        + 'sit beside the thing they describe. ⛔ Not by adding a tree scan here: the ruling '
+        + 'of 2026-09-18 closed that door, and this assertion is deliberately the weaker one.',

No date, no half-count — the accurate reading already lives in this file's header and on the card. The one claim the new message does make was measured before it was written rather than reasoned: neither commit that falsified the sentence touches scripts/check-lint-coverage.mjs (2414e3751, fb7dfedbb — 0 hits each in git show --name-only), and the last commit to touch that file before this branch is ff29450a9, the one that wrote the sentence. So "went stale without anyone touching this file" is exact.

The message as it now renders when the pin fires

AssertionError: the rule-2 banner names another gate's source file again. Whatever it says
about that file is a reading taken once, by a person who is not looking at it, and
re-derived never — the sentence that stood there went stale without anyone touching this
file, which is objectui#9906. Say it at that gate, or in this pin, where the words sit
beside the thing they describe. ⛔ Not by adding a tree scan here: the ruling of
2026-09-18 closed that door, and this assertion is deliberately the weaker one.

Whole-diff grep for the clause, every spelling it has worn

Run over all three changed files, not the one site the review named — the lesson of the clause having been found in three places by two readers across two rounds. Hit list after the fix, including the legitimate ones:

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

Copy link
Copy Markdown
Collaborator Author

Test (shard 1/4) is red on this head, and it is ⛔ not this pull request's failure

Stating this rather than re-running silently, and stating the ⛔ limits of what this seat will do about it.

The reading

Test (shard 1/4) on 72d3cf65e: cancelled, started_at 18:58:04Z → completed_at 19:18:20Z = 20m16s, against the workflow's timeout-minutes: 20. Hitting that ceiling is reported as cancelled.

⚠️ That is the job's own run — one check run's start-to-finish — which is exactly the quantity timeout-minutes bounds. ⛔ It is not queue dwell, and this seat has previously built a maintainer-facing claim out of those two different clocks and had to withdraw it. The clock here is the right one.

The other three shards on this same head:

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 Test aggregator, 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 four Test (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

@os-try-charles
os-try-charles marked this pull request as ready for review September 18, 2026 19:40
@os-try-charles
os-try-charles added this pull request to the merge queue Sep 18, 2026
Merged via the queue into main with commit d6a8444 Sep 18, 2026
46 of 47 checks passed
@os-try-charles
os-try-charles deleted the claude/issue-9906-lint-coverage-banner-claim branch September 18, 2026 20:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

2 participants