diff --git a/scripts/check-where-matcher-conformance.mjs b/scripts/check-where-matcher-conformance.mjs index 211be704d0..23550c7374 100644 --- a/scripts/check-where-matcher-conformance.mjs +++ b/scripts/check-where-matcher-conformance.mjs @@ -232,6 +232,55 @@ const repoRoot = resolve(__dirname, '..'); const BASELINE_PATH = 'scripts/where-matcher-conformance.baseline.json'; const SCAN_ROOT = 'packages'; +/** + * ## The dispatch-gates declaration -- the `ROOT_DIR_WATCH_HINTS` idiom (#13163) + * + * `scripts/pm/dispatch-gates.mjs` derives WHICH gates a card must run by matching the + * path literals in each gate's source against the card's changed files. This gate + * declared its population TWICE and the derivation could read NEITHER of them: + * + * 1. IN PROSE, in the discovery section above -- "a function in `packages/**\/*.test.ts` + * whose body...". `extractWatchHints` masks comment ranges by construction, so a + * population documented in a comment is invisible to it BY DESIGN. ⛔ Rewording that + * comment fixes nothing. (The separator is escaped only so the glob cannot close + * this block comment -- the same spelling rule the PM's bare-root worklist follows.) + * 2. IN CODE, as `SCAN_ROOT` above -- a bare single-segment word, which `looksPathy` + * refuses as no hint at all (a measured refusal: admitting the bare-top-level-word + * class costs +139084 fabricated pairs, and it stays). The one narrow re-admission, + * `moduleRelativeDirectoryHint`, resolves a single-segment literal against the + * SCRIPT'S OWN directory, so `'packages'` resolves under `scripts/`, which is not + * tracked ⇒ null. + * + * Measured before this declaration, `extractWatchHints` over this file returned exactly + * ONE hint: `scripts/where-matcher-conformance.baseline.json`, this gate's own ledger. + * ⇒ the derivation could name this gate only for a change set that edits the set of + * files ALREADY KNOWN to be wrong, and never for a NEW silently-wrong matcher anywhere + * under the root it scans -- the exact inverse of what it guards. The live specimen is a + * PR that derived 30 of 30 green gates locally, whole-repo lint included, and still went + * RED in CI on a test file it ADDS. + * + * The spelling below is measured on this tree at 2889 of the 2889 files this gate's own + * `testFilesUnder()` walk admits -- set-equal in BOTH directions against `hintCovers`, + * nothing walked left uncovered and nothing covered left unwalked, so 100% precise and + * complete -- against 5509 tracked files under the bare root. Its liveness and precision + * carry a second, independent pin in the PM's bare-root worklist, whose recorded verdict + * for this row moves to DECLARED-NARROWER with this change. + * + * ⛔ It must be spelled as a LITERAL, not built from `SCAN_ROOT` -- the hint extractor + * reads source text, so a computed template of the root would produce no hint and leave + * the gate exactly as invisible. Both directions are pinned in `--self-test` below. A + * declaration that can drift from the scan is worse than none -- it replaces a silent + * gate with a lying one. + * + * ⚠️ This does NOT retire the convention-KIND entry that names this gate for "adds or + * edits a test file" in the dispatch tool: that entry is a different authority, pinned by + * that tool's own self-test, and it reaches a card dispatched BEFORE its code exists, + * which no path derivation can. What changes is that the high-signal MATCHED column -- + * the one a dev pastes and runs -- now names this gate for the test files it really + * walks, instead of only for edits to its own baseline. + */ +const ROOT_DIR_WATCH_HINTS = ['packages/**/*.test.ts']; + // --------------------------------------------------------------------------- // The probe vocabulary. Field names are deliberately synthetic so no matcher // can special-case them (several doubles branch on `organization_id`, `id`, @@ -872,6 +921,58 @@ function selfTest() { expect('a fallen count is an error (ratchet down)', reconcile(fakeMeasured, { 'a.test.ts': { silent: 2 } }).length === 1); expect('a stale entry is an error', reconcile(new Map(), { 'gone.test.ts': { silent: 1 } }).length === 1); + // -- the dispatch-gates declaration (#13163's landing obligation) --------- + // + // Enforcement cannot hold either half here: the declaration is read by ANOTHER TOOL + // (the PM's dispatch derivation), so a wrong or stale one runs green in this file + // forever and pays itself out as a dev dispatched on a test-file card with this gate + // missing from the brief -- the CI round trip #13163 was filed for. A missing + // declaration is a silent gate; a surplus one is a LYING gate, and the price the + // derivation records for a fabricated lead is higher than for a missing one. Driven + // through this gate's OWN corpus walk, never a copy of its regex. + const DECLARED_TAIL = '.test.ts'; + const walked = testFilesUnder(join(repoRoot, SCAN_ROOT)) + .map((abs) => relative(repoRoot, abs).replace(/\\/g, '/')); + // The needle is ASSEMBLED, never spelled. Written as a literal here it would appear in + // this assertion's own source text, so `includes` would find it in the CHECK rather + // than in the declaration and stay green with the declaration deleted -- a phantom pin. + const declNeedle = `'${SCAN_ROOT}/` + '*'.repeat(2) + `/*${DECLARED_TAIL}'`; + const ownSource = readFileSync(fileURLToPath(import.meta.url), 'utf8'); + expect( + 'the declaration is spelled as a LITERAL in this source, not computed -- the hint ' + + 'extractor reads source text, so a computed root would build no hint at all', + ownSource.includes(declNeedle), + ); + expect( + 'the declared hint is rooted at the population constant this gate actually walks', + ROOT_DIR_WATCH_HINTS.length === 1 && ROOT_DIR_WATCH_HINTS[0].startsWith(`${SCAN_ROOT}/`), + ); + expect( + 'the corpus walk is non-empty, so the two directions below judge something', + walked.length > 0, + ); + expect( + 'nothing is declared that this gate does not walk -- every file testFilesUnder admits ' + + 'lies under the declared root and ends in the declared extension', + ROOT_DIR_WATCH_HINTS.every((h) => h.endsWith(DECLARED_TAIL)) + && walked.every((f) => f.startsWith(`${SCAN_ROOT}/`) && f.endsWith(DECLARED_TAIL)), + ); + // ...and the declared filter is a NARROWING rather than the bare root wearing a glob: + // a non-test sibling sitting in the same directory as an admitted file is NOT admitted. + const admitted = new Set(walked); + const siblingDir = walked.length ? dirname(join(repoRoot, walked[0])) : null; + const nonTestSibling = siblingDir + ? readdirSync(siblingDir) + .map((e) => relative(repoRoot, join(siblingDir, e)).replace(/\\/g, '/')) + .find((f) => !f.endsWith(DECLARED_TAIL) && statSync(join(repoRoot, f)).isFile()) + : null; + expect('a non-test sibling exists to prove the filter discriminates', Boolean(nonTestSibling)); + expect( + 'and the declaration is a NARROWING, not the bare root wearing a glob -- that sibling ' + + 'is under the same root and is NOT in the walk', + Boolean(nonTestSibling) && !admitted.has(nonTestSibling), + ); + if (failures.length > 0) { console.error(`✗ check-where-matcher-conformance --self-test (${failures.length} failure(s)):\n`); for (const f of failures) console.error(` • ${f}`); diff --git a/scripts/pm/bare-root-worklist.mjs b/scripts/pm/bare-root-worklist.mjs index f37ea2453f..42562b33be 100644 --- a/scripts/pm/bare-root-worklist.mjs +++ b/scripts/pm/bare-root-worklist.mjs @@ -189,6 +189,19 @@ const POPULATION_CONSTANT = /^(?:[A-Z0-9_]*_ROOTS?|[A-Z0-9_]*_DIRS?|POPULATION|[ * covered. A withdrawn verdict that deleted the row would land it back as an * untriaged FRESH row on the next run, which is the assertion below saying so. * + * ⭐ An EIGHTEENTH row was re-decided on 2026-08-29 under that same authorisation + * sentence and no wider one: `check:where-matcher SCAN_ROOT packages`, whose refusal + * rested on the identical retired collapse and whose population is the identical + * `.test.ts` corpus as the `check:objectql-double-limit` row. It was left standing on + * 2026-08-26 because it had no recorded consumer, which is the criterion that split that + * class in two; #13163 is that consumer, and it is MEASURED rather than argued — the + * derivation reached this gate for 0 of the test corpus, so a dev adding a + * silently-wrong matcher ran a 30-of-30 green local union and lost a CI round anyway. So + * this row takes the DECLARED-NARROWER half of the split rather than + * SPELLABLE-UNDECLARED, and its gate now carries the declaration. ⛔ Its numbers are + * re-measured on the 2026-08-29 tree and NOT carried from its sibling, which this + * docblock forbids by name. + * * ⚠️ One row of that seventeen was re-measured into a DIFFERENT population, not * merely fresher digits: #12392 (PR #12423, `69d0e18`) made * `check-skills-token-ratchet`'s walk RECURSIVE over whole skill directories, so @@ -325,6 +338,27 @@ const TRIAGE = new Map([ + 'bare root is still not covered — the spelling reaches no arbitrary file at the top of ' + 'the root — which is what this verdict says and is correct, not outstanding debt', }], + ['check:where-matcher SCAN_ROOT packages', { + verdict: 'DECLARED-NARROWER', + spelling: 'package test files', + why: 'REFUSED as unspellable on the reading that every glob form of this population ' + + 'collapses to a malformed double-separator prefix reaching nothing. #12300 retired that ' + + 'collapse — a glob in a non-final segment is MATCHED now — so the refusal was FALSE of ' + + 'this tree, in the same way and for the same reason as its identically-populated sibling ' + + 'check:objectql-double-limit above. Re-measured HERE rather than inherited from that row: ' + + 'the recorded spelling reaches 2889 of the 2889 files this gate own testFilesUnder() walk ' + + 'admits, SET-EQUAL in both directions — nothing walked left uncovered, nothing covered ' + + 'left unwalked — so 100% precise and complete, against 5509 tracked files under the bare ' + + 'root. Declared beside SCAN_ROOT under the ROOT_DIR_WATCH_HINTS idiom for consumer ' + + '#13163, the measured downstream pull this row lacked when the seventeen were ' + + 're-adjudicated: before it, extractWatchHints over the gate returned ONE hint, the gate ' + + 'own baseline JSON, so the derivation could name this gate only for a change set editing ' + + 'the files ALREADY KNOWN to be wrong and never for a NEW silently-wrong matcher — the ' + + 'inverse of what it guards, paid as a CI round trip by a PR that derived 30 of 30 green ' + + 'gates locally. The row STAYS in the sweep because the bare root is still not covered — ' + + 'the spelling reaches no arbitrary file at the top of the root — which is what this ' + + 'verdict says and is correct, not outstanding debt', + }], ['check:skill-refs SKILLS_DIR skills', { verdict: 'DECLARED-NARROWER', spelling: 'skill reference folders', @@ -451,10 +485,6 @@ const TRIAGE = new Map([ why: 'test files only, 2510 of 4903 (51%) — and already refused in that gate own docblock, ' + 'measured there at 76 real couplings out of 4861 (1.6%)', }], - ['check:where-matcher SCAN_ROOT packages', { - verdict: 'REFUSE-UNSPELLABLE', - why: 'test files only, 2510 of 4903 (51%)', - }], ['check:runner-env-posture SCANNED_ROOTS packages', { verdict: 'REFUSE-UNSPELLABLE', why: 'non-test source beneath a `src` SEGMENT — 1812 of 5241 (35%), re-derived from the gate '