diff --git a/scripts/check-org-identifier.mjs b/scripts/check-org-identifier.mjs index 36df46c993..2c868bfb45 100644 --- a/scripts/check-org-identifier.mjs +++ b/scripts/check-org-identifier.mjs @@ -275,6 +275,34 @@ import { maskComments } from './js-comment-mask.mjs'; import { isEntrypoint } from './invoked-as.mjs'; const ROOTS = ['examples', 'apps', 'packages']; + +/** + * The population this gate enumerates, declared for + * `scripts/pm/dispatch-gates.mjs`. + * + * `sourceFiles` runs `git ls-files -- examples apps packages` — three SUBTREES, + * seeded from the `ROOTS` constant above. The derivation reads SOURCE TEXT and + * `hintCovers` refuses a bare single-segment literal by design (accepting them + * was priced at +139084 fabricated (gate, file) pairs, because these three words + * are path COMPONENTS in dozens of gates that never read those roots), so this + * gate declared nothing and was scored `undetermined` for EVERY card: it reached + * no dispatch brief and no `--commands` harvest while CI ran it on every pull + * request. + * + * ⛔ Not a whole-tree marker, and this gate is the reason that boundary is + * written down. It was held OUT of the whole-tree bucket with a + * `ROOT_WALK_RESIDUE_LEDGER` row precisely because the liveness predicate that + * vouches for a whole-tree declaration sees a `git ls-files` call and cannot + * tell a three-subtree enumeration apart from a repo-wide one — so a whole-tree + * marker here would be exactly the mis-declaration that predicate cannot catch. + * That ledger row named this declaration as the remedy; landing it clears the + * row, and the two moves are coupled by construction (a stale exclusion reds). + * + * The self-test derives the coupling from `ROOTS` on both sides rather than + * re-spelling it, so widening or renaming a root cannot leave this declaration + * describing the old population. + */ +const ROOT_DIR_WATCH_HINTS = ['examples/**', 'apps/**', 'packages/**']; const EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.cts', '.mts']; const EXCLUDED = /(^|\/)(node_modules|dist|build|\.next|\.turbo)\//; // Dropped WHOLESALE: absence pins and fixtures that CONSTRUCT the removed @@ -614,11 +642,12 @@ let selfTestReachedVerdict = false; // to find what stopped registering. const SELF_TEST_BATTERIES = Object.freeze({ 'check-org-identifier self-test': 31, + 'the dispatch-gates population declaration': 4, }); // DELETING an entry silences that battery's floor exactly as effectively as // zeroing it, so the roster's own size is pinned too. -const SELF_TEST_BATTERY_FLOOR = 1; +const SELF_TEST_BATTERY_FLOOR = 2; // 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 @@ -750,6 +779,35 @@ check(() => { } }); + // ── The dispatch-gates population declaration ──────────────────────────── + battery('the dispatch-gates population declaration'); + const declFail = (msg) => { console.error(` ✗ ${msg}`); failed++; }; +check(() => { + if (!ROOTS.filter((r) => !r.includes('/')).every((r) => ROOT_DIR_WATCH_HINTS.includes(`${r}/**`))) { + declFail('a separator-less ROOT is not declared in the subtree spelling — a bare root is refused ' + + 'as too generic, so the hint extractor reads nothing and this gate returns to scoring ' + + '`undetermined` for every card.'); + } +}); +check(() => { + if (!ROOT_DIR_WATCH_HINTS.every((h) => ROOTS.includes(h.replace(/\/\*+$/, '')))) { + declFail('ROOT_DIR_WATCH_HINTS declares a root this gate does not enumerate — a declaration that ' + + 'has drifted from the scan replaces a silent gate with a lying one.'); + } +}); +check(() => { + if (ROOTS.some((r) => ROOT_DIR_WATCH_HINTS.includes(r))) { + declFail('the declared form is a ROOTS entry — the bare form is what `git ls-files` takes, and it ' + + 'is exactly what the hint extractor cannot read.'); + } +}); +check(() => { + if (ROOT_DIR_WATCH_HINTS.length !== ROOTS.length) { + declFail('the declaration is not one hint per enumerated root — a short list reads exactly like ' + + 'the undetermined verdict it exists to leave.'); + } +}); + // ── The floor: every declared battery RAN, and ran its cases (#13489) ──── // // Evaluated after every battery has had its chance and BEFORE the verdict, so diff --git a/scripts/check-sdui-manifest.mjs b/scripts/check-sdui-manifest.mjs index a6dbac4d96..9e29c9d2c3 100644 --- a/scripts/check-sdui-manifest.mjs +++ b/scripts/check-sdui-manifest.mjs @@ -50,19 +50,57 @@ import { createHash } from 'node:crypto'; import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; -import { dirname, join } from 'node:path'; +import { dirname, join, sep } from 'node:path'; import { fileURLToPath } from 'node:url'; import { isEntrypoint } from './invoked-as.mjs'; const DEFAULT_ROOT = join(dirname(fileURLToPath(import.meta.url)), '..'); +/** The three files `checkTree` opens, repo-relative. */ +const READ_PATHS = { + artefact: 'sdui.manifest.json', + record: join('scripts', 'sdui-manifest.record.json'), + pin: '.objectui-sha', +}; + +/** + * The population this gate reads, declared for `scripts/pm/dispatch-gates.mjs`. + * + * `checkTree` opens exactly three files. None of them was visible to the + * derivation: it reads SOURCE TEXT, and a literal with no separator is refused + * by `hintCovers` as too generic (`sdui.manifest.json`), while `.objectui-sha` + * is not one of the dot-prefixed names the extractor admits and the record path + * is assembled with `join()`. So both of this gate's CI invocations — the scan + * and its `--self-test` — were scored `undetermined` for EVERY card, absent from + * every dispatch brief and every `--commands` harvest, while CI ran them on each + * pull request. That cost is sharpest here: the cards that implicate this gate + * are exactly the ones that move the objectui pin or regenerate the manifest. + * + * `ROOT_WATCH_HINTS` is the mixed-roots spelling of the idiom, which is what + * this population is: two repo-ROOT files and one under `scripts/`. The + * repo-root pair carries the `/**` suffix because a bare single-segment literal + * is refused; the collapse reduces each one back to the single file it names and + * to nothing else. + * + * ⛔ Not a whole-tree marker, and not `scripts/**`: three files are three files. + * + * The self-test derives the coupling from `READ_PATHS` — the same object + * `checkTree` reads — so a moved or added read reds here rather than leaving the + * declaration describing the old population. + */ +const ROOT_WATCH_HINTS = [ + 'sdui.manifest.json/**', + 'scripts/sdui-manifest.record.json', + '.objectui-sha/**', +]; + /** Gate one tree. Returns a list of problems; empty = green. */ export function checkTree(root) { const problems = []; - const artefactPath = join(root, 'sdui.manifest.json'); - const recordPath = join(root, 'scripts', 'sdui-manifest.record.json'); - const pinPath = join(root, '.objectui-sha'); + const artefactPath = join(root, READ_PATHS.artefact); + const recordPath = join(root, READ_PATHS.record); + const pinPath = join(root, READ_PATHS.pin); if (!existsSync(artefactPath)) { problems.push( @@ -247,6 +285,26 @@ function selfTest() { for (const p of problems) console.error(` ${p}`); } } + // ── The dispatch-gates population declaration ────────────────────────── + // Filed outside the cases table, deliberately: each table ROW is a declared + // battery here, so an assertion added as a row would owe a roster entry for a + // case that gates nothing about `checkTree`'s verdicts. + const declFail = (message) => { console.error(`✗ self-test: ${message}`); failures++; }; + const readPosix = Object.values(READ_PATHS).map((f) => f.split(sep).join('/')); + const declared = ROOT_WATCH_HINTS.map((h) => h.replace(/\/\*+$/, '')); + if (!readPosix.every((f) => declared.includes(f))) { + declFail('a file checkTree reads is not declared for dispatch-gates — an unreadable literal is how ' + + 'this gate came to declare nothing at all, and it scores `undetermined` for every card again.'); + } + if (!declared.every((h) => readPosix.includes(h))) { + declFail('ROOT_WATCH_HINTS declares a path this gate does not read — a declaration that has drifted ' + + 'from the reads replaces a silent gate with a lying one.'); + } + if (!ROOT_WATCH_HINTS.filter((h) => !h.replace(/\/\*+$/, '').includes('/')).every((h) => h.endsWith('/**'))) { + declFail('a repo-ROOT file is declared without the subtree suffix — a bare single-segment literal is ' + + 'refused by hintCovers as too generic, so it would contribute no hint at all.'); + } + // ── The floor: every declared row RAN, and ran its case (#13489) ─────── // // Evaluated after every row has had its chance and BEFORE the verdict, so the diff --git a/scripts/check-tenant-chokepoint.mjs b/scripts/check-tenant-chokepoint.mjs index f74cedbfe7..b2448f4f49 100644 --- a/scripts/check-tenant-chokepoint.mjs +++ b/scripts/check-tenant-chokepoint.mjs @@ -108,7 +108,7 @@ */ import { readFileSync, existsSync } from 'node:fs'; -import { join, relative } from 'node:path'; +import { join, relative, sep } from 'node:path'; import { fileURLToPath } from 'node:url'; import { requireDefaultExport } from './import-prerequisite.mjs'; const ts = await requireDefaultExport('typescript', () => import('typescript'), import.meta.url); @@ -124,6 +124,38 @@ const SCAN_FILES = [ join('packages', 'drivers', 'driver-turso', 'src', 'turso-driver.ts'), ]; +/** + * The population this gate reads, declared for `scripts/pm/dispatch-gates.mjs`. + * + * `SCAN_FILES` is assembled with `join()`, one SEGMENT per literal, so the + * derivation — which reads SOURCE TEXT — saw a handful of bare words and no path + * at all. This gate was scored `undetermined` for EVERY card: absent from every + * dispatch brief and every `--commands` harvest, while CI ran it on each pull + * request. Worse than the usual cost, because this gate's population is THREE + * FILES: the cards that actually implicate it are rare and precisely nameable, + * and it was reaching none of them. + * + * The spelling is `DECLARED_WATCH_HINTS` rather than the directory idiom: this + * is an enumerated population, not a walked subtree, and each hint is a whole + * repo-relative FILE path — which carries a separator, so `hintCovers` reads it + * without a glob suffix. + * + * ⛔ Not `packages/drivers/**`, and not a whole-tree marker. Both would put this + * gate on cards it does not read a byte for; a lead that is wrong on thousands + * of files is worse than the silence it replaces. + * + * The self-test derives the coupling from `SCAN_FILES` on both sides rather than + * re-spelling it, so a driver added to or removed from the family cannot leave + * this declaration describing the old population. The POSIX normalisation is + * load-bearing: `join()` yields a platform separator, and the declaration is + * read as text by a scanner that only knows `/`. + */ +const DECLARED_WATCH_HINTS = [ + 'packages/drivers/driver-sql/src/sql-driver.ts', + 'packages/drivers/driver-sqlite-wasm/src/sqlite-wasm-driver.ts', + 'packages/drivers/driver-turso/src/turso-driver.ts', +]; + /** * The floor a real scan must clear. A scan that silently stops matching reports * "clean" while reading nothing (#4690), and this gate's whole value is that it @@ -522,6 +554,24 @@ function selfTest() { assert(builders.length === 0, 'the empty fixture must discover no builders'); } + // ── the dispatch-gates population declaration ──────────────────────────── + const scannedPosix = SCAN_FILES.map((f) => f.split(sep).join('/')); + assert( + scannedPosix.every((f) => DECLARED_WATCH_HINTS.includes(f)), + 'every SCAN_FILES entry is declared for dispatch-gates — a join()-assembled path is invisible to ' + + 'the hint extractor, which is how this gate came to declare nothing at all', + ); + assert( + DECLARED_WATCH_HINTS.every((h) => scannedPosix.includes(h)), + 'and nothing is declared that this gate does not read — a declaration that has drifted from the ' + + 'scan replaces a silent gate with a lying one', + ); + assert( + DECLARED_WATCH_HINTS.every((h) => !h.includes('*')), + 'the declaration stays file-exact — a `packages/drivers/**` widening would name this gate on ' + + 'cards it does not read a byte for', + ); + // ── The floor: every declared battery RAN, and ran its cases (#13489) ──── // // Evaluated after every battery has had its chance and BEFORE the verdict, so diff --git a/scripts/pm/dispatch-gates.mjs b/scripts/pm/dispatch-gates.mjs index 79b4a2e508..9f0667019b 100644 --- a/scripts/pm/dispatch-gates.mjs +++ b/scripts/pm/dispatch-gates.mjs @@ -2341,13 +2341,6 @@ export function wholeTreePopulationRefusal(entry) { * reds too: a stale exclusion is an exclusion nobody is measuring any more. */ export const ROOT_WALK_RESIDUE_LEDGER = [ - [ - 'check:org-identifier', - 'its enumeration is `git ls-files -- examples apps packages` (the ROOTS constant) — three SUBTREES, not the ' - + 'tree. The liveness predicate selects it on limb A and is documented as too weak to tell that apart, so a ' - + 'whole-tree marker here would be precisely the mis-declaration that predicate cannot catch. Its remedy is ' - + 'the ordinary ROOT_DIR_WATCH_HINTS declaration naming those three roots, after which it is MATCHED here.', - ], [ 'scripts/check-console-intercept-disarm.mjs', 'its `scan(REPO_ROOT)` walks `workspacePackageDirs(root)` — every workspace PACKAGE ROOT\'s package.json and ' diff --git a/scripts/publish-smoke-pack.mjs b/scripts/publish-smoke-pack.mjs index 15b1e76c21..c2a7c98883 100644 --- a/scripts/publish-smoke-pack.mjs +++ b/scripts/publish-smoke-pack.mjs @@ -40,6 +40,18 @@ * reading a copy that a gate keeps honest, rather than the thing itself. */ +// ## The dispatch-gates population +// +// `main()` shells out to `pnpm -r list` and `pnpm pack`; it opens no path this +// file names, and it runs only from `scripts/publish-smoke.sh` during a release. +// The family CI schedules on a pull request is `check:publish-smoke-pin`, which +// is this file's `--self-test`: pure fixtures, no workspace, no network, no +// tracked file. There is no path population for a card to implicate, so the +// marker is the honest declaration -- and `packages/**` would be the costly +// error, naming this family on thousands of cards for reads it never performs. +// +// dispatch-gates: no-path-population -- check:publish-smoke-pin runs this packer's --self-test against in-memory fixtures; the workspace enumeration and the pack itself belong to the release smoke, which no pull request schedules + import { execFile } from 'node:child_process'; import { mkdirSync, writeFileSync } from 'node:fs'; import { resolve } from 'node:path'; diff --git a/scripts/run-with-stall-guard.mjs b/scripts/run-with-stall-guard.mjs index 7ac9a686a9..52acfd5608 100644 --- a/scripts/run-with-stall-guard.mjs +++ b/scripts/run-with-stall-guard.mjs @@ -182,6 +182,19 @@ // Exit status: the child's own code when it finishes; 75 on a declared stall; // 1 when the child dies on a signal this guard did not send. +// ## The dispatch-gates population +// +// This is a RUNNER, not a scanner. Its reads are `/proc` (process state for the +// stall verdict) and, when CI passes `--report-dir`, a runner temp directory of +// diagnostic reports -- neither is in the repository, and no card can contain +// either. The family CI schedules on a pull request is `check:stall-guard`, +// which is this file's `--self-test`. So there is no path population to declare, +// and the marker says so rather than leaving the family in the unexamined pile. +// A card that edits THIS file is already led to `check:stall-guard-budget`, +// which declares this path as part of its own population. +// +// dispatch-gates: no-path-population -- this is a process runner: it reads /proc and, when given --report-dir, a runner temp dir, never a file in the repository; check:stall-guard is its --self-test + import { spawn } from 'node:child_process'; import { createWriteStream, diff --git a/scripts/sync-docs-image-tags.mjs b/scripts/sync-docs-image-tags.mjs index 9649967524..8a3edd694b 100644 --- a/scripts/sync-docs-image-tags.mjs +++ b/scripts/sync-docs-image-tags.mjs @@ -75,6 +75,22 @@ // historical facts across three files with nothing downstream to complain, so // --self-test asserts a clean corpus is left BYTE-IDENTICAL, with no write at all. +// ## The dispatch-gates population, read rather than assumed +// +// This file is a REWRITER, and in rewrite mode it opens the three doc surfaces +// plus `packages/cli/package.json` -- all of them inherited from +// `check-docs-image-tag.mjs`, which declares them and is judged on them. But the +// family CI schedules is `check:docs-image-tag-sync`, and both of its call sites +// (lint.yml and release.yml) run `--self-test`, which works entirely inside a +// `mkdtempSync` fixture tree and reads not one tracked file. So there is no +// population for a card to implicate, and the honest declaration is the marker +// rather than a hint set copied from the gate next door -- a hint here would +// name this family on every `content/docs/**` card for reads its CI invocation +// never performs. The SURFACES themselves are already declared where they are +// read: `check:docs-image-tag` carries them. +// +// dispatch-gates: no-path-population -- both CI call sites (lint.yml, release.yml) run this rewriter's --self-test, which works inside a mkdtemp fixture tree and reads no tracked file; the doc surfaces and the version source belong to check:docs-image-tag, which declares them + import { readFileSync, writeFileSync, mkdirSync, mkdtempSync, rmSync, statSync, existsSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { dirname, join } from 'node:path';