Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions scripts/check-driver-memory-census.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,30 @@ function walk(dir, out = []) {
* an ignore list of our own; the walk is a fallback for a checkout that is not a
* git repository (a published tarball, an unpacked source archive) so the gate
* degrades to "scans more" rather than to "scans nothing".
*
* ## Why this gate declares its population to the dispatch derivation (#15312)
*
* `scripts/pm/dispatch-gates.mjs` maps a card's file surface to the gate
* families that watch it, so a seat can run locally what CI will run. This gate
* names no path literal anywhere in its source, so that derivation scored it
* `undetermined` for every card in the tree and it appeared in no seat's
* runnable list. Measured cost: a PR added one `vi.mock('@objectstack/driver-memory')`
* to a new test file, the seat derived and ran 57 families with 53 green, and
* CI's Lint & Repo Gates then failed on THIS gate — a red the seat could not
* have been told about, one cycle late.
*
* The truthful population is the whole repository, which is exactly what the
* marker below says. `candidateFiles` enumerates every tracked `*.ts`,
* `*.tsx`, `*.mts`, `*.cts` and every `package.json` in the tree, with a
* repo-root walk as the non-git fallback; NO subtree narrows it, and a module
* binding on the frozen package is a red wherever in the tree it is written.
* ⛔ The remedy is the declaration and not a path literal: an "every file"
* literal is what that tool's header prices as a fabricated lead on every card.
*
* A card that edits this gate reaches it by identity anyway; the marker is for
* every OTHER card, which is all of them.
*/
// 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.
function candidateFiles() {
let files;
try {
Expand Down
202 changes: 202 additions & 0 deletions scripts/pm/dispatch-gates.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2217,6 +2217,122 @@ export function wholeTreePopulationRefusal(entry) {
return null;
}

/**
* The WHOLE-TREE RESIDUE ledger (#15312) — the families whose own source sweeps
* the repository root, that this derivation can place on NO card, and that are
* deliberately not in the whole-tree bucket above.
*
* ## The defect
*
* `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 anywhere in its source — so this 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 was right; what was
* missing was a way for a seat to be TOLD about it before the push, which is
* exactly what the whole-tree channel above exists to be. It now declares.
*
* ⚠️ Fixing that one gate is not the fix. The question worth answering is
* "which gates does CI run that no derivation can name", mechanically, so the
* NEXT one reds here rather than in CI a cycle later. This table and the live
* case in `--self-test` are that answer for the class the card names.
*
* ## The population, and why the closure subtraction is load-bearing
*
* A family is IN it when three things hold at once: its own source carries a
* recognised repo-root walk (`repoRootWalkSpelling` — the same predicate that
* vouches for a whole-tree declaration), it declares NEITHER marker, and no
* path in the tree OUTSIDE the gate's own file closure can place it. That last
* subtraction is not a detail: every family matches the card that edits the
* gate itself, through the identity key, so counting that as "the derivation
* can name it" would score this whole population green on a card nobody files.
*
* ## Why this class and not "every gate CI runs"
*
* The wider question was MEASURED for this card rather than guessed at: on
* cd1f8ee96, of the 186 gate scripts CI runs, 44 are named by no derivation at
* all. The bulk of them are SUBTREE walkers — seeded at `packages`, `apps`,
* `examples` through a runtime constant no source scan reads — whose remedy is
* the ordinary `ROOT_DIR_WATCH_HINTS` declaration, one per-gate judgement each.
* A table that swallowed all 44 would be forty rows nobody revisits, which is
* the shape `artifactRosterLines` refuses for its own members. So this holds
* the class the card named, the rest is a filed follow-up, and the boundary is
* STATED here rather than left to be inferred from what the table happens to
* contain.
*
* ## Maintaining this table
*
* A member this table does not list reds `check:pm-dispatch-gates`, and there
* are exactly two honest repairs. If the gate really does read the whole tree,
* give it the `whole-tree-population` marker: it then leaves this population by
* DECLARING, which is the outcome this table exists to push toward. If it does
* not, add a row saying what it reads INSTEAD, so a reader can check the claim
* against the gate. ⛔ Never a row that only says "not whole-tree" — that is
* the reason-less opt-out both markers refuse, and it reads exactly like a
* placeholder nobody will revisit. A listed family that stops being a member
* 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 '
+ 'vitest.config.*, read off pnpm-workspace.yaml. That is workspace-wide but it is not every file: a new test '
+ 'file under an existing package does not move it, a new PACKAGE does. Declaring the whole tree would put it '
+ 'on every card on a population it does not read.',
],
[
'scripts/check-console-intercept-disarm.mjs --self-test',
'the same gate file, reached through its self-test invocation; the reading above is the whole of it.',
],
[
'scripts/check-skill-frame-freshness.mjs --self-test',
'lint.yml runs the SELF-TEST HALF and never the scan — its step is named that, and the step comment states why '
+ '(the pnpm script would drag the scan in with it). The repo-root default parameter belongs to the scan half '
+ 'CI does not schedule, so a whole-tree row for this family would advertise work no workflow performs.',
],
[
'scripts/symbol-anchors.mjs --self-test',
'a shared grammar-and-extractor LIBRARY, invoked by CI only as its own self-test; its `git ls-files` runs over a '
+ 'corpus its caller passes in. The corpus walk it lends is exercised by check-adr-symbol-anchors.mjs, which '
+ 'declares ROOT_DIR_WATCH_HINTS = [docs/adr/**] and is placed by that.',
],
];

/** The ledger as a Map, keyed by the family key this derivation places. */
export const ROOT_WALK_RESIDUE_REASONS = new Map(ROOT_WALK_RESIDUE_LEDGER);

/**
* Why this family is an unnamed repo-root walker, or null when it is not one.
*
* Pure, and reading only what discovery already put on the entry plus the
* placement this derivation gave it — the same contract
* `wholeTreePopulationRefusal` above states, and for the same reason: the live
* sweep in `--self-test` and any future caller must not be able to disagree
* about what a member of this population IS.
*
* ⚠️ `placement` is the verdict from `placeFamily` over a probe card that
* EXCLUDES the gate's own file closure. Passing the raw whole-tree verdict
* instead returns `matched` for every family in the repo and empties this
* population silently — which is why the live case builds the probe rather than
* taking a convenient shortcut, and why this parameter is a verdict and not a
* path list (a caller cannot get the subtraction wrong in a way this function
* would then hide).
*/
export function unnamedRootWalk(entry, placement) {
if (!entry?.rootWalk) return null;
if (entry.wholeTreeReason || entry.noPopulationReason) return null;
if (placement === 'matched') return null;
return entry.rootWalk;
}

/**
* A family whose verdict CANNOT EXIST outside a workflow run, read from the
* gate's own source rather than from a roster of names (#14004).
Expand Down Expand Up @@ -15989,6 +16105,92 @@ function selfTest() {
declaredWholeTree.every(([, e]) => placeFamily(e, [p]).verdict === 'always-runs')),
);

// ── The WHOLE-TREE RESIDUE (#15312) ───────────────────────────────────────
//
// The channel above says what a DECLARING family gets. This says what happens
// to a family that should have declared and did not — the case the card was
// filed on, where a gate CI runs appeared in no seat's runnable list and the
// seat learned about it from CI a cycle later. Every fixture case below names
// the limb it discriminates; the live case is the one that can go red on a
// gate added tomorrow, which is the whole point of the table it grades.
const rwEntry = (over = {}) => ({
rootWalk: REPO_ROOT_WALK_SPELLINGS[0].label,
wholeTreeReason: null,
noPopulationReason: null,
...over,
});
t(
'an undeclared, unplaceable repo-root walker IS a member, and comes back as the walk that made it one',
unnamedRootWalk(rwEntry(), 'undetermined') === REPO_ROOT_WALK_SPELLINGS[0].label,
);
t(
'a `silent` one is a member too — silent and undetermined are both "no card names it"',
unnamedRootWalk(rwEntry(), 'silent') === REPO_ROOT_WALK_SPELLINGS[0].label,
);
t(
'declaring the whole tree LEAVES the population — the repair this table exists to push toward',
unnamedRootWalk(rwEntry({ wholeTreeReason: 'sweeps git ls-files' }), 'undetermined') === null,
);
t(
'and so does declaring no path population — the opposite answer, but an examined one either way',
unnamedRootWalk(rwEntry({ noPopulationReason: 'the self-test is the whole run' }), 'undetermined') === null,
);
t(
'a family the derivation can place BY PATH is not a member: a seat is already told about it',
unnamedRootWalk(rwEntry(), 'matched') === null,
);
t(
'and neither is a gate whose source carries no repo-root walk at all — this population is one CLASS, not the whole residue',
unnamedRootWalk(rwEntry({ rootWalk: null }), 'undetermined') === null,
);
// LIVE, against the real tree, and the case the next unnamed census gate reds
// on. The probe card is every tracked file MINUS the family's own file
// closure: without the subtraction every family matches the card that edits
// the gate itself, through the identity key, and this whole population would
// score empty while reading like a pass.
{
const liveAll = [...liveTree.files];
const members = [];
for (const [check, entry] of liveDiscovery.byCheck) {
if (!entry.rootWalk || entry.wholeTreeReason || entry.noPopulationReason) continue;
const own = new Set(entry.files ?? []);
const probe = own.size ? liveAll.filter((f) => !own.has(f)) : liveAll;
const walk = unnamedRootWalk(entry, placeFamily(entry, probe).verdict);
if (walk) members.push(check);
}
const unlisted = members.filter((c) => !ROOT_WALK_RESIDUE_REASONS.has(c)).sort();
const stale = [...ROOT_WALK_RESIDUE_REASONS.keys()].filter((c) => !members.includes(c)).sort();
t(
'every gate CI runs whose own source sweeps the repo root is DECLARED whole-tree, declared path-less, ' +
'derivable by path, or a justified row in ROOT_WALK_RESIDUE_LEDGER' +
(unlisted.length ? ` — unlisted: ${unlisted.join(', ')}` : '') +
(stale.length ? ` — listed but no longer a member: ${stale.join(', ')}` : ''),
unlisted.length === 0 && stale.length === 0,
);
// The control that makes the case above an instrument. A sweep that silently
// found NOTHING — a renamed field, a discovery that stopped reading gate
// sources — reports "nothing unlisted" and is indistinguishable from a pass,
// which is #4690 pointed at this file's own guard. So the population has to
// be seen to be non-empty, and the gate the card was filed on has to be seen
// to have LEFT it by declaring rather than by going unmeasured.
t(
`control: the residue population is non-empty (${members.length} member(s)), so the case above is measuring something`,
members.length > 0,
);
t(
'control: the card\'s own specimen has left this population through the BUCKET, not through the ledger — ' +
'check:driver-memory-census declares whole-tree, is backed by its walk, and is in no exclusion row',
Boolean(liveDiscovery.byCheck.get('check:driver-memory-census')?.wholeTreeReason)
&& wholeTreePopulationRefusal(liveDiscovery.byCheck.get('check:driver-memory-census')) === null
&& !ROOT_WALK_RESIDUE_REASONS.has('check:driver-memory-census'),
);
t(
'every ledger row carries a reason that says what the gate reads INSTEAD — a reason-less exclusion is the ' +
'placeholder shape both markers refuse',
ROOT_WALK_RESIDUE_LEDGER.every(([, why]) => typeof why === 'string' && why.trim().length > 40),
);
}

// ── The CI-MEASURED-ONLY shape (#14004) ───────────────────────────────────
//
// The two markers above are DECLARATIONS a gate carries. This one is the
Expand Down
Loading