From fff7ec59b398b3522918fa432835655428a48a89 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 4 Sep 2026 19:41:51 +0000 Subject: [PATCH] fix(devx): declare check:merge-driver's manifest population, and re-measure the bounded-subtree walk specimen MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two cards on the dispatch derivation, folded because both land in `scripts/pm/dispatch-gates.mjs`. Card relations are declared once in the PR body; this branch squashes, so no commit here carries a closing keyword. ## The manifest population (#15501) `git-merge-regen --self-test` refuses a generator with no recorded merge disposition, and the population that refusal sweeps is the MANIFESTS: the root `package.json` plus every workspace member's, read for their `gen:` / `check:` rows. What the family declared to `scripts/pm/dispatch-gates.mjs` was the artifact paths `scripts/regen-artifacts.mjs` carries, imported one level down — the generators ALREADY routed. So the one class of card the refusal exists to catch (a card that ADDS a generator, touching a manifest and a new `scripts/*.mjs`) was the one class the derivation could not name, and the gate fired a cycle late, in CI, on every card of that shape. Measured on 615fac3a0, before: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack -- package.json → 0 lines naming check:merge-driver and after: `pnpm check:merge-driver` is listed, for the root manifest and for a member manifest alike. The repair is the population declaration, in the idiom the derivation already supports and beside the read it describes — `ROOT_FILE_WATCH_HINTS` for the root manifest in the subtree spelling `hintCovers` accepts, `DECLARED_WATCH_HINTS` for the member manifests as full-path glob literals, both following `check-workspace-manifest-cycles.mjs`, which declares the identical population. No hand list of generator names (that ledger is `regen-artifacts.mjs`'s, and this gate already reconciles it two ways), and no self-test case edited to pass. A twelfth self-test callee, `reconcileManifestPopulation`, holds both declarations against the enumerator's LIVE answer in both directions — every member manifest covered, no declared pattern covering zero, the root literal's collapsed form equal to the path the gate opens for the root owner — with its roster entry and floor (11 → 12), since nothing else in the repo can redden when a declaration read by another tool goes stale. Two dispatch-gates self-test cases per direction, pinned against the LIVE tree through the same discovery pass `derive` runs: a change set touching the root or a member manifest derives the gate, an unrelated brand-new `scripts/*.mjs` does not, and an in-place ablation shows it is the manifest declaration doing it. ## The bounded-subtree walk specimen (#15510) Two sentences cited `check-self-test-workflow-commands.mjs` as the LIVE specimen for `repoRootWalkSpelling`'s negative direction. The outcome they report is re-measured and unchanged — the limbs select five of the six candidates, and that gate is selected by none — but the REASON was written as the shape of that gate's walk, and a gate can stop holding a walk without the census moving. The census sentence now states the population (a bounded subtree, `scripts/`), which is what the remedy turns on; its limb attribution is corrected too, measured: three by limb A, one by B, one by C, not "limbs A/B". The fixture-side comment now names two measured specimens at two different walk roots — `check-self-test-wired.mjs` (`walkScripts`, seeded at `scripts/`) and `check-spec-parsed-alias.mjs` (`walkZodFiles`, seeded at `packages/spec/src`) — and two LIVE cases read them off their real source, so the claim cannot rot back into prose pointing at a file with no walk in it. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk --- scripts/git-merge-regen.mjs | 179 ++++++++++++++++++++++++++++++++-- scripts/pm/dispatch-gates.mjs | 161 ++++++++++++++++++++++++++++-- 2 files changed, 324 insertions(+), 16 deletions(-) diff --git a/scripts/git-merge-regen.mjs b/scripts/git-merge-regen.mjs index 2eb532d34b..3aad5b89b6 100755 --- a/scripts/git-merge-regen.mjs +++ b/scripts/git-merge-regen.mjs @@ -76,6 +76,62 @@ import { import { blankAnchorLineNumbers } from './doc-line-anchors.mjs'; import { workspacePackages } from './workspace-enumerator.mjs'; +/** + * The ROOT manifest, in the SUBTREE spelling `scripts/pm/dispatch-gates.mjs` + * can match (#15501). + * + * `reconcileGenerators` and `reconcileScripts` read `gen:` / `check:` rows out + * of the MANIFESTS -- the root one, plus every workspace member's -- and that + * population was declared nowhere. What this family DID declare is the artifact + * paths `scripts/regen-artifacts.mjs` carries, imported one level down as its + * hints: the artifacts ALREADY routed. A card that ADDS a generator touches a + * manifest and a new `scripts/*.mjs`, and neither is in that population until + * the card lands the very row it is being asked to add. So the one class of + * change the "no recorded merge disposition" refusal exists to catch was the + * one class the dispatch derivation could not name, and the gate fired a cycle + * late, in CI, on every card of that shape. Measured on `615fac3a0`, before + * this declaration existed: + * + * node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack -- package.json + * -> 0 lines naming check:merge-driver + * + * ⛔ The fix is the POPULATION, never a hand list of generator names: that + * ledger is `scripts/regen-artifacts.mjs`'s and this gate already owns the + * two-way reconciliation over it. + * + * A bare `package.json` carries no path separator and `hintCovers` refuses it + * as too generic; the trailing `/` + `**` suffix collapses back to the literal + * filename and matches it exactly -- `check-workspace-manifest-cycles.mjs`'s + * idiom for `pnpm-workspace.yaml` and `check-turbo-task-graph.mjs`'s for + * `turbo.json`. `reconcileManifestPopulation` pins the exact string, because + * every other signal this gate emits stays green when it is reworded back. + */ +const ROOT_FILE_WATCH_HINTS = ['package.json/**']; + +/** + * The MEMBER manifests this gate opens -- one per workspace package, which is + * exactly the list `workspacePackages(REPO_ROOT)` hands `reconcileGenerators` + * -- spelled as full-path glob LITERALS, never built from a `SCAN_ROOT`-shaped + * constant (the bare-top-level-dir species `scripts/pm/bare-root-worklist.mjs` + * records as unjudged). The three roots and their spelling follow + * `check-workspace-manifest-cycles.mjs`, which declares the identical + * population for the identical reason, and they are held against the + * enumerator's LIVE answer in both directions by `reconcileManifestPopulation` + * below -- so a workspace root this repo grows cannot leave the declaration + * behind in silence. + * + * ⚠️ Each of these carries its glob in a NON-FINAL segment, so `hintCovers` + * judges it as a PATTERN: it reaches member manifests and nothing else, never + * the thousands of files under those roots. That is the distinction between + * this declaration and the whole-subtree widening + * `check-pnpm-filter-targets.mjs` refuses for its own population. + */ +const DECLARED_WATCH_HINTS = [ + 'packages/**/package.json', + 'apps/**/package.json', + 'examples/**/package.json', +]; + /** * The comparators a row's `mixed` field may name (#14064). * @@ -288,7 +344,7 @@ function drive(argv) { // ── Why the CALLEE NAME is the battery ── // // This file has no `selfTest()` entry function and no named section banners: -// the `--self-test` dispatch at the bottom invokes ELEVEN named callees, each +// the `--self-test` dispatch at the bottom invokes TWELVE named callees, each // printing its own line and returning a boolean. So the roster's unit is the // CALLEE, and its label is the one the SOURCE ALREADY CARRIES — the function's // own name. Nothing is invented and nothing is judged per comment, and a set @@ -303,10 +359,10 @@ function drive(argv) { // (PR #15271, `check-sdui-manifest`) makes a table row a battery. It does so // for a file whose SELF-TEST *is* the table: one literal table, one driving // loop over it, and a sink that writes only when a row fails. Here the table is -// a local of ONE callee among eleven, its rows are evaluated eagerly into +// a local of ONE callee among twelve, its rows are evaluated eagerly into // booleans before anything loops, and the callee already reduces them to a // single printed verdict of its own. Flooring those rows would floor one -// callee's internals while the other ten stayed at callee granularity — a +// callee's internals while the other eleven stayed at callee granularity — a // roster whose unit changes per entry. The rule: the battery is the unit the // DISPATCH names. // @@ -321,6 +377,7 @@ const SELF_TEST_BATTERIES = Object.freeze({ reconcileAttributeSemantics: 1, reconcileScripts: 1, reconcileGenerators: 1, + reconcileManifestPopulation: 1, reconcileUntrackedDispositions: 1, reconcileOwnership: 1, hookIsExecutable: 1, @@ -336,7 +393,7 @@ const SELF_TEST_BATTERIES = Object.freeze({ // key in the literal above, so the roster falls below this number; the // roster ↔ dispatch cross-check in the floor block is the other half, and it // names WHICH callee was listed twice. -const SELF_TEST_BATTERY_FLOOR = 11; +const SELF_TEST_BATTERY_FLOOR = 12; // The key a registration is filed under when a callee registers no name at all. // It is not a declared battery, so it reds by the same set difference rather @@ -346,7 +403,7 @@ const UNATTRIBUTED_BATTERY = '(no callee named)'; // The battery ledger, read by `batteryFloorFailures()` from the dispatch block // at the very bottom of this file. It is MODULE-level rather than local to a // self-test body because this file HAS no self-test body: the registrations -// happen inside eleven separate callees and the floor is read at the dispatch's +// happen inside twelve separate callees and the floor is read at the dispatch's // verdict site, so the ledger has to outlive every one of those frames. // // ⚠️ Named for the roster's role, deliberately NOT with a self-test spelling: @@ -358,7 +415,7 @@ const batterySeen = new Map(); /** * Record that a self-test callee RAN. * - * Called as the FIRST statement of each of the eleven callees the `--self-test` + * Called as the FIRST statement of each of the twelve callees the `--self-test` * dispatch invokes — above any early return, so a callee that bails out early * still reports that it ran, and the floor is never met by a frame that * returned before doing anything. @@ -371,7 +428,7 @@ function registerCase(name) { /** * The floor: every declared callee RAN (#13489). * - * Evaluated at the dispatch's verdict site — after all eleven callees have had + * Evaluated at the dispatch's verdict site — after all twelve callees have had * their chance and immediately before the success line — and reached only from * the `--self-test` branch, so a production merge-driver run never reads the * ledger at all. @@ -677,6 +734,111 @@ function reconcileGenerators() { return ok; } +/** + * The two watch-hint declarations above, held against the manifest population + * `reconcileGenerators` really reads (#15501). + * + * Nothing else in this repo can redden when they go wrong. They are read by + * ANOTHER tool entirely -- `extractWatchHints` in + * `scripts/pm/dispatch-gates.mjs` scans this file's SOURCE TEXT -- so a wrong, + * stale or re-computed declaration runs green here forever and pays itself out + * as a dev dispatched on a new-generator card with this gate missing from the + * brief, which is the whole failure this declaration was added to end. Same + * reason `check-pnpm-filter-targets.mjs` pins its own, and the same discipline + * `check-workspace-manifest-cycles.mjs` applies to the identical population. + * + * The population is derived through the SAME two functions the gate uses -- + * `workspacePackages` and `manifestFor` -- never re-spelled here, so a moved + * read cannot leave this reconciliation agreeing with a list nobody opens. + */ +function reconcileManifestPopulation() { + registerCase('reconcileManifestPopulation'); + + // `**` crosses separators; every other glob character stays within one + // segment. Local rather than shared for the reason `check-watch-hint-literal` + // gives for not importing the files it judges: a gate that pulled in the + // derivation to borrow one predicate would import 20k lines of another tool's + // module body -- and, here, hand this family every path literal in it. + const patternMatches = (pattern, path) => { + const segs = pattern.split('/'); + let rx = ''; + for (let i = 0; i < segs.length; i++) { + if (segs[i] === '**') { + rx += '(?:[^/]+/)*'; + continue; + } + rx += segs[i].replace(/[.+^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '[^/]*'); + if (i < segs.length - 1) rx += '/'; + } + return new RegExp(`^${rx}$`).test(path); + }; + + const problems = []; + const ok = (label, cond) => { + if (!cond) problems.push(label); + }; + + // ── The ROOT manifest, pinned as BYTES ── + // A reword back to the bare filename is invisible in every other signal: + // production green, CI green, and the only thing lost is that a card adding a + // `gen:` row to the root manifest can name this gate at all. + ok( + 'the root manifest is declared in the SUBTREE spelling (hintCovers refuses the bare filename as too generic)', + ROOT_FILE_WATCH_HINTS.join(',') === 'package.json/**', + ); + // …and the literal is tied to the real read rather than typed twice: the + // declaration's collapsed form must be the path `reconcileGenerators` opens + // for the root owner. + ok( + 'the collapsed root literal is the path this gate actually opens for the root owner', + ROOT_FILE_WATCH_HINTS[0].replace(/\/\*+$/, '') === manifestFor('.'), + ); + ok( + 'every declared entry carries a path separator', + [...ROOT_FILE_WATCH_HINTS, ...DECLARED_WATCH_HINTS].every((h) => h.includes('/')), + ); + + // ── The MEMBER manifests, against the enumerator's LIVE answer ── + // Both directions, mirroring `check-workspace-manifest-cycles.mjs`: a pattern + // covering nothing is a fabricated lead on every card, and a manifest no + // pattern covers is the undeclared read this gate would otherwise ship with. + let memberManifests; + try { + memberManifests = workspacePackages(REPO_ROOT).map((p) => manifestFor(p.dir)); + } catch (err) { + return fail(`the workspace could not be enumerated, so the declaration could not be reconciled: ${err?.message ?? err}`); + } + ok(`the enumerator finds member manifests to declare (${memberManifests.length})`, memberManifests.length > 0); + const undeclared = memberManifests.filter((m) => !DECLARED_WATCH_HINTS.some((h) => patternMatches(h, m))); + ok( + `every member manifest this gate opens is covered by a declared pattern (uncovered: ${undeclared.join(', ') || 'none'})`, + undeclared.length === 0, + ); + const empty = DECLARED_WATCH_HINTS.filter((h) => !memberManifests.some((m) => patternMatches(h, m))); + ok(`and no declared pattern covers zero of them (empty: ${empty.join(', ') || 'none'})`, empty.length === 0); + // The other direction on the ROOT: it is not a workspace member, so a member + // pattern that reached it would mean the two declarations had collapsed into + // one over-wide glob. + ok( + 'no member pattern reaches the root manifest — the root is declared separately because it is not a workspace member', + !DECLARED_WATCH_HINTS.some((h) => patternMatches(h, manifestFor('.'))), + ); + ok( + 'the pattern matcher crosses separators for `**` and does not run past the filename', + patternMatches('packages/**/package.json', 'packages/spec/package.json') && + patternMatches('packages/**/package.json', 'packages/plugins/plugin-auth/package.json') && + !patternMatches('packages/**/package.json', 'packages/spec/src/index.ts'), + ); + + if (problems.length) { + return fail(`watch-hint declaration(s) out of step with the manifest population:\n ${problems.join('\n ')}\n` + + ' ROOT_FILE_WATCH_HINTS / DECLARED_WATCH_HINTS at the top of this file declare the manifests\n' + + ' reconcileGenerators reads. Repair the DECLARATION, never this reconciliation.'); + } + console.log(`✓ the declared manifest population covers the root manifest and all ${memberManifests.length} member manifest(s)`); + return true; +} + /** * The `untracked: true` dispositions, held against git rather than against their own * prose (#13731). @@ -1140,7 +1302,7 @@ function endToEndMixed() { if (process.argv.includes('--self-test')) { console.log('git-merge-regen --self-test\n'); - // The eleven callees as a literal LIST rather than eleven bare calls, so the + // The twelve callees as a literal LIST rather than twelve bare calls, so the // names this block invokes are data the floor below can cross-check the // roster against, in both directions. The names are read off the function // declarations themselves (`fn.name`), so a renamed callee moves this list @@ -1150,6 +1312,7 @@ if (process.argv.includes('--self-test')) { reconcileAttributeSemantics, reconcileScripts, reconcileGenerators, + reconcileManifestPopulation, reconcileUntrackedDispositions, reconcileOwnership, hookIsExecutable, diff --git a/scripts/pm/dispatch-gates.mjs b/scripts/pm/dispatch-gates.mjs index 9f0667019b..f9dbdd690a 100644 --- a/scripts/pm/dispatch-gates.mjs +++ b/scripts/pm/dispatch-gates.mjs @@ -2210,11 +2210,22 @@ export function declaredWholeTreePopulation(scriptSource) { * for a new spelling is to EXTEND this list with a self-test case beside it, * never to route around the check. * - * Measured over the six candidate gates #14189 and #14325 nominated: limbs A/B - * select five, and `check-self-test-workflow-commands.mjs` — whose walk is - * seeded at `scripts/`, a bounded subtree — is selected by none of them, which - * is the reading of its source, not a coincidence. That gate's remedy is the - * ordinary subtree declaration, not this channel. + * Measured over the six candidate gates #14189 and #14325 nominated: the limbs + * select five — three by A (`check-nul-bytes`, `check-refd-timer-probe`, + * `check-closing-keyword-parity`), one by B (`check-watch-hint-literal`), one + * by C (`check-comment-mask-corpus`) — and `check-self-test-workflow-commands.mjs` + * is selected by none of them, which is the reading of its source, not a + * coincidence. Its population is a bounded subtree (`scripts/`), never the + * tree, so its remedy is the ordinary subtree declaration and not this channel. + * + * ⚠️ Re-derived rather than recalled (#15510), and the count is the durable + * half of that sentence: the ATTRIBUTION was stale. `check-comment-mask-corpus` + * moved from limb B to limb C when limb B was tightened to refuse a path BUILD + * (the note on limb B below records that tightening), and "limbs A/B select + * five" was left behind. The REASON the sixth is unselected is stale-prone for + * its own reason: it was the SHAPE of that gate's walk, and a gate can stop + * holding a walk without the census outcome moving at all. What is written + * above is the population, which is what the remedy turns on. */ export const REPO_ROOT_WALK_SPELLINGS = [ { @@ -14167,6 +14178,86 @@ function selfTest() { ); } + // ── The MANIFEST population of check:merge-driver (#15501) ──────────────── + // + // That family's `git-merge-regen --self-test` refuses a generator with no + // recorded merge disposition, and the population that refusal sweeps is the + // MANIFESTS — the root one plus every workspace member's, read for their + // `gen:` / `check:` rows. What the family declared HERE was the artifact + // paths `scripts/regen-artifacts.mjs` carries, imported one level down: the + // generators ALREADY routed. So the one class of card the refusal exists to + // catch — a card that ADDS a generator, touching a manifest and a new + // `scripts/*.mjs` — was the one class this derivation could not name, and the + // gate fired a cycle late, in CI, on every card of that shape. Measured + // before the repair, on a `package.json` change set: zero lines naming it. + // + // Pinned against the LIVE tree through the same discovery pass `derive` runs, + // for the reason the scripts/** case above states: a hand-built fixture could + // pass here while the real family stayed unnameable, which is the exact + // failure these cases exist to stop recurring. + const mdFamilies = discoverFamilies().byCheck; + const MERGE_DRIVER = 'check:merge-driver'; + const mdEntry = mdFamilies.get(MERGE_DRIVER); + t(`${MERGE_DRIVER} is discovered at all — the pins below mean nothing without this`, Boolean(mdEntry)); + // The declared halves, stripped for the ablation below. Spelled as a + // PREDICATE over the live hint set rather than as a copy of the declaration, + // so a hint respelled in `git-merge-regen.mjs` cannot leave a stale twin here + // that keeps the ablation passing. + const isManifestHint = (h) => h === 'package.json/**' || h.endsWith('/package.json'); + const mdStripped = mdEntry ? { ...mdEntry, hints: (mdEntry.hints ?? []).filter((h) => !isManifestHint(h)) } : null; + // The POSITIVE direction, one probe per declared half. The table is pinned to + // its own length first (#13799's floor recipe): a loop over an emptied table + // runs zero cases and prints nothing, which reads exactly like a pass. + const MD_MANIFEST_PROBES = [ + ['the ROOT manifest, where the measured CI red added its `gen:` row', 'package.json'], + ['a workspace MEMBER manifest', 'packages/plugins/plugin-auth/package.json'], + ]; + t('the manifest probe table still has both declared halves in it', MD_MANIFEST_PROBES.length === 2); + for (const [why, probe] of MD_MANIFEST_PROBES) { + const verdict = mdEntry ? classifyEntry(mdEntry, [probe]).verdict : null; + // The reading rides in the case NAME, never in a third argument: `t` takes + // two, so a detail passed beyond them is dropped on the floor — and a + // failing case whose reading went with it is a case nobody can act on. + t( + `a change set touching ${why} derives ${MERGE_DRIVER} — ${probe} => ${verdict}`, + verdict === 'matched', + ); + // …and it is the manifest declaration doing it. Without this half the case + // above could pass through any hint that happens to cover the probe — + // `packages/spec/package.json`, for one, was already reachable through the + // `packages/spec` literal, so a probe chosen there would have proved + // nothing at all. Both not-matched verdicts are accepted, spelled as an + // explicit pair so a NEW verdict value added later cannot slip through as + // a pass — the same reasoning as the scripts/** ablation above. + const residual = mdStripped ? classifyEntry(mdStripped, [probe]).verdict : null; + t( + `…and it is the manifest declaration doing it: strip it and ${probe} goes back to NOT MATCHED` + + ` (hints ${mdEntry?.hints?.length} -> ${mdStripped?.hints?.length}, residual ${residual})`, + // The length comparison is what stops this passing VACUOUSLY: with no + // manifest hint to remove, `mdStripped` is the entry itself and a + // not-matched verdict compared against itself reads as a pass. + Boolean(mdStripped) && + mdStripped.hints.length < (mdEntry.hints ?? []).length && + ['silent', 'undetermined'].includes(residual), + ); + } + // The NEGATIVE direction, and it is what keeps the declaration from being a + // whole-tree widening in disguise: an unrelated script is not newly derived. + // Stated as "the declaration moved NOTHING for it" rather than as a bare + // not-matched verdict — the manifest hints are the only thing that changed, + // so the two verdicts agreeing is the claim, and it stays true whatever the + // rest of the family's population does later. + const mdUnrelated = 'scripts/the-one-nobody-has-written-yet.mjs'; + const mdUnrelatedVerdict = mdEntry ? classifyEntry(mdEntry, [mdUnrelated]).verdict : null; + t( + `an unrelated brand-new scripts/*.mjs is NOT derived to ${MERGE_DRIVER} (${mdUnrelatedVerdict})`, + ['silent', 'undetermined'].includes(mdUnrelatedVerdict), + ); + t( + 'and the manifest declaration is what moved nothing for it — the same verdict with and without', + mdUnrelatedVerdict === (mdStripped ? classifyEntry(mdStripped, [mdUnrelated]).verdict : null), + ); + // ── The bare root this gate must NOT spell (#10875) ─────────────────────── // // `check:published-files` asks whether a published package ships a scripts/ @@ -15877,14 +15968,68 @@ function selfTest() { "nor does one whose --self-test body stages a fixture tree (the self-test is not the gate's work)", repoRootWalkSpelling("function selfTest() {\n execFileSync('git', ['ls-files', '-z'], { cwd: tmp });\n}\n") === null, ); - // The live specimen for the NEGATIVE direction: a walk seeded at a bounded - // subtree. check-self-test-workflow-commands.mjs is exactly this shape, and - // it is why #14325's census of six is a census of five here. + // The NEGATIVE direction: a walk seeded at a bounded subtree, which is why + // #14325's census of six is a census of five here. + // + // The fixture string below is data, not a read of any file, so it cannot go + // stale — but it also cannot show that the shape still EXISTS in this tree, + // and a specimen named in prose can rot while every case here stays green. + // That is exactly what happened to the name this comment used to carry + // (#15510): `check-self-test-workflow-commands.mjs` was cited as the live + // specimen and then had its walk removed, leaving a sentence pointing at a + // file with no walk in it. So the specimens below are MEASURED, and there + // are two of them at two different walk roots, so one file's repair cannot + // empty the claim again: + // + // scripts/check-self-test-wired.mjs `walkScripts(scriptsDir)`, seeded at + // `join(ROOT, 'scripts')` + // scripts/check-spec-parsed-alias.mjs `walkZodFiles(SPEC_SRC)`, seeded at + // `join(ROOT, 'packages/spec/src')` + // + // Both read `null` from this predicate on this tree — the reading is pinned + // LIVE two cases down, against their real source rather than against a + // string typed here. t( 'a walk seeded at a bounded subtree is not a repo-root walk', repoRootWalkSpelling("const scriptsDir = join(ROOT, 'scripts');\nconst files = walkScripts(scriptsDir);") === null, ); + // LIVE, on this tree: the two specimens the comment above names, read off + // their real source rather than typed here (#15510). This is the half a + // fixture cannot carry — that the shape the negative direction is written + // for still exists in this repo — and it is the half that went stale last + // time. Their walk ROOTS are named in the case text, so a reader who opens + // one is told what to look for; a file that has gone is NOT MEASURED rather + // than a quiet pass, per the convention at the top of this battery. + const SUBTREE_WALK_SPECIMENS = [ + ['scripts/check-self-test-wired.mjs', "walkScripts(scriptsDir), seeded at join(ROOT, 'scripts')"], + ['scripts/check-spec-parsed-alias.mjs', "walkZodFiles(SPEC_SRC), seeded at join(ROOT, 'packages/spec/src')"], + ]; + // The floor (#13799): a loop over an emptied table runs zero cases and reads + // exactly like a pass, and this table's whole purpose is to not be down to + // one name again. + t('the bounded-subtree specimen table still names two files at two walk roots', SUBTREE_WALK_SPECIMENS.length === 2); + for (const [file, walk] of SUBTREE_WALK_SPECIMENS) { + const abs = nodePath.join(ROOT, file); + if (!existsSync(abs)) { + unmeasurable( + `the bounded-subtree walk specimen ${file}`, + 'the file is not in this tree, so its source cannot be read — name a specimen that is, or say plainly that only the fixture backs this direction.', + ); + continue; + } + const src = readFileSync(abs, 'utf8'); + t( + `LIVE: ${file} still holds a bounded-subtree walk (${walk})`, + /walk[A-Za-z]*\(/.test(src), + ); + t( + `LIVE: …and this predicate reads ${file} as NOT a repo-root walk, the negative direction the fixture above stands for`, + repoRootWalkSpelling(src) === null, + ); + } + + // The refusals — the two contradictions the derivation must not resolve on // the gate's behalf. const wtLive = { wholeTreeReason: 'sweeps the tree', rootWalk: REPO_ROOT_WALK_SPELLINGS[0].label, noPopulationReason: null };