Skip to content

Commit 2ed1212

Browse files
fix(tooling): attribute a gate's declared hints per ROW, not per FILE (#19204)
**Attributing a declared hint to a CONSTANT rather than to a FILE is information the `ROOT_DIR_WATCH_HINTS` idiom does not carry today.** Hints are declared per FILE — one array beside the constants — so a file-level read cannot tell two rows at one root apart, and `check-driver-conformance` carries exactly two at `packages`: `DRIVERS_DIR`, which owns `packages/drivers/**`, and `CASE_SETS_DIR`, which owns nothing there. That is why the obvious pin reds a correct row on day one, and it is the finding this card produced rather than the patch. Part of #15926. ## The gap, re-verified on the base of this branch `scripts/pm/bare-root-worklist.mjs` compared a recorded refusal against what a gate declares in exactly three places, and all three missed the same shape: - the declaration pin was scoped by **verdict** (`v.verdict !== 'DECLARED-NARROWER'` then `continue`), - `contradictedRows` is scoped by **reachability** (`r.covered`), which a strictly narrower declaration never makes true, - every remaining pin asks the **tree**, which a declaration does not move. So a `REFUSE-*` row whose gate declares a genuinely narrower hint at that row's root fell between all three, forever, with `--self-test` green. #15602 measured that happening: `check-declaration-mirrors` declared both sides of its mirror, the row went on recording `REFUSE-UNSPELLABLE`, and nothing anywhere reddened. ## ⚠️ A correction to the dispatch's own measurement Asking today's question — which `REFUSE-*` rows have a gate declaring at that root — yields **one** row on this branch's base (`4cb3b15f4`), not two: ``` DECLARES REFUSE-UNSPELLABLE scripts/check-driver-conformance.mjs CASE_SETS_DIR packages -> ["packages/drivers/**"] total: 1 ``` `check-declaration-mirrors.mjs SCRIPTS_DIR scripts` is recorded `DECLARED-NARROWER` on `origin/main` — #15602 landed and re-decided it, so it is no longer a `REFUSE-*` row at all. Consequence for the pricing below: **on today's real tree the new direction has zero genuine positives.** Its whole live effect is to hold one attribution that was previously prose, and its RED end must be constructed. That does not weaken the case (the class is what ran green indefinitely), but it does mean "which existing rows go red" is answerable exactly: none, on any route. ## The three routes, priced All three were priced before one was written, as the card required. Measurements are at `4cb3b15f4` in worktree `objectstack-issue-15926` unless stated. ### Route 1 — attribute by COVERAGE Ask whether any declared hint COVERS the population that row's constant names. | term | measured | |---|---| | files | `scripts/pm/bare-root-worklist.mjs`, plus every gate that would have to export a walk to drive | | lines | **not closable** — see below | | new pins | ~46 attribution + ~138 population pins (each population is a new claim about the tree, and this file's own rule is that an unpinned claim is the allowlist rot it refuses by name, so each owes a LIVE/PRECISE/COMPLETE triple like every `SPELLINGS` entry) | | rows green to red | **cannot be stated without doing the route** | The mechanism is right — `packages/drivers/**` does not cover `packages/spec/src/data/*-conformance.ts`, so `CASE_SETS_DIR` stays green, and a declaration at `packages/spec/src/data/**` would cover it and red. The cost is the input: this map holds a row's population as **prose plus a number**, most of these gates export no walk, and the file's own docblock says so per row ("its gate exports no walk to drive and reproducing the filter by hand would be the estimate this docblock refuses"). The recorded precedent for how expensive a machine-readable population is here is `CENSUS_REFUSE_WIDE`: 14 rows measured under `strace -f -e trace=openat` against a dedicated worktree at one pinned base, after a JS-level `fs` shim was tried first and measured EMPTY. ⇒ Pricing this route **is** doing it: one re-measurement per row, 46 of them, which is the second card the triage said was hiding inside the option. The unpriceable-ness is itself the measurement. ### Route 2 — extend `omits` DOWNWARD ⭐ landed here Let a `REFUSE` row name the sibling declaration it is deliberately not claiming, the way a `DECLARED-NARROWER` row names a hint it passes over. | term | measured | |---|---| | files | **1** — `scripts/pm/bare-root-worklist.mjs` | | lines | **+347 / −37** (384 changed); the executable part is about 120 lines, the rest is the docblock this file requires of a widening | | new pins | battery `The DECLARATION a row describes, read from the gate's own SOURCE` goes **43 → 128 cases** (a floor, measured on a run) | | rows green to red | **zero**, named: the only recorded row the widening newly reaches is `scripts/check-driver-conformance.mjs CASE_SETS_DIR packages`, and it goes green by RECORDING the attribution its own `why` already asserted. The other 34 recorded rows sit in gates that declare nothing at their root; the remaining 11 were already inside the pin. | The 85 new cases break down as: 46 gate-source-readable + 46 attribution (the generalisation, one per recorded row), 22 the `DECLARED-NARROWER` pair the pin already ran, 3 non-vacuity identities, 1 `omits` shape rule and 1 control proving that rule can FAIL, 1 "no refusal records a spelling", and 7 constructed-gate cases. What it buys is not new information — the `CASE_SETS_DIR` row's `why` has said *"Its sibling constant took the escape; this one has nothing honest to declare"* since it was written. ⭐ It puts that already-recorded sentence under a pin that re-checks it every run, turning prose that happens to be true into a fact that must stay true. ### Route 3 — attribute in the IDIOM Carry the constant beside the hint at the declaration site. | term | measured | |---|---| | files | **65** files declare, **71** declarations, **158** hint literals — corroborated by the gate's own pass line: `check-watch-hint-literal: 71 declaration(s) across 4 rostered name(s) ... all 158 of those literals admitted`. Plus `scripts/check-watch-hint-literal.mjs` itself (939 lines, 72 self-test cases) and each declaring gate's own assertions over its array. | | lines | 158 hint entries rewritten at minimum; the four rostered names occur **405** times across **73** tracked files, 71 of those being the declaration statements themselves | | new pins | the parser's own cases, plus one per gate carrying two population constants at one root (measured: 3 `(file, root)` pairs carry two rows; 2 of those files declare) | | rows green to red | during the transition, **all 71 declarations** and, in this file, the declaration pin on **all 11 `DECLARED-NARROWER` rows at once** | The blocking mechanic is measured, not assumed: `literalHints` accepts only a flat array of quoted string literals and returns `null` otherwise, which `auditSourceName` turns into `ok: false` — *"the declaration is COMPUTED, not a literal array"*. So any richer element shape reds every one of the 71 declarations until the parser, the roster and every gate's self-test move with them in one PR. A cheaper spelling (a parallel owner map beside the array) is a FIFTH rostered idiom with its own roster entry, its own gate and its own drift pin — a relocation of the blast radius, not a reduction. ⛔ Not reached for as the "proper" repair. It is the right long-term shape and it is a card of its own. ## What landed Every hint a gate declares at a row's bare root is now **attributed**: either CLAIMED by that row's recorded spelling, or explicitly DISCLAIMED in its `omits`, with the reason in its `why`. The comparison is a pure exported function (`declarationAttribution`), so the live rows and the fixture cases beside them cannot disagree about what an attribution is — the same reason `contradictedRows` is pure. Three asymmetries the generalisation respects: - the `recorded is a subset of declared` direction stays scoped to `DECLARED-NARROWER`. A `SPELLABLE-UNDECLARED` spelling is a claim about the TREE, not about the array — its verdict says the gate declares NOTHING for that population — so reading it as a claim on the declaration would red 11 correct rows. A case pins that direction: a gate that STARTS declaring exactly that spelling reds the row for a re-decision instead of quietly satisfying it. - a refusal records no `spelling` at all, now asserted. - a record cannot both claim and disclaim one hint. ⛔ **The `CASE_SETS_DIR` row is not suppressed.** An allowlist names a ROW to skip and nothing about the gate can make it fire again; this names a HINT, re-reads it out of the gate's source every run, and reds when either side moves. Proven below in both directions. ⛔ No verdict is added, changed or withdrawn. The 13 `UNJUDGED` rows are untouched. ## 先红后绿 — both directions driven **RED, on the real map** (ablation: delete the one `omits`, restore from `HEAD`, restore proven byte-identical): ``` BEFORE marker count: 1 AFTER marker count: 0 RED LEG EXIT=1 failures: 1 x self-test: ...and every hint that gate declares at packages is ATTRIBUTED by "scripts/check-driver-conformance.mjs CASE_SETS_DIR packages" -- UNATTRIBUTED: [packages/drivers/**], named as omitted but NOT declared: [none], both claimed and disclaimed: [none]. HEAD blob 8476b24... == disk blob 8476b24... git diff HEAD: empty ``` **RED, from the GATE's side** (ablation: move `check-driver-conformance`'s declaration to another subtree) — this is the half that separates an attribution from an allowlist: ``` GATE-MOVED LEG EXIT=1 failures: 3 ATTRIBUTED by "scripts/check-driver-conformance.mjs CASE_SETS_DIR packages" ATTRIBUTED by "scripts/check-driver-conformance.mjs DRIVERS_DIR packages" NOT DECLARED: packages/drivers/** named as omitted but NOT declared: [packages/drivers/**] restore: disk blob == HEAD blob, git status clean, self-test back to EXIT 0 ``` **RED, on a CONSTRUCTED case** — a `REFUSE` row whose gate declares a narrower hint at that root and which attributes it to nothing. Driven inside `--self-test`, through the idiom's OWN parser (`auditSource` over a gate source built for it), never a hand-fed hint array. Its hints are joined from segments at runtime, and its declaration NAME is assembled at runtime too: `const` plus the rostered name spelled out inside this file IS a declaration site to `check:watch-hint-literal`, which reds `every live declaration is a literal` on it — measured, then repaired. **GREEN, on today's real tree**: `check-driver-conformance CASE_SETS_DIR packages` does not red, and the reason is the attribution judgement. ``` node scripts/pm/bare-root-worklist.mjs --self-test -> EXIT 0 81 live row(s) - 46 recorded verdict(s) - 13 UNJUDGED - 14 CENSUS ``` All three pinned numbers unchanged from the base; assertion count only rises. ## Gates Derived with `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands` (no hand-written path list) and reconciled with `--ran`: ``` Run reconciliation -- 28 derived, 28 run, 0 NOT-MEASURED, 0 UNRUN. EXIT CODES -- all 28 accounted famil(ies) carry one. ``` Every one exited 0. Named readings worth quoting: - `pnpm check:pm-dispatch-gates` — `VERDICT command-exit 0`, 611.7s, run detached under `scripts/pm/os-verify-lock.sh` and waited on with `tail --pid` (`held the lock 613s, waited 0s`). ⚠️ It is not a timeout-shaped NOT MEASURED; it is a reading. - `pnpm check:watch-hint-literal` — went RED first on this diff, for the declaration-name reason above, and is green after: `71 declaration(s) across 4 rostered name(s) ... no unrostered spelling of the idiom in the tree`. - `pnpm lint` (`eslint . --no-inline-config`, the whole repo, not a narrowing) — EXIT 0. - Type check: `scripts/pm/bare-root-worklist.mjs` is a `.mjs` with no `.d.mts` mirror, the repo-root `tsconfig.json` sets neither `allowJs` nor `checkJs`, and `turbo run typecheck` is per-package while this diff touches no package. ⇒ **no tsc program compiles this file** — a measured absence, not an unrun lane. `dispatch-gates` prints two families whose names contain "typecheck" (#19172); neither reads this path. ## Changeset None, `skip-changeset`. The diff is one repo-root tooling script under `scripts/pm/`, which no package's `files[]` ships — nothing already published moves. ## Not in scope, noted `scripts/check-whole-set-label-write.mjs ROOTS scripts` is an `UNJUDGED` row whose gate DOES declare three hints at `scripts`. It owes a maintainer ruling under #15468 and is ⛔ none of this card's business — the new pin deliberately does not reach rows carrying no verdict. Worth knowing for whoever judges it: if it lands on a refusal, the pin will ask it to attribute those three. --- _Generated by [Claude Code](https://claude.ai/code/session_017ef78bLdybu3AffehKkhfk)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 4cb3b15 commit 2ed1212

1 file changed

Lines changed: 347 additions & 37 deletions

File tree

0 commit comments

Comments
 (0)