From c5e46219a152a4b89db37b7b816e880b4ca72acc Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 25 Aug 2026 19:16:44 +0000 Subject: [PATCH] fix(pm): fail the bare-root worklist self-test when a recorded verdict sits on a REACHABLE row MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The worklist prints a live `state` beside a recorded `verdict`. When a gate carrying a refusal later declares a watch hint, the state flips to REACHABLE while the refusal stays put, and the row asserts both that its population is reachable by declaration and that it cannot be spelled. Neither existing triage assertion can see it: both audit the KEY SET. Such a row is still a row, so its verdict is not STALE, and it has left `open`, so it is not FRESH. Both halves stay satisfied and `--self-test` exits 0. Add the third direction of the same coupling — a recorded verdict may not sit on a row the sweep finds REACHABLE — and name the offending row. The pairing is general rather than restricted to the two `REFUSE-*` classes, and the failure text does NOT send the row to `DECLARED-NARROWER` as the reporting card proposed. Measured: `covered` asks whether a hint reaches an arbitrary file at the TOP of the root, so it turns true only for spellings that collapse back to the bare word; every genuinely narrower subtree leaves the row uncovered. A covered row is the bare root wearing a glob, which is the one thing `DECLARED-NARROWER` states it is not — so no covered row can honestly wear any of the three verdicts. The message names the two honest resolutions and picks neither, since choosing re-decides a verdict on a shrink-only map. No TRIAGE row's `why` or `verdict` is touched, and no row is added or removed. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01UjM2ia8Av1v5NqfqQEQmC6 --- scripts/pm/bare-root-worklist.mjs | 47 +++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/scripts/pm/bare-root-worklist.mjs b/scripts/pm/bare-root-worklist.mjs index fa81c19206..36c60124c0 100644 --- a/scripts/pm/bare-root-worklist.mjs +++ b/scripts/pm/bare-root-worklist.mjs @@ -601,6 +601,53 @@ function selfTest() { + 'check-examples-live-imports.mjs, check-driver-conformance.mjs). ⛔ Declaring a root the ' + 'gate does not read wholesale is the costlier error.' : ''}`, fresh.length === 0); + // CONTRADICTED: a refusal that outlived the reachability it refused. This is + // the THIRD direction of the same coupling, and the one neither assertion + // above can see, because both audit the KEY SET. A row whose gate later + // declares its root is STILL A ROW, so its verdict is not stale; and it has + // left `open`, so it is not fresh either. Both halves stay satisfied while + // `report` prints REACHABLE directly above a recorded reason saying the + // population cannot be spelled — one row asserting both that it is now + // reachable and that it is not. This was PREDICTED in #11155's dev report and + // then MEASURED on PR #12061, by declaring a hint on a gate carrying a + // REFUSE-UNSPELLABLE verdict: the modified and unmodified trees both exited 0 + // here, which is why a green self-test could not be read as evidence. + // + // The pairing is general rather than restricted to the two refusals, because + // EVERY verdict this file defines presupposes an UNCOVERED row: the refusals + // say a declaration was refused, and DECLARED-NARROWER says in as many words + // that the bare root is still not covered. + // + // ⛔ So the remedy is NOT "move the row to DECLARED-NARROWER". That is the + // obvious repair, it is what the reporting card proposed, and it is wrong — + // measured here rather than assumed. `covered` asks whether a hint reaches an + // ARBITRARY file at the top of the root, so it turns true ONLY for the + // spellings that collapse back to the bare word itself; every genuinely + // narrower subtree, and every deeper segment filter, leaves the row uncovered. + // A covered row is therefore the bare root wearing a separator or a glob, + // which is the one thing DECLARED-NARROWER states it is not. No covered row + // can honestly wear any of the three, and choosing between the two honest + // resolutions RE-DECIDES a verdict on a shrink-only map — not something this + // assertion may do quietly, so it names both and picks neither. + const contradicted = rows + .filter((r) => r.covered && TRIAGE.has(r.key)) + .map((r) => `${r.key} [recorded ${TRIAGE.get(r.key).verdict}]`) + .sort(); + t(`no recorded verdict sits on a row the sweep now finds REACHABLE${contradicted.length + ? ` — CONTRADICTED: ${contradicted.join(' · ')}. That gate now declares a hint reaching an ` + + 'arbitrary file at the top of the root, so the row claims BOTH that the population is ' + + 'reachable by declaration and, in its own recorded reason, that it is not. Two honest ' + + 'resolutions, differing in WHICH half is wrong. Withdraw the DECLARATION, if the recorded ' + + 'reason still holds and the hint names the gate for files it never opens — the usual ' + + 'answer under REFUSE-UNSPELLABLE, whose whole reason is that a wholesale hint would be ' + + 'FALSE, and the costlier error by the price hintCovers records. Or withdraw the VERDICT, ' + + 'if the declaration is deliberate and overrides the refusal, leaving the row to print ' + + 'REACHABLE with no reason beneath it: that is the shrink this map already permits, and the ' + + 'seven reachable rows carrying no verdict today are its live shape. ⛔ Do NOT re-point the ' + + 'row at DECLARED-NARROWER: that verdict is defined for a row that stays UNCOVERED, and a ' + + 'covered row is the bare root wearing a glob, not a narrower subtree.' + : ''}`, contradicted.length === 0); + // The spelling rule the triage docblock states, held mechanically: a key // spelled as a bare path would enter this file's own declared population. const asHints = (s) => extractWatchHints(`const L = ${JSON.stringify(s)};`);