From a5d988e3d90ff8e780e4dd8c88cf724abb05b415 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 2 Sep 2026 18:30:37 +0000 Subject: [PATCH] Declare the scripts/ population two gates walk but never named `scripts/pm/dispatch-gates.mjs` builds every dispatch's gate list by scanning a gate's own source for the path literals it operates on, and "looks like a path" there means "carries a separator". Both gates below seed a recursive walk at the bare single-segment word `scripts`, which the extractor cannot see at all, so the only literals recoverable from either file named individual artifacts: the six scripts one gate's ledger cites by name, and two `owner/repo` action slugs plus sandbox filenames in the other. An artifact roster is not a population -- a list of the files that already exist can never contain the one added tomorrow -- so both families walked all of `scripts/` and appeared on no card that edited any of it. Each now declares that population in its own module body under the ROOT_DIR_WATCH_HINTS idiom, pinned in its own --self-test against the LIVE walk in both directions: nothing walked left uncovered, nothing covered left unwalked. One hint per admitted extension rather than the bare subtree, following check-ratchet-remedy-authority.mjs at this same root -- `scripts/**` would name both gates for the 91 JSON, Markdown and text files under the root that neither one opens. An extension the filter admits but the tree does not yet hold is deliberately absent, because a hint reaching nothing tracked is a dead lead the consumer reports as a population; the pins redden the day such a file lands. Measured on this tree with the derivation's own --residue, probe scripts/check-nul-bytes.mjs: 14 matched families -> 16, Silent 148 -> 146. With an outside probe (packages/metadata-protocol/src/protocol.ts) both stay Silent, so the declaration does not over-claim. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV --- scripts/check-self-test-wired.mjs | 107 +++++++++++++++++++++++- scripts/check-whole-set-label-write.mjs | 106 +++++++++++++++++++++++ 2 files changed, 212 insertions(+), 1 deletion(-) diff --git a/scripts/check-self-test-wired.mjs b/scripts/check-self-test-wired.mjs index f02d4261b8..c7774657c2 100644 --- a/scripts/check-self-test-wired.mjs +++ b/scripts/check-self-test-wired.mjs @@ -116,6 +116,41 @@ const RATCHET_AUTHORITY_MARKER = '⛔ MAINTAINER-ONLY'; /** Extensions whose files can be a `scripts/` entry point. */ const SCRIPT_EXT = /\.(mjs|mts|js|sh)$/; +/** + * POPULATION DECLARATION -- what `scripts/pm/dispatch-gates.mjs` is told this + * gate reads, in the subtree spelling that tool compares in. Provenance ONLY: + * nothing in this file reads this array. + * + * That tool builds every dispatch's gate list by scanning a gate's own source + * for the path literals it operates on, and "looks like a path" there means + * "carries a separator". This gate's corpus root arrives as + * `join(ROOT, 'scripts')` -- a bare single-segment word -- so the only literals + * the extractor could recover from this file were the workflow directory and + * the handful of individual scripts the ledger below cites BY NAME. That is an + * artifact roster, not a population: a list of the files that already exist can + * never contain the one added tomorrow. The measured result was a family that + * walks all of `scripts/` and appeared on no card that edited any of it. + * + * ⛔ NOT the bare subtree. One hint per admitted extension, following + * `check-ratchet-remedy-authority.mjs` at this same root: `scripts/**` would + * name this gate for the JSON, Markdown and text files under the root that + * `walkScripts` never opens. The declared set is SET-EQUAL to that walk -- + * nothing walked left uncovered, nothing covered left unwalked -- which is what + * `--self-test` pins, in both directions and against the live tree. + * + * An extension `SCRIPT_EXT` admits but the tree does not yet HOLD is + * deliberately absent: a hint reaching nothing tracked is a dead lead, which + * the consumer reports as a population and is not one. The pin below reddens + * the day such a file lands, which is the coupling that keeps this honest. + * + * Spelled as a LITERAL array, never computed from `SCRIPT_EXT`: the extractor + * reads SOURCE TEXT, so a built spelling keeps this value identical at runtime, + * keeps every assertion about it green, and contributes ZERO hints. + * `check-watch-hint-literal.mjs` holds that rule fleet-wide; the self-test + * below holds the own-source half. + */ +const ROOT_DIR_WATCH_HINTS = ['scripts/**/*.mjs', 'scripts/**/*.mts', 'scripts/**/*.sh']; + /** * A `scripts/...` path, optionally followed by `--self-test`. * @@ -463,6 +498,7 @@ const SELF_TEST_BATTERIES = Object.freeze({ 'right boundary': 4, 'alias resolution': 4, 'population verdict': 4, + 'population declaration': 7, 'ledger hygiene': 9, 'live ledger': 4, }); @@ -470,7 +506,7 @@ const SELF_TEST_BATTERIES = Object.freeze({ // DELETING an entry silences that battery's floor exactly as effectively as // zeroing it, so the registry's own size is pinned too. Adding a battery raises // this number; removing one is the same ⛔ deliberate edit as lowering a count. -const SELF_TEST_BATTERY_FLOOR = 6; +const SELF_TEST_BATTERY_FLOOR = 7; // The key an assertion is filed under when no battery is open. It is not a // declared battery, so it reds by the same set difference rather than silently @@ -597,6 +633,75 @@ function selfTest() { ); } + // ── POPULATION DECLARATION: what the dispatch derivation is told this gate reads ── + // + // Nothing in this file can ENFORCE the declaration: `ROOT_DIR_WATCH_HINTS` is + // read by another tool entirely (`extractWatchHints` in + // `scripts/pm/dispatch-gates.mjs`), so a stale or wrong one runs green here + // forever and pays itself out as a dev dispatched on a `scripts/` card with + // this gate absent from the brief -- the exact round this declaration was + // added to end. So the pins below hold it against the LIVE WALK rather than + // against a fixture: a sandbox tree would keep them green while the real + // declaration drifted. + battery('population declaration'); + { + const walked = walkScripts(join(ROOT, 'scripts')); + const extOf = (path) => path.slice(path.lastIndexOf('.')); + // Written INDEPENDENTLY of `hintCovers` on purpose: a pin that reuses the + // consumer's own matcher cannot catch the consumer changing under it. + const declares = (path) => + path.startsWith('scripts/') && ROOT_DIR_WATCH_HINTS.includes(`scripts/**/*${extOf(path)}`); + + ok( + walked.length > 0, + 'the population pin walked NO files — a broken walk proves nothing about the declaration (#4690)', + ); + ok( + walked.every(declares), + 'a file this gate WALKS is left undeclared — the declaration under-names the population it exists ' + + 'to publish, which is the silence it was added to end', + ); + ok( + ROOT_DIR_WATCH_HINTS.every((hint) => walked.some((path) => declares(path) && `scripts/**/*${extOf(path)}` === hint)), + 'a declared hint reaches nothing this gate walks — a dead lead, which the consumer reports as a ' + + 'population and is not one', + ); + ok( + ROOT_DIR_WATCH_HINTS.every((hint) => SCRIPT_EXT.test(hint)), + 'a declared hint names an extension SCRIPT_EXT does not admit — the declaration over-names the walk, ' + + 'and a declaration that can drift from the walk is worse than none', + ); + ok( + !ROOT_DIR_WATCH_HINTS.some((hint) => hint === 'scripts' || hint.endsWith('/**') || hint === '.' || hint === '**'), + 'the bare subtree or the repo root was declared — it would name this gate for every JSON, Markdown ' + + 'and text file under the root that this gate never opens', + ); + ok( + ROOT_DIR_WATCH_HINTS.every((hint) => hint.includes('/')), + 'a declared literal carries no separator, so the consumer refuses it as too generic and it reaches nothing', + ); + // The literal SPELLING is the whole mechanism: a value built from + // SCRIPT_EXT would keep the runtime value identical, keep every assertion + // above green, and contribute ZERO hints. `check-watch-hint-literal` owns + // that rule fleet-wide; this is the own-source half. + let ownSource = null; + try { + ownSource = readFileSync(join(ROOT, 'scripts/check-self-test-wired.mjs'), 'utf8'); + } catch { + ownSource = null; + } + const declSites = ownSource === null + ? [] + : [...ownSource.matchAll(/\bconst\s+ROOT_DIR_WATCH_HINTS\s*=\s*([^;]*);/g)]; + ok( + declSites.length === 1 + && ROOT_DIR_WATCH_HINTS.every((hint) => declSites[0][1].includes(`'${hint}'`)) + && !/[A-Za-z_$][\w$]*\s*\./.test(declSites[0][1]), + 'the declaration is not a single literal array of quoted strings — the extractor reads SOURCE TEXT, ' + + 'so a computed spelling contributes nothing while every assertion above stays green', + ); + } + // ── Ledger hygiene: every row must still be true, and still be needed ──── battery('ledger hygiene'); { diff --git a/scripts/check-whole-set-label-write.mjs b/scripts/check-whole-set-label-write.mjs index 45313aba23..46a8f0eb54 100644 --- a/scripts/check-whole-set-label-write.mjs +++ b/scripts/check-whole-set-label-write.mjs @@ -156,6 +156,42 @@ export const SCANNED_EXTENSIONS = new Set(['.yml', '.yaml', '.mjs', '.js', '.cjs const SKIP_DIRS = new Set(['node_modules', '.git', 'dist', 'build', 'coverage']); +/** + * POPULATION DECLARATION -- what `scripts/pm/dispatch-gates.mjs` is told this + * gate reads, in the subtree spelling that tool compares in. Provenance ONLY: + * nothing in this file reads this array. + * + * That tool builds every dispatch's gate list by scanning a gate's own source + * for the path literals it operates on, and "looks like a path" there means + * "carries a separator". Two of the three `ROOTS` carry one and are recovered + * for free; the third is the bare single-segment word `scripts`, which the + * extractor cannot see AT ALL. So the only other literals this file offered it + * were two `owner/repo` action slugs and the sandbox filenames the fixtures + * build -- an artifact roster, not a population. The measured result was a gate + * that walks all of `scripts/` and appeared on no card that edited any of it, + * while its two `.github` roots were named correctly the whole time. + * + * ⛔ NOT the bare subtree. One hint per admitted extension, following + * `check-ratchet-remedy-authority.mjs` at this same root: `scripts/**` would + * name this gate for the JSON, Markdown and text files under the root that + * `walk` skips on `SCANNED_EXTENSIONS`. The declared set is SET-EQUAL to what + * that walk admits under this root -- nothing walked left uncovered, nothing + * covered left unwalked -- which is what `--self-test` pins, in both directions + * and against the live tree. + * + * An extension `SCANNED_EXTENSIONS` admits but the tree does not yet HOLD under + * this root is deliberately absent: a hint reaching nothing tracked is a dead + * lead, which the consumer reports as a population and is not one. The pin + * below reddens the day such a file lands. + * + * Spelled as a LITERAL array, never computed from `SCANNED_EXTENSIONS`: the + * extractor reads SOURCE TEXT, so a built spelling keeps this value identical + * at runtime, keeps every assertion about it green, and contributes ZERO hints. + * `check-watch-hint-literal.mjs` holds that rule fleet-wide; the self-test + * below holds the own-source half. + */ +const ROOT_DIR_WATCH_HINTS = ['scripts/**/*.mjs', 'scripts/**/*.ts', 'scripts/**/*.sh']; + /** * How far apart the method slot and the `/labels` path may sit and still be * read as one call. A backslash-continued `curl` puts them 1-3 lines apart; a @@ -898,6 +934,76 @@ export function selfTest() { failures.push(`WHOLE_SET_ACTIONS['${action}'] has no source-read reason`); } + // ── POPULATION DECLARATION: what the dispatch derivation is told this gate reads ── + // + // Nothing in this file can ENFORCE the declaration: `ROOT_DIR_WATCH_HINTS` is + // read by another tool entirely (`extractWatchHints` in + // `scripts/pm/dispatch-gates.mjs`), so a stale or wrong one runs green here + // forever and pays itself out as a dev dispatched on a `scripts/` card with + // this gate absent from the brief. Held against the LIVE WALK rather than a + // fixture tree: the fixtures below build sandboxes, and a pin over one of + // those would stay green while the real declaration drifted. + { + const walked = []; + walk(REPO_ROOT, 'scripts', walked); + const extOf = (path) => path.slice(path.lastIndexOf('.')); + // Written INDEPENDENTLY of the consumer's `hintCovers`, deliberately: a pin + // that reuses the consumer's own matcher cannot catch it changing underneath. + const declares = (path) => + path.startsWith('scripts/') && ROOT_DIR_WATCH_HINTS.includes(`scripts/**/*${extOf(path)}`); + + expect('POPULATION the declaration pin walked files at all (#4690)', walked.length > 0, true); + expect( + 'POPULATION every file this gate walks under scripts/ is declared', + walked.every(declares), + true, + ); + expect( + 'POPULATION every declared hint reaches a file this gate walks (no dead lead)', + ROOT_DIR_WATCH_HINTS.every((hint) => walked.some((path) => declares(path) && `scripts/**/*${extOf(path)}` === hint)), + true, + ); + expect( + 'POPULATION every declared hint names an extension SCANNED_EXTENSIONS admits', + ROOT_DIR_WATCH_HINTS.every((hint) => SCANNED_EXTENSIONS.has(extOf(hint))), + true, + ); + expect( + 'POPULATION the bare subtree and the repo root are NOT declared', + ROOT_DIR_WATCH_HINTS.some((hint) => hint === 'scripts' || hint.endsWith('/**') || hint === '.' || hint === '**'), + false, + ); + expect( + 'POPULATION every declared literal carries a separator (a bare word reaches nothing)', + ROOT_DIR_WATCH_HINTS.every((hint) => hint.includes('/')), + true, + ); + expect( + 'POPULATION the scripts root is the one ROOTS entry the extractor cannot see, and it is the one declared', + ROOTS.filter((root) => !root.includes('/')).join(), + 'scripts', + ); + // The literal SPELLING is the whole mechanism: a value built from + // SCANNED_EXTENSIONS would keep every assertion above green and contribute + // ZERO hints. `check-watch-hint-literal` owns that rule fleet-wide. + let ownSource = null; + try { + ownSource = readFileSync(join(REPO_ROOT, 'scripts/check-whole-set-label-write.mjs'), 'utf8'); + } catch { + ownSource = null; + } + const declSites = ownSource === null + ? [] + : [...ownSource.matchAll(/\bconst\s+ROOT_DIR_WATCH_HINTS\s*=\s*([^;]*);/g)]; + expect( + 'POPULATION declared exactly once, as an array of quoted literals the text scan can read', + declSites.length === 1 + && ROOT_DIR_WATCH_HINTS.every((hint) => declSites[0][1].includes(`'${hint}'`)) + && !/[A-Za-z_$][\w$]*\s*\./.test(declSites[0][1]), + true, + ); + } + // The checked-in allowlist itself passes the reason rule. for (const [index, entry] of ALLOWLIST.entries()) { if (typeof entry.reason === 'string' && entry.reason.trim().length >= MIN_REASON_LENGTH) continue;