Skip to content

Commit 27875fc

Browse files
claude[bot]claude
andauthored
fix(pm): dispatch-gates names every CI-run gate whose source sweeps the repo root (#15344)
`check:driver-memory-census` counts a `vi.mock` of a frozen driver package as a module binding wherever in the tree it is written, and names no path literal in its source — so the dispatch derivation scored it `undetermined` for every card and no `--commands` harvest could contain it. A seat derived its family, ran 57 of them with 53 green, and CI's lint job then failed on the one gate the derivation could not offer. The gate's truthful population is the whole repository, so it now says so with the `whole-tree-population` marker the bucket already uses, vouched by the liveness predicate that reads its `git ls-files` enumeration. Fixing one gate is not the fix. `ROOT_WALK_RESIDUE_LEDGER` and its live `--self-test` case make the class computed: every family whose own source carries a recognised repo-root walk must declare whole-tree, declare path-less, be derivable by path, or hold a justified exclusion row. The next unnamed census gate reds in `check:pm-dispatch-gates` instead of in CI a cycle later. The wider residue was measured rather than guessed: of the 186 gate scripts CI runs, 44 are named by no derivation. The remainder are subtree walkers whose remedy is the ordinary ROOT_DIR_WATCH_HINTS declaration, one per-gate judgement each; that class is filed separately rather than swallowed by a forty-row table nobody would revisit. Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk Co-authored-by: Claude <noreply@anthropic.com>
1 parent a3fbd20 commit 27875fc

2 files changed

Lines changed: 225 additions & 0 deletions

File tree

scripts/check-driver-memory-census.mjs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,30 @@ function walk(dir, out = []) {
188188
* an ignore list of our own; the walk is a fallback for a checkout that is not a
189189
* git repository (a published tarball, an unpacked source archive) so the gate
190190
* degrades to "scans more" rather than to "scans nothing".
191+
*
192+
* ## Why this gate declares its population to the dispatch derivation (#15312)
193+
*
194+
* `scripts/pm/dispatch-gates.mjs` maps a card's file surface to the gate
195+
* families that watch it, so a seat can run locally what CI will run. This gate
196+
* names no path literal anywhere in its source, so that derivation scored it
197+
* `undetermined` for every card in the tree and it appeared in no seat's
198+
* runnable list. Measured cost: a PR added one `vi.mock('@objectstack/driver-memory')`
199+
* to a new test file, the seat derived and ran 57 families with 53 green, and
200+
* CI's Lint & Repo Gates then failed on THIS gate — a red the seat could not
201+
* have been told about, one cycle late.
202+
*
203+
* The truthful population is the whole repository, which is exactly what the
204+
* marker below says. `candidateFiles` enumerates every tracked `*.ts`,
205+
* `*.tsx`, `*.mts`, `*.cts` and every `package.json` in the tree, with a
206+
* repo-root walk as the non-git fallback; NO subtree narrows it, and a module
207+
* binding on the frozen package is a red wherever in the tree it is written.
208+
* ⛔ The remedy is the declaration and not a path literal: an "every file"
209+
* literal is what that tool's header prices as a fabricated lead on every card.
210+
*
211+
* A card that edits this gate reaches it by identity anyway; the marker is for
212+
* every OTHER card, which is all of them.
191213
*/
214+
// dispatch-gates: whole-tree-population -- `candidateFiles` is a `git ls-files` enumeration of every tracked *.ts/*.tsx/*.mts/*.cts and every package.json in the repository (repo-root walk as the non-git fallback), so no card's file surface can narrow this gate: a module binding on the frozen package is a red wherever in the tree it is written.
192215
function candidateFiles() {
193216
let files;
194217
try {

scripts/pm/dispatch-gates.mjs

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2217,6 +2217,122 @@ export function wholeTreePopulationRefusal(entry) {
22172217
return null;
22182218
}
22192219

2220+
/**
2221+
* The WHOLE-TREE RESIDUE ledger (#15312) — the families whose own source sweeps
2222+
* the repository root, that this derivation can place on NO card, and that are
2223+
* deliberately not in the whole-tree bucket above.
2224+
*
2225+
* ## The defect
2226+
*
2227+
* `check:driver-memory-census` counts a `vi.mock` of a frozen driver package as
2228+
* a module binding wherever in the tree it is written, and names no path
2229+
* literal anywhere in its source — so this derivation scored it `undetermined`
2230+
* for every card and no `--commands` harvest could contain it. A seat derived
2231+
* its family, ran 57 of them with 53 green, and CI's lint job then failed on
2232+
* the one gate the derivation could not offer. The gate was right; what was
2233+
* missing was a way for a seat to be TOLD about it before the push, which is
2234+
* exactly what the whole-tree channel above exists to be. It now declares.
2235+
*
2236+
* ⚠️ Fixing that one gate is not the fix. The question worth answering is
2237+
* "which gates does CI run that no derivation can name", mechanically, so the
2238+
* NEXT one reds here rather than in CI a cycle later. This table and the live
2239+
* case in `--self-test` are that answer for the class the card names.
2240+
*
2241+
* ## The population, and why the closure subtraction is load-bearing
2242+
*
2243+
* A family is IN it when three things hold at once: its own source carries a
2244+
* recognised repo-root walk (`repoRootWalkSpelling` — the same predicate that
2245+
* vouches for a whole-tree declaration), it declares NEITHER marker, and no
2246+
* path in the tree OUTSIDE the gate's own file closure can place it. That last
2247+
* subtraction is not a detail: every family matches the card that edits the
2248+
* gate itself, through the identity key, so counting that as "the derivation
2249+
* can name it" would score this whole population green on a card nobody files.
2250+
*
2251+
* ## Why this class and not "every gate CI runs"
2252+
*
2253+
* The wider question was MEASURED for this card rather than guessed at: on
2254+
* cd1f8ee96, of the 186 gate scripts CI runs, 44 are named by no derivation at
2255+
* all. The bulk of them are SUBTREE walkers — seeded at `packages`, `apps`,
2256+
* `examples` through a runtime constant no source scan reads — whose remedy is
2257+
* the ordinary `ROOT_DIR_WATCH_HINTS` declaration, one per-gate judgement each.
2258+
* A table that swallowed all 44 would be forty rows nobody revisits, which is
2259+
* the shape `artifactRosterLines` refuses for its own members. So this holds
2260+
* the class the card named, the rest is a filed follow-up, and the boundary is
2261+
* STATED here rather than left to be inferred from what the table happens to
2262+
* contain.
2263+
*
2264+
* ## Maintaining this table
2265+
*
2266+
* A member this table does not list reds `check:pm-dispatch-gates`, and there
2267+
* are exactly two honest repairs. If the gate really does read the whole tree,
2268+
* give it the `whole-tree-population` marker: it then leaves this population by
2269+
* DECLARING, which is the outcome this table exists to push toward. If it does
2270+
* not, add a row saying what it reads INSTEAD, so a reader can check the claim
2271+
* against the gate. ⛔ Never a row that only says "not whole-tree" — that is
2272+
* the reason-less opt-out both markers refuse, and it reads exactly like a
2273+
* placeholder nobody will revisit. A listed family that stops being a member
2274+
* reds too: a stale exclusion is an exclusion nobody is measuring any more.
2275+
*/
2276+
export const ROOT_WALK_RESIDUE_LEDGER = [
2277+
[
2278+
'check:org-identifier',
2279+
'its enumeration is `git ls-files -- examples apps packages` (the ROOTS constant) — three SUBTREES, not the '
2280+
+ 'tree. The liveness predicate selects it on limb A and is documented as too weak to tell that apart, so a '
2281+
+ 'whole-tree marker here would be precisely the mis-declaration that predicate cannot catch. Its remedy is '
2282+
+ 'the ordinary ROOT_DIR_WATCH_HINTS declaration naming those three roots, after which it is MATCHED here.',
2283+
],
2284+
[
2285+
'scripts/check-console-intercept-disarm.mjs',
2286+
'its `scan(REPO_ROOT)` walks `workspacePackageDirs(root)` — every workspace PACKAGE ROOT\'s package.json and '
2287+
+ 'vitest.config.*, read off pnpm-workspace.yaml. That is workspace-wide but it is not every file: a new test '
2288+
+ 'file under an existing package does not move it, a new PACKAGE does. Declaring the whole tree would put it '
2289+
+ 'on every card on a population it does not read.',
2290+
],
2291+
[
2292+
'scripts/check-console-intercept-disarm.mjs --self-test',
2293+
'the same gate file, reached through its self-test invocation; the reading above is the whole of it.',
2294+
],
2295+
[
2296+
'scripts/check-skill-frame-freshness.mjs --self-test',
2297+
'lint.yml runs the SELF-TEST HALF and never the scan — its step is named that, and the step comment states why '
2298+
+ '(the pnpm script would drag the scan in with it). The repo-root default parameter belongs to the scan half '
2299+
+ 'CI does not schedule, so a whole-tree row for this family would advertise work no workflow performs.',
2300+
],
2301+
[
2302+
'scripts/symbol-anchors.mjs --self-test',
2303+
'a shared grammar-and-extractor LIBRARY, invoked by CI only as its own self-test; its `git ls-files` runs over a '
2304+
+ 'corpus its caller passes in. The corpus walk it lends is exercised by check-adr-symbol-anchors.mjs, which '
2305+
+ 'declares ROOT_DIR_WATCH_HINTS = [docs/adr/**] and is placed by that.',
2306+
],
2307+
];
2308+
2309+
/** The ledger as a Map, keyed by the family key this derivation places. */
2310+
export const ROOT_WALK_RESIDUE_REASONS = new Map(ROOT_WALK_RESIDUE_LEDGER);
2311+
2312+
/**
2313+
* Why this family is an unnamed repo-root walker, or null when it is not one.
2314+
*
2315+
* Pure, and reading only what discovery already put on the entry plus the
2316+
* placement this derivation gave it — the same contract
2317+
* `wholeTreePopulationRefusal` above states, and for the same reason: the live
2318+
* sweep in `--self-test` and any future caller must not be able to disagree
2319+
* about what a member of this population IS.
2320+
*
2321+
* ⚠️ `placement` is the verdict from `placeFamily` over a probe card that
2322+
* EXCLUDES the gate's own file closure. Passing the raw whole-tree verdict
2323+
* instead returns `matched` for every family in the repo and empties this
2324+
* population silently — which is why the live case builds the probe rather than
2325+
* taking a convenient shortcut, and why this parameter is a verdict and not a
2326+
* path list (a caller cannot get the subtraction wrong in a way this function
2327+
* would then hide).
2328+
*/
2329+
export function unnamedRootWalk(entry, placement) {
2330+
if (!entry?.rootWalk) return null;
2331+
if (entry.wholeTreeReason || entry.noPopulationReason) return null;
2332+
if (placement === 'matched') return null;
2333+
return entry.rootWalk;
2334+
}
2335+
22202336
/**
22212337
* A family whose verdict CANNOT EXIST outside a workflow run, read from the
22222338
* gate's own source rather than from a roster of names (#14004).
@@ -15990,6 +16106,92 @@ function selfTest() {
1599016106
declaredWholeTree.every(([, e]) => placeFamily(e, [p]).verdict === 'always-runs')),
1599116107
);
1599216108

16109+
// ── The WHOLE-TREE RESIDUE (#15312) ───────────────────────────────────────
16110+
//
16111+
// The channel above says what a DECLARING family gets. This says what happens
16112+
// to a family that should have declared and did not — the case the card was
16113+
// filed on, where a gate CI runs appeared in no seat's runnable list and the
16114+
// seat learned about it from CI a cycle later. Every fixture case below names
16115+
// the limb it discriminates; the live case is the one that can go red on a
16116+
// gate added tomorrow, which is the whole point of the table it grades.
16117+
const rwEntry = (over = {}) => ({
16118+
rootWalk: REPO_ROOT_WALK_SPELLINGS[0].label,
16119+
wholeTreeReason: null,
16120+
noPopulationReason: null,
16121+
...over,
16122+
});
16123+
t(
16124+
'an undeclared, unplaceable repo-root walker IS a member, and comes back as the walk that made it one',
16125+
unnamedRootWalk(rwEntry(), 'undetermined') === REPO_ROOT_WALK_SPELLINGS[0].label,
16126+
);
16127+
t(
16128+
'a `silent` one is a member too — silent and undetermined are both "no card names it"',
16129+
unnamedRootWalk(rwEntry(), 'silent') === REPO_ROOT_WALK_SPELLINGS[0].label,
16130+
);
16131+
t(
16132+
'declaring the whole tree LEAVES the population — the repair this table exists to push toward',
16133+
unnamedRootWalk(rwEntry({ wholeTreeReason: 'sweeps git ls-files' }), 'undetermined') === null,
16134+
);
16135+
t(
16136+
'and so does declaring no path population — the opposite answer, but an examined one either way',
16137+
unnamedRootWalk(rwEntry({ noPopulationReason: 'the self-test is the whole run' }), 'undetermined') === null,
16138+
);
16139+
t(
16140+
'a family the derivation can place BY PATH is not a member: a seat is already told about it',
16141+
unnamedRootWalk(rwEntry(), 'matched') === null,
16142+
);
16143+
t(
16144+
'and neither is a gate whose source carries no repo-root walk at all — this population is one CLASS, not the whole residue',
16145+
unnamedRootWalk(rwEntry({ rootWalk: null }), 'undetermined') === null,
16146+
);
16147+
// LIVE, against the real tree, and the case the next unnamed census gate reds
16148+
// on. The probe card is every tracked file MINUS the family's own file
16149+
// closure: without the subtraction every family matches the card that edits
16150+
// the gate itself, through the identity key, and this whole population would
16151+
// score empty while reading like a pass.
16152+
{
16153+
const liveAll = [...liveTree.files];
16154+
const members = [];
16155+
for (const [check, entry] of liveDiscovery.byCheck) {
16156+
if (!entry.rootWalk || entry.wholeTreeReason || entry.noPopulationReason) continue;
16157+
const own = new Set(entry.files ?? []);
16158+
const probe = own.size ? liveAll.filter((f) => !own.has(f)) : liveAll;
16159+
const walk = unnamedRootWalk(entry, placeFamily(entry, probe).verdict);
16160+
if (walk) members.push(check);
16161+
}
16162+
const unlisted = members.filter((c) => !ROOT_WALK_RESIDUE_REASONS.has(c)).sort();
16163+
const stale = [...ROOT_WALK_RESIDUE_REASONS.keys()].filter((c) => !members.includes(c)).sort();
16164+
t(
16165+
'every gate CI runs whose own source sweeps the repo root is DECLARED whole-tree, declared path-less, ' +
16166+
'derivable by path, or a justified row in ROOT_WALK_RESIDUE_LEDGER' +
16167+
(unlisted.length ? ` — unlisted: ${unlisted.join(', ')}` : '') +
16168+
(stale.length ? ` — listed but no longer a member: ${stale.join(', ')}` : ''),
16169+
unlisted.length === 0 && stale.length === 0,
16170+
);
16171+
// The control that makes the case above an instrument. A sweep that silently
16172+
// found NOTHING — a renamed field, a discovery that stopped reading gate
16173+
// sources — reports "nothing unlisted" and is indistinguishable from a pass,
16174+
// which is #4690 pointed at this file's own guard. So the population has to
16175+
// be seen to be non-empty, and the gate the card was filed on has to be seen
16176+
// to have LEFT it by declaring rather than by going unmeasured.
16177+
t(
16178+
`control: the residue population is non-empty (${members.length} member(s)), so the case above is measuring something`,
16179+
members.length > 0,
16180+
);
16181+
t(
16182+
'control: the card\'s own specimen has left this population through the BUCKET, not through the ledger — ' +
16183+
'check:driver-memory-census declares whole-tree, is backed by its walk, and is in no exclusion row',
16184+
Boolean(liveDiscovery.byCheck.get('check:driver-memory-census')?.wholeTreeReason)
16185+
&& wholeTreePopulationRefusal(liveDiscovery.byCheck.get('check:driver-memory-census')) === null
16186+
&& !ROOT_WALK_RESIDUE_REASONS.has('check:driver-memory-census'),
16187+
);
16188+
t(
16189+
'every ledger row carries a reason that says what the gate reads INSTEAD — a reason-less exclusion is the ' +
16190+
'placeholder shape both markers refuse',
16191+
ROOT_WALK_RESIDUE_LEDGER.every(([, why]) => typeof why === 'string' && why.trim().length > 40),
16192+
);
16193+
}
16194+
1599316195
// ── The CI-MEASURED-ONLY shape (#14004) ───────────────────────────────────
1599416196
//
1599516197
// The two markers above are DECLARATIONS a gate carries. This one is the

0 commit comments

Comments
 (0)