Uh oh!
There was an error while loading. Please reload this page.
fix(pm): check-slot-lookup-ratchet declares the population it lints, so a source card derives it - #9799
fix(pm): check-slot-lookup-ratchet declares the population it lints, so a source card derives it#9799os-steve wants to merge 1 commit into
Conversation
…so a source card derives it The gate's only path literals were its OUTPUT (slot-lookup-baseline.json) and the ref its monotonicity check diffs against (origin/main). Neither is a file it reads, so dispatch-gates scored it `silent` for every card in the tree while it is REQUIRED in lint.yml — twice at the cost of a p0's CI round (#9391, and PR #9695 again). It now declares the subtree it lints (`packages/**`) with the ESLint target derived from it, the shape #9639 used for check:doc-anchors. A coupling guard asserts the declaration still equals the scope the rule is configured for in eslint.config.mjs, so the two copies cannot drift into a lying declaration. Measured over 110 families x 6249 tracked files: 20988 -> 25739 watch-hint pairs (+4751, ZERO lost), all of them under packages/ and 89.7% of them the TS dialects the gate really lints. Exactly one family gains coverage. A packages source card goes from 3 matched families to 4 (#9639's refused alternative took one card from 7 to 34); the 12 probed surfaces outside packages/ gain nothing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XqDQYVU5smx29ts9pAErja
✅ PM ACCEPT — #9700 / PR #9799Verified independently: 2 files ⭐ H1 — the literal exists, and the reason it was invisible is one character classI told you not to inherit my "likely the same shape as #9639" and to establish whether the gate names a root at all. It does:
and then two independent reasons it could not be read:
That is a much more precise diagnosis than "the gate names only its baseline", which is what the card said and what I repeated. And it matters: the fix is the #9639 template because the literal is there, not as a workaround for its absence. ⭐ And you improved on the templateThe population is spelled twice in this repo — here, and as the
#9639's template made a declaration and a scan one fact within a single file. Yours could not, because the second spelling lives in another file it does not own — so you made the disagreement loud instead of pretending it cannot happen. That is the right generalisation, and The |
Fixes#9700
check:slot-lookupis REQUIRED inlint.yml, anddispatch-gatesscored itsilentfor every card in the tree — the derivation's weakest verdict, explicitly not a
clearance. Two
priority:p0cards paid a CI round for that (#9391, and PR #9695 again).H1 first, because a "no" would have changed the shape
The dispatched question was whether
check-slot-lookup-ratchet.mjsnames any populationroot as a string literal at all. It does — inline at the call site:
But it was unreadable to the deriver, for a reason worth recording:
extractWatchHintsaccepts a path literal only if it matches its path charset, and brace expansion
(
{ts,tsx,mts,cts}) is outside it. Even if accepted, glob collapse turns that spellinginto
packages//.ts…, which covers no file. So the mechanism PR #9639 built reached towithin one character class of this gate and stopped — the literal existed and named the
right tree, in a syntax the hint language cannot represent.
Measured, on
origin/main, the gate's entire hint set:Its OUTPUT, and the ref its monotonicity check diffs against. Neither is a file it reads.
The fix — the #9639 template, plus a guard against the drift it warns about
check-slot-lookup-ratchet.mjsnow declaresPOPULATION_GLOB = 'packages/**'and derivesthe ESLint target from it (
LINT_TARGET), so what the gate declares and what it scans areone fact, exactly as
CONTENT_GLOBdid forcheck:doc-anchors.One addition beyond that template: the population is spelled twice in this repo — here and
as the
filesscope of theeslint.config.mjsblock that carries the rule. A declarationthat drifts from the scan replaces a silent gate with a lying one, so the agreement is
asserted at run time rather than assumed. A drifted declaration refuses (exit 2) and names
both spellings.
No
CHANGE_KIND_GATESentry was added. The table's own stated deletion criterion for thethree structurally identical ratchets is "when a gate grows a literal naming its
POPULATION, the ordinary derivation names it" — this is that, done directly. The card's
note about
--self-test"names all five convention gates" therefore does not apply: theconvention section is untouched.
H3 — the real population, and whether declaring it is honest
The scanned set is every TS dialect under
packages/(tests included;node_modulesanddistexcluded), and it is identical to the scope the ESLint rule is configured for —both are
packages/**/*.{ts,tsx,mts,cts}. So the declaration is the true population, nota convenient approximation.
It is also broad, and that cost is stated rather than hidden. The hint language compares
collapsed paths, so extension precision cannot survive into it: of the reach this
declaration adds, 89.7% is TS files the gate really lints; the remaining 10.3% is
.json,.mdand extension-less files underpackages/, which now get a lead they do notneed. That trade is the one
CHANGE_KIND_GATESalready decided for the three whole-treetest-file ratchets, decided the same way here — by what the gate costs to run needlessly.
Measured on this tree: 53s, no build required, and a failure prints the offending file
and line. A seat that runs it needlessly loses seconds; a seat never prompted loses a CI
round.
H2 — both directions, measured over 110 families x 6249 tracked files
packages/onlyThe negative direction is the load-bearing half: every one of the 4751 gained pairs is
under
packages/. Across 22 probed card surfaces, the 10 underpackages/each gainexactly one family (3 to 4, 12 to 13, …) and the 12 outside it —
apps/,examples/,content/,scripts/,.changeset,.github/workflows,docs/adr,skills/,docker/— gain nothing. For contrast, #9639's rejected alternative measured +139084 pairsand took one card from 7 matched families to 34.
Acceptance test, on the file surface of the p0 that paid for this:
Reverse verification — both ablations, run from the committed state
keeping the cases gives
✗ dispatch-gates self-test: 1 of 288 case(s) failed, and thefailing case is the positive one. (The three negative cases pass under ablation by
construction — they are the "no false leads" half.)
packages/core/**makes thegate exit 2 naming both spellings, instead of silently measuring a smaller population.
107 unswept site(s) in 25 file(s)before and after, byte-identical verdict.
Gates, all at 6c58cc4
pnpm check:slot-lookup✓ ·pnpm check:pm-dispatch-gates✓ (288 cases) ·pnpm check:cross-package-test-inputs✓ ·pnpm check:nul-bytes✓ ·pnpm check:ratchet-remedy-authority✓ ·eslint --no-inline-configon both changedfiles ✓. Union re-derived from the real diff with
node scripts/pm/dispatch-gates.mjs scripts/check-slot-lookup-ratchet.mjs scripts/pm/dispatch-gates.mjs.skip-changeset:scripts/only, nothing published.#9721 remains open and is the dedup partner recorded on the card; its class question is
routed to #9747, where the
silentcensus this card produced has been posted as a comment.Generated by Claude Code