Skip to content

Commit bc54b26

Browse files
claude[bot]claude
andauthored
fix(scripts): the subtree-walking gates declare their watch hints, so dispatch-gates names them (#15341) (#15466)
* fix(scripts): the subtree-walking gates declare their watch hints (#15341) Sixteen gate families CI runs declared no path literal at all and walked a subtree seeded from a runtime constant, so dispatch-gates scored them `undetermined` for every card: absent from every dispatch brief and every `--commands` harvest while CI ran them on each pull request. Each gate now declares the population its own source really reads, under the rostered watch-hint idiom, with the coupling held in that gate's own self-test. Three families whose CI invocation is a fixture-only `--self-test` carry the `no-path-population` marker instead. `check:org-identifier` gains hints and its now-stale ROOT_WALK_RESIDUE_LEDGER row is deleted in the same commit. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk * fix(scripts): withdraw the wholesale-root hints refused by bare-root-worklist (#15341) `scripts/pm/bare-root-worklist.mjs` carries the recorded, measured triage for exactly this class, and its `--self-test` reds on a declaration that contradicts a recorded refusal. Eleven of the sixteen gates the card names are refused there by name: REFUSE-WIDE where the bare root is TRUE of the walk and refused for width, REFUSE-UNSPELLABLE where the population is a file-kind filter no subtree glob can spell. Two of those rows quote percentages for the exact spellings this branch had declared. Those eleven declarations are withdrawn and their gates left as they were. What stays is the half neither map refuses: two enumerated FILE populations at 100% precision, three families whose CI invocation is a fixture-only self-test and which now say so, and check:org-identifier, whose ROOT_WALK_RESIDUE_LEDGER row names this declaration as its remedy. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 347b777 commit bc54b26

7 files changed

Lines changed: 213 additions & 13 deletions

scripts/check-org-identifier.mjs

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,34 @@ import { maskComments } from './js-comment-mask.mjs';
275275
import { isEntrypoint } from './invoked-as.mjs';
276276

277277
const ROOTS = ['examples', 'apps', 'packages'];
278+
279+
/**
280+
* The population this gate enumerates, declared for
281+
* `scripts/pm/dispatch-gates.mjs`.
282+
*
283+
* `sourceFiles` runs `git ls-files -- examples apps packages` — three SUBTREES,
284+
* seeded from the `ROOTS` constant above. The derivation reads SOURCE TEXT and
285+
* `hintCovers` refuses a bare single-segment literal by design (accepting them
286+
* was priced at +139084 fabricated (gate, file) pairs, because these three words
287+
* are path COMPONENTS in dozens of gates that never read those roots), so this
288+
* gate declared nothing and was scored `undetermined` for EVERY card: it reached
289+
* no dispatch brief and no `--commands` harvest while CI ran it on every pull
290+
* request.
291+
*
292+
* ⛔ Not a whole-tree marker, and this gate is the reason that boundary is
293+
* written down. It was held OUT of the whole-tree bucket with a
294+
* `ROOT_WALK_RESIDUE_LEDGER` row precisely because the liveness predicate that
295+
* vouches for a whole-tree declaration sees a `git ls-files` call and cannot
296+
* tell a three-subtree enumeration apart from a repo-wide one — so a whole-tree
297+
* marker here would be exactly the mis-declaration that predicate cannot catch.
298+
* That ledger row named this declaration as the remedy; landing it clears the
299+
* row, and the two moves are coupled by construction (a stale exclusion reds).
300+
*
301+
* The self-test derives the coupling from `ROOTS` on both sides rather than
302+
* re-spelling it, so widening or renaming a root cannot leave this declaration
303+
* describing the old population.
304+
*/
305+
const ROOT_DIR_WATCH_HINTS = ['examples/**', 'apps/**', 'packages/**'];
278306
const EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.cts', '.mts'];
279307
const EXCLUDED = /(^|\/)(node_modules|dist|build|\.next|\.turbo)\//;
280308
// Dropped WHOLESALE: absence pins and fixtures that CONSTRUCT the removed
@@ -614,11 +642,12 @@ let selfTestReachedVerdict = false;
614642
// to find what stopped registering.
615643
const SELF_TEST_BATTERIES = Object.freeze({
616644
'check-org-identifier self-test': 31,
645+
'the dispatch-gates population declaration': 4,
617646
});
618647

619648
// DELETING an entry silences that battery's floor exactly as effectively as
620649
// zeroing it, so the roster's own size is pinned too.
621-
const SELF_TEST_BATTERY_FLOOR = 1;
650+
const SELF_TEST_BATTERY_FLOOR = 2;
622651

623652
// The key an assertion is filed under when no battery is open. It is not a
624653
// declared battery, so it reds by the same set difference rather than silently
@@ -750,6 +779,35 @@ check(() => {
750779
}
751780
});
752781

782+
// ── The dispatch-gates population declaration ────────────────────────────
783+
battery('the dispatch-gates population declaration');
784+
const declFail = (msg) => { console.error(` ✗ ${msg}`); failed++; };
785+
check(() => {
786+
if (!ROOTS.filter((r) => !r.includes('/')).every((r) => ROOT_DIR_WATCH_HINTS.includes(`${r}/**`))) {
787+
declFail('a separator-less ROOT is not declared in the subtree spelling — a bare root is refused '
788+
+ 'as too generic, so the hint extractor reads nothing and this gate returns to scoring '
789+
+ '`undetermined` for every card.');
790+
}
791+
});
792+
check(() => {
793+
if (!ROOT_DIR_WATCH_HINTS.every((h) => ROOTS.includes(h.replace(/\/\*+$/, '')))) {
794+
declFail('ROOT_DIR_WATCH_HINTS declares a root this gate does not enumerate — a declaration that '
795+
+ 'has drifted from the scan replaces a silent gate with a lying one.');
796+
}
797+
});
798+
check(() => {
799+
if (ROOTS.some((r) => ROOT_DIR_WATCH_HINTS.includes(r))) {
800+
declFail('the declared form is a ROOTS entry — the bare form is what `git ls-files` takes, and it '
801+
+ 'is exactly what the hint extractor cannot read.');
802+
}
803+
});
804+
check(() => {
805+
if (ROOT_DIR_WATCH_HINTS.length !== ROOTS.length) {
806+
declFail('the declaration is not one hint per enumerated root — a short list reads exactly like '
807+
+ 'the undetermined verdict it exists to leave.');
808+
}
809+
});
810+
753811
// ── The floor: every declared battery RAN, and ran its cases (#13489) ────
754812
//
755813
// Evaluated after every battery has had its chance and BEFORE the verdict, so

scripts/check-sdui-manifest.mjs

Lines changed: 62 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,19 +50,57 @@
5050
import { createHash } from 'node:crypto';
5151
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
5252
import { tmpdir } from 'node:os';
53-
import { dirname, join } from 'node:path';
53+
import { dirname, join, sep } from 'node:path';
5454
import { fileURLToPath } from 'node:url';
5555

5656
import { isEntrypoint } from './invoked-as.mjs';
5757

5858
const DEFAULT_ROOT = join(dirname(fileURLToPath(import.meta.url)), '..');
5959

60+
/** The three files `checkTree` opens, repo-relative. */
61+
const READ_PATHS = {
62+
artefact: 'sdui.manifest.json',
63+
record: join('scripts', 'sdui-manifest.record.json'),
64+
pin: '.objectui-sha',
65+
};
66+
67+
/**
68+
* The population this gate reads, declared for `scripts/pm/dispatch-gates.mjs`.
69+
*
70+
* `checkTree` opens exactly three files. None of them was visible to the
71+
* derivation: it reads SOURCE TEXT, and a literal with no separator is refused
72+
* by `hintCovers` as too generic (`sdui.manifest.json`), while `.objectui-sha`
73+
* is not one of the dot-prefixed names the extractor admits and the record path
74+
* is assembled with `join()`. So both of this gate's CI invocations — the scan
75+
* and its `--self-test` — were scored `undetermined` for EVERY card, absent from
76+
* every dispatch brief and every `--commands` harvest, while CI ran them on each
77+
* pull request. That cost is sharpest here: the cards that implicate this gate
78+
* are exactly the ones that move the objectui pin or regenerate the manifest.
79+
*
80+
* `ROOT_WATCH_HINTS` is the mixed-roots spelling of the idiom, which is what
81+
* this population is: two repo-ROOT files and one under `scripts/`. The
82+
* repo-root pair carries the `/**` suffix because a bare single-segment literal
83+
* is refused; the collapse reduces each one back to the single file it names and
84+
* to nothing else.
85+
*
86+
* ⛔ Not a whole-tree marker, and not `scripts/**`: three files are three files.
87+
*
88+
* The self-test derives the coupling from `READ_PATHS` — the same object
89+
* `checkTree` reads — so a moved or added read reds here rather than leaving the
90+
* declaration describing the old population.
91+
*/
92+
const ROOT_WATCH_HINTS = [
93+
'sdui.manifest.json/**',
94+
'scripts/sdui-manifest.record.json',
95+
'.objectui-sha/**',
96+
];
97+
6098
/** Gate one tree. Returns a list of problems; empty = green. */
6199
export function checkTree(root) {
62100
const problems = [];
63-
const artefactPath = join(root, 'sdui.manifest.json');
64-
const recordPath = join(root, 'scripts', 'sdui-manifest.record.json');
65-
const pinPath = join(root, '.objectui-sha');
101+
const artefactPath = join(root, READ_PATHS.artefact);
102+
const recordPath = join(root, READ_PATHS.record);
103+
const pinPath = join(root, READ_PATHS.pin);
66104

67105
if (!existsSync(artefactPath)) {
68106
problems.push(
@@ -247,6 +285,26 @@ function selfTest() {
247285
for (const p of problems) console.error(` ${p}`);
248286
}
249287
}
288+
// ── The dispatch-gates population declaration ──────────────────────────
289+
// Filed outside the cases table, deliberately: each table ROW is a declared
290+
// battery here, so an assertion added as a row would owe a roster entry for a
291+
// case that gates nothing about `checkTree`'s verdicts.
292+
const declFail = (message) => { console.error(`✗ self-test: ${message}`); failures++; };
293+
const readPosix = Object.values(READ_PATHS).map((f) => f.split(sep).join('/'));
294+
const declared = ROOT_WATCH_HINTS.map((h) => h.replace(/\/\*+$/, ''));
295+
if (!readPosix.every((f) => declared.includes(f))) {
296+
declFail('a file checkTree reads is not declared for dispatch-gates — an unreadable literal is how '
297+
+ 'this gate came to declare nothing at all, and it scores `undetermined` for every card again.');
298+
}
299+
if (!declared.every((h) => readPosix.includes(h))) {
300+
declFail('ROOT_WATCH_HINTS declares a path this gate does not read — a declaration that has drifted '
301+
+ 'from the reads replaces a silent gate with a lying one.');
302+
}
303+
if (!ROOT_WATCH_HINTS.filter((h) => !h.replace(/\/\*+$/, '').includes('/')).every((h) => h.endsWith('/**'))) {
304+
declFail('a repo-ROOT file is declared without the subtree suffix — a bare single-segment literal is '
305+
+ 'refused by hintCovers as too generic, so it would contribute no hint at all.');
306+
}
307+
250308
// ── The floor: every declared row RAN, and ran its case (#13489) ───────
251309
//
252310
// Evaluated after every row has had its chance and BEFORE the verdict, so the

scripts/check-tenant-chokepoint.mjs

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@
108108
*/
109109

110110
import { readFileSync, existsSync } from 'node:fs';
111-
import { join, relative } from 'node:path';
111+
import { join, relative, sep } from 'node:path';
112112
import { fileURLToPath } from 'node:url';
113113
import { requireDefaultExport } from './import-prerequisite.mjs';
114114
const ts = await requireDefaultExport('typescript', () => import('typescript'), import.meta.url);
@@ -124,6 +124,38 @@ const SCAN_FILES = [
124124
join('packages', 'drivers', 'driver-turso', 'src', 'turso-driver.ts'),
125125
];
126126

127+
/**
128+
* The population this gate reads, declared for `scripts/pm/dispatch-gates.mjs`.
129+
*
130+
* `SCAN_FILES` is assembled with `join()`, one SEGMENT per literal, so the
131+
* derivation — which reads SOURCE TEXT — saw a handful of bare words and no path
132+
* at all. This gate was scored `undetermined` for EVERY card: absent from every
133+
* dispatch brief and every `--commands` harvest, while CI ran it on each pull
134+
* request. Worse than the usual cost, because this gate's population is THREE
135+
* FILES: the cards that actually implicate it are rare and precisely nameable,
136+
* and it was reaching none of them.
137+
*
138+
* The spelling is `DECLARED_WATCH_HINTS` rather than the directory idiom: this
139+
* is an enumerated population, not a walked subtree, and each hint is a whole
140+
* repo-relative FILE path — which carries a separator, so `hintCovers` reads it
141+
* without a glob suffix.
142+
*
143+
* ⛔ Not `packages/drivers/**`, and not a whole-tree marker. Both would put this
144+
* gate on cards it does not read a byte for; a lead that is wrong on thousands
145+
* of files is worse than the silence it replaces.
146+
*
147+
* The self-test derives the coupling from `SCAN_FILES` on both sides rather than
148+
* re-spelling it, so a driver added to or removed from the family cannot leave
149+
* this declaration describing the old population. The POSIX normalisation is
150+
* load-bearing: `join()` yields a platform separator, and the declaration is
151+
* read as text by a scanner that only knows `/`.
152+
*/
153+
const DECLARED_WATCH_HINTS = [
154+
'packages/drivers/driver-sql/src/sql-driver.ts',
155+
'packages/drivers/driver-sqlite-wasm/src/sqlite-wasm-driver.ts',
156+
'packages/drivers/driver-turso/src/turso-driver.ts',
157+
];
158+
127159
/**
128160
* The floor a real scan must clear. A scan that silently stops matching reports
129161
* "clean" while reading nothing (#4690), and this gate's whole value is that it
@@ -522,6 +554,24 @@ function selfTest() {
522554
assert(builders.length === 0, 'the empty fixture must discover no builders');
523555
}
524556

557+
// ── the dispatch-gates population declaration ────────────────────────────
558+
const scannedPosix = SCAN_FILES.map((f) => f.split(sep).join('/'));
559+
assert(
560+
scannedPosix.every((f) => DECLARED_WATCH_HINTS.includes(f)),
561+
'every SCAN_FILES entry is declared for dispatch-gates — a join()-assembled path is invisible to '
562+
+ 'the hint extractor, which is how this gate came to declare nothing at all',
563+
);
564+
assert(
565+
DECLARED_WATCH_HINTS.every((h) => scannedPosix.includes(h)),
566+
'and nothing is declared that this gate does not read — a declaration that has drifted from the '
567+
+ 'scan replaces a silent gate with a lying one',
568+
);
569+
assert(
570+
DECLARED_WATCH_HINTS.every((h) => !h.includes('*')),
571+
'the declaration stays file-exact — a `packages/drivers/**` widening would name this gate on '
572+
+ 'cards it does not read a byte for',
573+
);
574+
525575
// ── The floor: every declared battery RAN, and ran its cases (#13489) ────
526576
//
527577
// Evaluated after every battery has had its chance and BEFORE the verdict, so

scripts/pm/dispatch-gates.mjs

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2341,13 +2341,6 @@ export function wholeTreePopulationRefusal(entry) {
23412341
* reds too: a stale exclusion is an exclusion nobody is measuring any more.
23422342
*/
23432343
export const ROOT_WALK_RESIDUE_LEDGER = [
2344-
[
2345-
'check:org-identifier',
2346-
'its enumeration is `git ls-files -- examples apps packages` (the ROOTS constant) — three SUBTREES, not the '
2347-
+ 'tree. The liveness predicate selects it on limb A and is documented as too weak to tell that apart, so a '
2348-
+ 'whole-tree marker here would be precisely the mis-declaration that predicate cannot catch. Its remedy is '
2349-
+ 'the ordinary ROOT_DIR_WATCH_HINTS declaration naming those three roots, after which it is MATCHED here.',
2350-
],
23512344
[
23522345
'scripts/check-console-intercept-disarm.mjs',
23532346
'its `scan(REPO_ROOT)` walks `workspacePackageDirs(root)` — every workspace PACKAGE ROOT\'s package.json and '

scripts/publish-smoke-pack.mjs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,18 @@
4040
* reading a copy that a gate keeps honest, rather than the thing itself.
4141
*/
4242

43+
// ## The dispatch-gates population
44+
//
45+
// `main()` shells out to `pnpm -r list` and `pnpm pack`; it opens no path this
46+
// file names, and it runs only from `scripts/publish-smoke.sh` during a release.
47+
// The family CI schedules on a pull request is `check:publish-smoke-pin`, which
48+
// is this file's `--self-test`: pure fixtures, no workspace, no network, no
49+
// tracked file. There is no path population for a card to implicate, so the
50+
// marker is the honest declaration -- and `packages/**` would be the costly
51+
// error, naming this family on thousands of cards for reads it never performs.
52+
//
53+
// 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
54+
4355
import { execFile } from 'node:child_process';
4456
import { mkdirSync, writeFileSync } from 'node:fs';
4557
import { resolve } from 'node:path';

scripts/run-with-stall-guard.mjs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,19 @@
182182
// Exit status: the child's own code when it finishes; 75 on a declared stall;
183183
// 1 when the child dies on a signal this guard did not send.
184184

185+
// ## The dispatch-gates population
186+
//
187+
// This is a RUNNER, not a scanner. Its reads are `/proc` (process state for the
188+
// stall verdict) and, when CI passes `--report-dir`, a runner temp directory of
189+
// diagnostic reports -- neither is in the repository, and no card can contain
190+
// either. The family CI schedules on a pull request is `check:stall-guard`,
191+
// which is this file's `--self-test`. So there is no path population to declare,
192+
// and the marker says so rather than leaving the family in the unexamined pile.
193+
// A card that edits THIS file is already led to `check:stall-guard-budget`,
194+
// which declares this path as part of its own population.
195+
//
196+
// 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
197+
185198
import { spawn } from 'node:child_process';
186199
import {
187200
createWriteStream,

scripts/sync-docs-image-tags.mjs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,22 @@
7575
// historical facts across three files with nothing downstream to complain, so
7676
// --self-test asserts a clean corpus is left BYTE-IDENTICAL, with no write at all.
7777

78+
// ## The dispatch-gates population, read rather than assumed
79+
//
80+
// This file is a REWRITER, and in rewrite mode it opens the three doc surfaces
81+
// plus `packages/cli/package.json` -- all of them inherited from
82+
// `check-docs-image-tag.mjs`, which declares them and is judged on them. But the
83+
// family CI schedules is `check:docs-image-tag-sync`, and both of its call sites
84+
// (lint.yml and release.yml) run `--self-test`, which works entirely inside a
85+
// `mkdtempSync` fixture tree and reads not one tracked file. So there is no
86+
// population for a card to implicate, and the honest declaration is the marker
87+
// rather than a hint set copied from the gate next door -- a hint here would
88+
// name this family on every `content/docs/**` card for reads its CI invocation
89+
// never performs. The SURFACES themselves are already declared where they are
90+
// read: `check:docs-image-tag` carries them.
91+
//
92+
// 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
93+
7894
import { readFileSync, writeFileSync, mkdirSync, mkdtempSync, rmSync, statSync, existsSync } from 'node:fs';
7995
import { tmpdir } from 'node:os';
8096
import { dirname, join } from 'node:path';

0 commit comments

Comments
 (0)