From a51cd08e81e4dc65cd243d7402d89c1e7c4c6975 Mon Sep 17 00:00:00 2001 From: os-warren Date: Tue, 27 Oct 2026 06:18:43 +0000 Subject: [PATCH 1/3] tooling(dispatch-gates): the STALE TREE reading keeps only the moved paths the derivation actually opened MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `DERIVATION_SURFACE` stays the pathspec roots the staleness diff is scoped to, but `scripts/` also holds generated records nothing derives from (the SDUI manifest record, the ADR anchor files), regenerated by landings in other lanes, so every such landing read as a STALE TREE over a pm-only tree whose families had not moved. `baseDrift` now splits the moved paths against `derivationInputs` — the files the discovery really opened: every workflow, the composite actions it followed, the root manifest, each family's gate files, imports, spawned programs, path reads and package manifests, plus this engine's own module closure — into `changed` (inputs, the only ones that decide loud vs quiet) and `ignored` (under the roots, not inputs), and the quiet rendering names what it set aside. A caller may hand in `inputs` itself; a foreign checkout with no discovery keeps the raw superset reading. Self-test: a record-only drift reads FRESH; a gate-script, workflow or root manifest drift still reads STALE; live-tree pins on the measured set. Claude-Session: https://claude.ai/code/session_01JQwxbZ5vD35MzQ8UqhhTJB Co-authored-by: Claude --- scripts/pm/dispatch-gates.mjs | 182 ++++++++++++++++++++++-- scripts/pm/dispatch-gates.self-test.mjs | 110 +++++++++++++- 2 files changed, 277 insertions(+), 15 deletions(-) diff --git a/scripts/pm/dispatch-gates.mjs b/scripts/pm/dispatch-gates.mjs index b113bc65768..74e9f5bca31 100644 --- a/scripts/pm/dispatch-gates.mjs +++ b/scripts/pm/dispatch-gates.mjs @@ -15451,15 +15451,117 @@ export function repoIdentity({ cwd = ROOT } = {}) { } /** - * The files a derivation's ANSWER is made of — the ones whose staleness can - * change it. The gate inventory is read from the workflow files, `check:*` is - * resolved through the manifest, and the checks themselves live under - * `scripts/`. Everything else in the tree can be arbitrarily old without moving - * a single family, which is what makes this list the right filter and raw - * commit distance the wrong one. + * Where the files a derivation's ANSWER is made of LIVE — the pathspec roots + * the staleness diff is scoped to. The gate inventory is read from the + * workflow files, `check:*` is resolved through the manifest, and the checks + * themselves live under `scripts/`. Everything outside these roots can be + * arbitrarily old without moving a single family, which is what makes the + * roots the right filter and raw commit distance the wrong one. + * + * Roots, not the surface itself (#26334). `scripts/` also holds generated + * records nothing derives from — the SDUI manifest record, the ADR anchor + * files — and those move on landings in other lanes, so a diff scoped to the + * roots ALONE named them as "files it derives from" on every such landing: a + * STALE TREE over a pm-only tree whose derived set had not moved, which is the + * warning-on-every-honest-run shape the paragraph under `baseDrift` prices. + * Which files under the roots the answer is really made of is MEASURED, never + * listed beside it: `derivationInputs` below, and `baseDrift` keeps only the + * moved paths that are in it. */ export const DERIVATION_SURFACE = ['.github/workflows', '.github/actions', 'package.json', 'scripts']; +/** + * This engine's own module closure — the file itself, the first-party modules + * it imports, and the battery it loads by `import()` — spelled from this + * file's own URL, so a rename moves it. The discovery follows a gate's imports + * ONE level, and this file is reached as an import or a spawn target of the + * gates that use it, never as a gate of its own, so its data module sits two + * levels down from every family: a tree whose `CHANGE_KIND_GATES` row moved + * upstream would otherwise read FRESH. + */ +function engineClosure() { + const self = nodePath.relative(ROOT, new URL(import.meta.url).pathname); + const battery = nodePath.relative(ROOT, new URL('./dispatch-gates.self-test.mjs', import.meta.url).pathname); + return [self, battery, ...firstPartyImportTargets(self, readFileSync(nodePath.join(ROOT, self), 'utf8'))]; +} + +const derivationInputsMemo = new WeakMap(); + +/** + * The files this checkout's derivation ACTUALLY opens — the honest derivation + * surface, measured from the discovery rather than declared beside it. + * + * Every workflow file (the discovery reads all of them), every composite + * action the follow opened, the root manifest, and for every family its gate + * files, the modules it imports, the programs it spawns, the files it opens by + * path and the package manifests it resolves through — plus this engine's own + * closure. A path under `DERIVATION_SURFACE` that is in none of those is a + * path the answer was not made of, and a change to it upstream cannot have + * moved a family: a change that WOULD (a new gate, a new import, a new read) + * has to arrive through a file that IS in the set — the manifest, a workflow, + * or the gate that gained the edge — so the set is closed under the answer it + * describes. Measured on this tree: 266 of 443 tracked files under `scripts/` + * are inputs; the rest can move without moving a family. + * + * Memoised per discovery, which is itself memoised per tree, so a derivation + * that already discovered pays nothing more for it — and that is the ONLY + * price this may have. A discovery costs seconds (measured: ~14 s cold on + * this tree), so the modes that derive hand it to `baseDrift` and `--tier`, + * which reads no workflow by contract, does not: a battery of `--tier` + * children on a tree a few commits behind was measured to quadruple its wall + * clock when the banner asked for a discovery the mode never needed. + */ +export function derivationInputs({ tree = repoCorpus().tree } = {}) { + const discovered = discoverFamilies({ tree }); + const hit = derivationInputsMemo.get(discovered); + if (hit !== undefined) return hit; + const inputs = new Set(['package.json', ...engineClosure()]); + for (const wf of discovered.workflows) inputs.add(`.github/workflows/${wf}`); + for (const action of discovered.compositeActions) inputs.add(action); + for (const entry of discovered.byCheck.values()) { + for (const list of [entry.files, entry.imports, entry.runs, entry.reads, entry.manifests]) { + for (const rel of list ?? []) inputs.add(rel); + } + } + derivationInputsMemo.set(discovered, inputs); + return inputs; +} + +/** + * Split the paths a diff reported under the roots into the ones the answer is + * made of and the ones it is not. `inputs` null = NOT narrowed: every moved + * path is kept and `ignored` is `null` (no reading was taken, which is not an + * empty one — the same distinction `changed` draws), the only reading a + * checkout with no discovery of its own (a temporary repository in the + * battery) or a mode that reads no workflow (`--tier`) can honestly take: a + * superset, so it over-reports and never under-reports. + */ +export function narrowToDerivationInputs(moved, inputs) { + if (inputs === null || inputs === undefined) return { changed: [...moved], ignored: null }; + const changed = []; + const ignored = []; + for (const rel of moved) (inputs.has(rel) ? changed : ignored).push(rel); + return { changed, ignored }; +} + +/** + * The inputs a DERIVING mode hands `baseDrift`: this checkout's own measured + * set, behind a function so it is asked for only once something moved under + * the roots — a level tree never pays for it, and a deriving mode pays for it + * once, since the derivation that follows the banner discovers the same tree + * through the same memo. A discovery that refuses degrades to no narrowing + * (the raw roots, a superset): the reading over-reports, which is this file's + * direction for every degraded read, and the derivation then refuses loudly + * on its own. + */ +export function liveDerivationInputs() { + try { + return derivationInputs(); + } catch { + return null; + } +} + /** * How far behind `DEFAULT_BASE_REF` this checkout is — and whether that matters. * @@ -15491,6 +15593,24 @@ export const DERIVATION_SURFACE = ['.github/workflows', '.github/actions', 'pack * make the true number bigger, never smaller — which is what lets this stay * honest without the derivation reaching for the network. * + * The CHANGED SET is NARROWED to what the answer is made of (#26334). The + * diff is scoped to `DERIVATION_SURFACE`, and `scripts/` is a root that also + * holds generated records nothing derives from, regenerated by landings in + * other lanes; reported raw, every such landing read as a STALE TREE over a + * tree whose families had not moved — the warning-nobody-reads shape the + * paragraph above was written to avoid, arriving by another route. So the + * moved paths are split against `derivationInputs` — the files the discovery + * really opened, measured, never listed — into `changed` (inputs) and + * `ignored` (under the roots, not inputs), and only `changed` decides between + * loud and quiet. The CALLER names `inputs` — a set, or a function asked for + * one only once something moved — because measuring it is a discovery, and + * which modes may pay for one is a fact about the mode: the deriving modes + * hand in `liveDerivationInputs` and pay nothing extra, `--tier` reads no + * workflow by contract and hands in nothing, and a battery driving a temporary + * repository (no discovery of its own) hands in the set it means. `null` keeps + * every moved path with `ignored: null`, the superset reading, and the loud + * rendering says that reading was not narrowed. + * * The CHANGED SET inherits that lower-bound property, and for a while nothing * said so (#13392). `changed` is diffed against the same possibly-stale * snapshot the count is, so files-changed-vs-upstream is a SUPERSET of what @@ -15529,7 +15649,7 @@ export const DERIVATION_SURFACE = ['.github/workflows', '.github/actions', 'pack * not read, and only an ARRAY when it was. `unmeasuredDrift` below is the * single predicate all three degraded fields are read through. */ -export function baseDrift({ cwd = ROOT } = {}) { +export function baseDrift({ cwd = ROOT, inputs = null } = {}) { const read = (args) => { try { const r = runGit(args, cwd); @@ -15539,19 +15659,32 @@ export function baseDrift({ cwd = ROOT } = {}) { } }; const base = read(['rev-parse', '--short', DEFAULT_BASE_REF]); - if (base === null) return { base: null, behind: null, changed: null, headDate: null, baseDate: null }; + if (base === null) return { base: null, behind: null, changed: null, ignored: null, headDate: null, baseDate: null }; const counted = read(['rev-list', '--count', `HEAD..${DEFAULT_BASE_REF}`]); const behind = /^\d+$/.test(counted ?? '') ? Number(counted) : null; // At `behind: 0` the empty set is exact BY CONSTRUCTION — an empty commit // range moves no files — so it is a reading without running the diff. With // no distance in hand there is no range to read, so `changed` is unread too. const names = behind ? read(['diff', '--name-only', `HEAD...${DEFAULT_BASE_REF}`, '--', ...DERIVATION_SURFACE]) : behind === 0 ? '' : null; + // `null` = the diff was NOT read (a failed read is not an empty one); + // an array — even empty — = the diff ran and this is what it said. + const moved = names === null ? null : names.split('\n').filter(Boolean); + // Narrowed to the inputs ONLY once the diff is in hand and non-empty: an + // unread diff has nothing to narrow, an empty one is not worth a discovery, + // and `ignored` degrades with `changed` so the two never describe different + // readings. + const narrowed = moved === null + ? null + : narrowToDerivationInputs(moved, typeof inputs === 'function' ? (moved.length > 0 ? inputs() : null) : inputs); return { base, behind, - // `null` = the diff was NOT read (a failed read is not an empty one); - // an array — even empty — = the diff ran and this is what it said. - changed: names === null ? null : names.split('\n').filter(Boolean), + changed: narrowed === null ? null : narrowed.changed, + // The moved paths under the roots that are NOT inputs — set aside, never + // dropped: the quiet rendering names them so a reader can see the quiet + // was measured rather than assumed. `null` = not narrowed (no inputs were + // named, or the diff was not read), never an empty array. + ignored: narrowed === null ? null : narrowed.ignored, headDate: read(['log', '-1', '--format=%cI', 'HEAD']), baseDate: read(['log', '-1', '--format=%cI', DEFAULT_BASE_REF]), }; @@ -15688,11 +15821,19 @@ export function driftLines(drift) { ]; } if (!drift.behind) return []; - const { behind, base, changed, headDate, baseDate } = drift; + const { behind, base, changed, ignored, headDate, baseDate } = drift; const span = `HEAD${headDate ? ` ${headDate}` : ''} vs ${DEFAULT_BASE_REF} ${base}${baseDate ? ` ${baseDate}` : ''}`; if (changed.length === 0) { + // What moved under the roots and was set aside as not an input (#26334), + // named, so the quiet line reads as a measurement and not as the silence + // a level tree gets. Absent or empty prints nothing: a reading that set + // nothing aside renders byte-identically to one taken before the field + // existed. + const setAside = Array.isArray(ignored) && ignored.length > 0 + ? ` ${ignored.length} path(s) under the surface moved too, none of them an input of this answer: ${ignored.slice(0, 3).join(' ')}${ignored.length > 3 ? ` … +${ignored.length - 3} more` : ''}.` + : ''; return [ - ` At least ${behind} commit(s) behind ${DEFAULT_BASE_REF}, and none of the commit(s) this tree can SEE touched what this answer derives from — ${span}.`, + ` At least ${behind} commit(s) behind ${DEFAULT_BASE_REF}, and none of the commit(s) this tree can SEE touched what this answer derives from — ${span}.${setAside}`, ` Whether UNSEEN upstream work did, this run cannot tell: the range above ends at ${DEFAULT_BASE_REF}, a LOCAL snapshot only a fetch moves — not at upstream. Run 'git fetch ${DEFAULT_BASE_REMOTE} ${DEFAULT_BASE_BRANCH}' and derive again for the strongest reading a checkout can take.`, ]; } @@ -15702,6 +15843,14 @@ export function driftLines(drift) { ` Stale here: ${changed.slice(0, 6).join(' ')}${changed.length > 6 ? ` … +${changed.length - 6} more` : ''}`, ` Those files ARE the families printed below, so this run read their old copies and still exited 0 — a well-formed answer about a tree nobody is on.`, ` "At least": ${DEFAULT_BASE_REF} is a LOCAL ref only a fetch moves. Run 'git fetch ${DEFAULT_BASE_REMOTE} ${DEFAULT_BASE_BRANCH}' and derive again from a tree at ${DEFAULT_BASE_REF}.`, + // A list nobody narrowed may name paths the answer is not made of — a + // generated record under `scripts/` — and a reader cannot tell which from + // the list alone, so the reading says so and names the run that measures + // it (#26334). Absent from a narrowed reading: there the list IS the + // inputs. + ...(Array.isArray(ignored) ? [] : [ + ` Not narrowed to what the answer is made of: this run measured no derivation, so every moved path under the surface is listed, generated records included. A --commands or --ran run measures the inputs and lists only those.`, + ]), ]; } @@ -16225,7 +16374,12 @@ if (invokedDirectly) { for (const line of sisterTier.stdout) console.log(line); process.exit(0); } - for (const line of bannerLines({ identity, paths: declaredPaths, drift: baseDrift() })) console.error(line); + // `--tier` reads no workflow and no check script by contract (its branch + // below), so it hands the drift no inputs to narrow by and its loud reading + // says so; every deriving mode hands in the measured set, which it is about + // to discover anyway. + const drift = baseDrift({ inputs: process.argv.includes('--tier') ? null : liveDerivationInputs }); + for (const line of bannerLines({ identity, paths: declaredPaths, drift })) console.error(line); if (argv.assertion !== null) { // An assertion the tree contradicts is the measured failure, caught. It // ends the run: a caller that named the repo it needs has stated a diff --git a/scripts/pm/dispatch-gates.self-test.mjs b/scripts/pm/dispatch-gates.self-test.mjs index 9a94fc45f70..3c447d59fc9 100644 --- a/scripts/pm/dispatch-gates.self-test.mjs +++ b/scripts/pm/dispatch-gates.self-test.mjs @@ -140,6 +140,7 @@ import { declaresPullRequestTrigger, deepestTrackedPrefix, defaultedArgv, + derivationInputs, derivationProvenance, deriveTier, discoverFamilies, @@ -208,6 +209,8 @@ import { maskShellComments, metadataFormModulePaths, metadataFormsSurfaceIsExtracted, + liveDerivationInputs, + narrowToDerivationInputs, notRunnableCommandSet, outsideBlockCounts, outsideBlockNames, @@ -11103,6 +11106,64 @@ export function selfTest({ tier = 'full' } = {}) { benign.length === 2 && benign[0].includes('none of the commit(s)') && !benignText.includes('nothing this answer derives from changed')); t('it hands over the fetch, the one action that strengthens the reading', benignText.includes(`git fetch ${DEFAULT_BASE_REMOTE} ${DEFAULT_BASE_BRANCH}`)); t('and the quiet spelling still does not cry stale, so the loud spelling stays rare', !benignText.includes('STALE TREE')); + // The narrowed reading (#26334): the moved paths the answer is NOT made of + // are set aside into `ignored`, and the quiet line NAMES them — a quiet that + // reads as a measurement, not as the silence a level tree gets. A reading + // that set nothing aside, or one taken before the field existed, renders + // byte-identically to `benign` above, which is what keeps every pin on the + // quiet shape green across the change. + const setAside = driftLines({ base: 'aaaaaaa', behind: 3, changed: [], ignored: ['scripts/sdui-manifest.record.json'], headDate: '2026-01-01T00:00:00Z', baseDate: '2026-01-02T00:00:00Z' }); + const setAsideText = setAside.join('\n'); + t('a drift whose only moved paths are NOT inputs renders the quiet shape — two lines, scoped to what the tree can see, no STALE TREE', + setAside.length === 2 && setAside[0].includes('3 commit(s) behind') && setAside[0].includes('can SEE') && !setAsideText.includes('STALE TREE')); + t('and it names what moved and that none of it is an input, so the reader can see the quiet was measured', + setAside[0].includes('1 path(s) under the surface moved too, none of them an input') && setAside[0].includes('scripts/sdui-manifest.record.json')); + t('a reading that set nothing aside renders byte-identically to one that never carried the field', + driftLines({ base: 'aaaaaaa', behind: 7, changed: [], ignored: [], headDate: '2026-01-01T00:00:00Z', baseDate: '2026-01-02T00:00:00Z' }).join('\n') === benignText); + t('more than three set-aside paths are counted past the third, never listed in full', + driftLines({ base: 'aaaaaaa', behind: 1, changed: [], ignored: ['scripts/a.json', 'scripts/b.json', 'scripts/c.json', 'scripts/d.json', 'scripts/e.json'] })[0].includes('5 path(s) under the surface moved too') && driftLines({ base: 'aaaaaaa', behind: 1, changed: [], ignored: ['scripts/a.json', 'scripts/b.json', 'scripts/c.json', 'scripts/d.json', 'scripts/e.json'] })[0].includes('… +2 more')); + const split = narrowToDerivationInputs( + ['scripts/sdui-manifest.record.json', 'scripts/pm/dispatch-gates.mjs', '.github/workflows/lint.yml'], + new Set(['scripts/pm/dispatch-gates.mjs', '.github/workflows/lint.yml']), + ); + t('narrowToDerivationInputs keeps the inputs and sets the rest aside, order preserved on both sides', + split.changed.join() === 'scripts/pm/dispatch-gates.mjs,.github/workflows/lint.yml' && split.ignored.join() === 'scripts/sdui-manifest.record.json'); + t('and with no inputs to narrow by it keeps everything and reads ignored as NOT MEASURED (null), never as an empty array', + narrowToDerivationInputs(['a', 'b'], null).changed.join() === 'a,b' && narrowToDerivationInputs(['a', 'b'], null).ignored === null + && narrowToDerivationInputs(['a'], undefined).changed.join() === 'a' && narrowToDerivationInputs(['a'], undefined).ignored === null); + // The loud reading says when it was NOT narrowed — a `--tier` run, which + // reads no workflow by contract — and names the run that measures the + // inputs; a narrowed loud reading carries no such line, its list IS the + // inputs. Pinned on the literal shape above (no `ignored` field at all) and + // on the two real readings in the temporary repository below. + t('a loud reading with NO narrowing says so and names the deriving runs that measure the inputs', + loudText.includes('Not narrowed to what the answer is made of') && loudText.includes('--commands or --ran')); + t('and a loud reading that WAS narrowed carries no such line — its list is the inputs', + !driftLines({ base: 'aaaaaaa', behind: 2, changed: ['scripts/pm/dispatch-gates.mjs'], ignored: [], headDate: '2026-01-01T00:00:00Z', baseDate: '2026-01-02T00:00:00Z' }).join('\n').includes('Not narrowed')); + t('liveDerivationInputs on this tree answers the measured set itself, never a degraded null', + liveDerivationInputs() === live); + // The MEASURED set, read off this tree's own discovery: the narrowing is + // only honest if the set it narrows by is closed under the answer, so these + // pin the members that closure needs — the manifest, every workflow, every + // gate file, this engine and its two-levels-down data module — and pin that + // the set is a strict subset of what the roots hold, so the narrowing is + // not vacuous. The last case pins the live specimen: the generated SDUI + // manifest record, which moves on ui landings and which no family opens. + const live = derivationInputs(); + const trackedNow = new Set(trackedFiles()); + const workflowFiles = readdirSync(nodePath.join(ROOT, '.github/workflows')).filter((f) => /\.ya?ml$/.test(f)); + t('LIVE: every measured derivation input is a tracked file — no fabricated path', live.size > 0 && [...live].every((rel) => trackedNow.has(rel))); + t(`LIVE: the inputs hold the root manifest and all ${workflowFiles.length} workflow files`, + live.has('package.json') && workflowFiles.length > 0 && workflowFiles.every((f) => live.has(`.github/workflows/${f}`))); + t('LIVE: they hold this engine, its data module (two levels down from every family) and its battery', + live.has('scripts/pm/dispatch-gates.mjs') && live.has('scripts/pm/dispatch-gates.data.mjs') && live.has('scripts/pm/dispatch-gates.self-test.mjs') && live.has('scripts/pm/check-dispatch-gates.mjs')); + t('LIVE: and every gate file of every discovered family', [...discoverFamilies().byCheck.values()].every((entry) => entry.files.every((f) => live.has(f)))); + const trackedUnderScripts = [...trackedNow].filter((rel) => rel.startsWith('scripts/')); + const inputsUnderScripts = trackedUnderScripts.filter((rel) => live.has(rel)); + t(`LIVE: the narrowing is not vacuous — ${inputsUnderScripts.length} of ${trackedUnderScripts.length} tracked files under scripts/ are inputs, the rest can move without moving a family`, + inputsUnderScripts.length > 0 && inputsUnderScripts.length < trackedUnderScripts.length); + t('LIVE: the generated SDUI manifest record — the specimen that read STALE on every ui landing — is tracked and is NOT an input', + trackedNow.has('scripts/sdui-manifest.record.json') && !live.has('scripts/sdui-manifest.record.json')); // The THIRD unmeasured door (#13392): distance reads, changed set does not. // Under the old shape this state collapsed into `changed: []` and rendered // as the quiet clear sentence — a reassurance manufactured from a FAILED @@ -11145,6 +11206,10 @@ export function selfTest({ tier = 'full' } = {}) { // Positive control: a clone level with its base must read zero, or a // non-zero reading below proves nothing. t('a checkout level with its base measures zero drift (positive control)', baseDrift({ cwd: clone }).behind === 0); + // A level tree never pays for a discovery: the inputs function is not + // called when nothing moved, which a thrower proves. + t('and a level tree never asks for the inputs — the function handed in is not called when nothing moved', + baseDrift({ cwd: clone, inputs: () => { throw new Error('asked for inputs on a level tree'); } }).behind === 0); // The other end of the distinction, measured rather than hand-built: `up` // was `git init`-ed and has no remote at all, so the base ref genuinely // does not resolve there — the fresh-checkout state the object literals @@ -11191,14 +11256,57 @@ export function selfTest({ tier = 'full' } = {}) { t('drift against a real repo is measured from git, never assumed', offSurface.behind === 1 && !!offSurface.base); t('and a commit outside the derivation surface stays off the loud list — and the quiet render says what it can see', offSurface.changed.length === 0 && driftLines(offSurface).length === 2 && driftLines(offSurface)[0].includes('can SEE')); + // Upstream regenerates a record UNDER the roots that nothing derives from + // (#26334) — the card's specimen, a per-package SDUI manifest record. A + // temporary repository has no discovery of its own, so the inputs are + // handed in; the measured set is pinned above on the live tree. The raw + // reading is kept beside the narrowed one as the departure pin: it is + // what used to go loud. + const INPUTS = new Set(['scripts/check-thing.mjs', '.github/workflows/lint.yml', 'package.json']); + mkdirSync(nodePath.join(up, 'scripts', 'sdui-manifest.sources'), { recursive: true }); + writeFileSync(nodePath.join(up, 'scripts', 'sdui-manifest.sources', 'core.json'), '{"sha256":"a"}\n'); + gd(['add', '-A'], up); gd(['commit', '-qm', 'regenerate a record nothing derives from'], up); + gd(['fetch', '-q', DEFAULT_BASE_REMOTE], clone); + const rawRecord = baseDrift({ cwd: clone, inputs: null }); + const recordOnly = baseDrift({ cwd: clone, inputs: INPUTS }); + t('a record under the roots that nothing derives from IS what the raw diff reports — the reading that used to go loud — with ignored NOT MEASURED', + rawRecord.behind === 2 && rawRecord.changed.join() === 'scripts/sdui-manifest.sources/core.json' && rawRecord.ignored === null); + t('and the raw loud reading SAYS it was not narrowed, where the narrowed quiet one below names what it set aside', + driftLines(rawRecord).join('\n').includes('STALE TREE') && driftLines(rawRecord).join('\n').includes('Not narrowed to what the answer is made of')); + const recordOnlyLazy = baseDrift({ cwd: clone, inputs: () => INPUTS }); + t('inputs handed in as a function are asked for once something moved, and read identically to the set', + JSON.stringify(recordOnlyLazy) === JSON.stringify(baseDrift({ cwd: clone, inputs: INPUTS })) && recordOnlyLazy.ignored.join() === 'scripts/sdui-manifest.sources/core.json'); + t('narrowed by the inputs the same tree reads FRESH: changed empty, the record set aside by name', + recordOnly.behind === 2 && recordOnly.changed.length === 0 && recordOnly.ignored.join() === 'scripts/sdui-manifest.sources/core.json'); + const recordOnlyText = driftLines(recordOnly).join('\n'); + t('and it renders quiet — the visible-range sentence naming the set-aside record, no STALE TREE', + driftLines(recordOnly).length === 2 && recordOnlyText.includes('can SEE') && recordOnlyText.includes('none of them an input') && recordOnlyText.includes('scripts/sdui-manifest.sources/core.json') && !recordOnlyText.includes('STALE TREE')); + t('a foreign checkout handed no inputs is NOT narrowed by this tree\'s set — the raw roots are its only honest reading', + baseDrift({ cwd: clone }).changed.join() === 'scripts/sdui-manifest.sources/core.json'); // Now upstream moves a file the answer IS derived from — the measured shape. mkdirSync(nodePath.join(up, 'scripts'), { recursive: true }); writeFileSync(nodePath.join(up, 'scripts', 'check-thing.mjs'), 'export const a = 1;\n'); gd(['add', '-A'], up); gd(['commit', '-qm', 'change a check script'], up); gd(['fetch', '-q', DEFAULT_BASE_REMOTE], clone); const onSurface = baseDrift({ cwd: clone }); - t('a commit INSIDE the derivation surface is caught and named', onSurface.behind === 2 && onSurface.changed.includes('scripts/check-thing.mjs')); + t('a commit INSIDE the derivation surface is caught and named', onSurface.behind === 3 && onSurface.changed.includes('scripts/check-thing.mjs')); t('and that is the case that goes loud', driftLines(onSurface).join('\n').includes('STALE TREE')); + const onSurfaceNarrowed = baseDrift({ cwd: clone, inputs: INPUTS }); + t('narrowed by the inputs a changed gate script STILL goes loud, named alone, with the record still set aside — and no not-narrowed line', + onSurfaceNarrowed.changed.join() === 'scripts/check-thing.mjs' && onSurfaceNarrowed.ignored.join() === 'scripts/sdui-manifest.sources/core.json' + && driftLines(onSurfaceNarrowed).join('\n').includes('STALE TREE') && driftLines(onSurfaceNarrowed).join('\n').includes('Stale here: scripts/check-thing.mjs') + && !driftLines(onSurfaceNarrowed).join('\n').includes('Not narrowed')); + // The other two kinds of input the ruling names: a workflow and the root + // manifest, moving upstream together. + mkdirSync(nodePath.join(up, '.github', 'workflows'), { recursive: true }); + writeFileSync(nodePath.join(up, '.github', 'workflows', 'lint.yml'), 'on: pull_request\n'); + writeFileSync(nodePath.join(up, 'package.json'), '{"scripts":{"check:thing":"node scripts/check-thing.mjs"}}\n'); + gd(['add', '-A'], up); gd(['commit', '-qm', 'a workflow and the root manifest move'], up); + gd(['fetch', '-q', DEFAULT_BASE_REMOTE], clone); + const workflowAndManifest = baseDrift({ cwd: clone, inputs: INPUTS }); + t('a workflow and the root manifest moving upstream are inputs and go loud, both named', + workflowAndManifest.behind === 4 && workflowAndManifest.changed.includes('.github/workflows/lint.yml') && workflowAndManifest.changed.includes('package.json') + && driftLines(workflowAndManifest).join('\n').includes('STALE TREE')); // Reachability from an ORDINARY checkout, which is what makes the door // above a state of working clones and not only of repos built to show it: // `clone` is fully fetched and has just measured a real distance, and one From 9a12ebe176f4a526e895b567f20cd6c15e90605b Mon Sep 17 00:00:00 2001 From: os-warren Date: Tue, 27 Oct 2026 08:30:34 +0000 Subject: [PATCH 2/3] test(dispatch-gates): pin the not-narrowed loud line after the loud fixture it reads The two cases read `loudText` above its declaration, a temporal-dead-zone ReferenceError that only the slow tier reaches (the base-drift section is slow), so the fast-tier `check:pm-dispatch-gates` run stayed green. Claude-Session: https://claude.ai/code/session_01JQwxbZ5vD35MzQ8UqhhTJB Co-authored-by: Claude --- scripts/pm/dispatch-gates.self-test.mjs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/scripts/pm/dispatch-gates.self-test.mjs b/scripts/pm/dispatch-gates.self-test.mjs index 63a6062f50c..fe2740a2423 100644 --- a/scripts/pm/dispatch-gates.self-test.mjs +++ b/scripts/pm/dispatch-gates.self-test.mjs @@ -11133,15 +11133,6 @@ export function selfTest({ tier = 'full' } = {}) { t('and with no inputs to narrow by it keeps everything and reads ignored as NOT MEASURED (null), never as an empty array', narrowToDerivationInputs(['a', 'b'], null).changed.join() === 'a,b' && narrowToDerivationInputs(['a', 'b'], null).ignored === null && narrowToDerivationInputs(['a'], undefined).changed.join() === 'a' && narrowToDerivationInputs(['a'], undefined).ignored === null); - // The loud reading says when it was NOT narrowed — a `--tier` run, which - // reads no workflow by contract — and names the run that measures the - // inputs; a narrowed loud reading carries no such line, its list IS the - // inputs. Pinned on the literal shape above (no `ignored` field at all) and - // on the two real readings in the temporary repository below. - t('a loud reading with NO narrowing says so and names the deriving runs that measure the inputs', - loudText.includes('Not narrowed to what the answer is made of') && loudText.includes('--commands or --ran')); - t('and a loud reading that WAS narrowed carries no such line — its list is the inputs', - !driftLines({ base: 'aaaaaaa', behind: 2, changed: ['scripts/pm/dispatch-gates.mjs'], ignored: [], headDate: '2026-01-01T00:00:00Z', baseDate: '2026-01-02T00:00:00Z' }).join('\n').includes('Not narrowed')); t('liveDerivationInputs on this tree answers the measured set itself, never a degraded null', liveDerivationInputs() === live); // The MEASURED set, read off this tree's own discovery: the narrowing is @@ -11192,6 +11183,15 @@ export function selfTest({ tier = 'full' } = {}) { t('it names the stale files themselves, not just a count', loudText.includes('scripts/pm/dispatch-gates.mjs') && loudText.includes('.github/workflows/lint.yml')); t('it says the exit code is no defence — the measured failure exited 0', loudText.includes('exited 0')); t('the count is a LOWER bound, because the base ref is local and only a fetch moves it', loudText.includes('At least') && loudText.includes(`git fetch ${DEFAULT_BASE_REMOTE} ${DEFAULT_BASE_BRANCH}`)); + // The loud reading says when it was NOT narrowed — a `--tier` run, which + // reads no workflow by contract — and names the run that measures the + // inputs; a narrowed loud reading carries no such line, its list IS the + // inputs. Pinned on the literal shape above (no `ignored` field at all) and + // on the two real readings in the temporary repository below. + t('a loud reading with NO narrowing says so and names the deriving runs that measure the inputs', + loudText.includes('Not narrowed to what the answer is made of') && loudText.includes('--commands or --ran')); + t('and a loud reading that WAS narrowed carries no such line — its list is the inputs', + !driftLines({ base: 'aaaaaaa', behind: 2, changed: ['scripts/pm/dispatch-gates.mjs'], ignored: [], headDate: '2026-01-01T00:00:00Z', baseDate: '2026-01-02T00:00:00Z' }).join('\n').includes('Not narrowed')); t('drift reaches the banner, and stays behind the repo line that must come first', bannerLines({ identity: hereIdentity, paths: [], drift: { base: 'aaaaaaa', behind: 9, changed: ['scripts/x.mjs'] } })[0].includes('gate list derived from the tree of')); t('and a banner given no drift is byte-identical to before the flag existed', bannerLines({ identity: hereIdentity, paths: [], drift: null }).join('\n') === bannerLines({ identity: hereIdentity, paths: [] }).join('\n')); From 4b203a9dfd62f74036f9a45a7bb52c1a01b649df Mon Sep 17 00:00:00 2001 From: os-warren Date: Tue, 27 Oct 2026 08:40:58 +0000 Subject: [PATCH 3/3] test(dispatch-gates): pin liveDerivationInputs after the measured set it compares against The same slow-tier-only temporal-dead-zone shape as the previous commit: the case read `live` two lines above its declaration. Every identifier the new drift cases read is now checked to be declared above its first read. Claude-Session: https://claude.ai/code/session_01JQwxbZ5vD35MzQ8UqhhTJB Co-authored-by: Claude --- scripts/pm/dispatch-gates.self-test.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/pm/dispatch-gates.self-test.mjs b/scripts/pm/dispatch-gates.self-test.mjs index fe2740a2423..7238333f7c3 100644 --- a/scripts/pm/dispatch-gates.self-test.mjs +++ b/scripts/pm/dispatch-gates.self-test.mjs @@ -11133,8 +11133,6 @@ export function selfTest({ tier = 'full' } = {}) { t('and with no inputs to narrow by it keeps everything and reads ignored as NOT MEASURED (null), never as an empty array', narrowToDerivationInputs(['a', 'b'], null).changed.join() === 'a,b' && narrowToDerivationInputs(['a', 'b'], null).ignored === null && narrowToDerivationInputs(['a'], undefined).changed.join() === 'a' && narrowToDerivationInputs(['a'], undefined).ignored === null); - t('liveDerivationInputs on this tree answers the measured set itself, never a degraded null', - liveDerivationInputs() === live); // The MEASURED set, read off this tree's own discovery: the narrowing is // only honest if the set it narrows by is closed under the answer, so these // pin the members that closure needs — the manifest, every workflow, every @@ -11143,6 +11141,8 @@ export function selfTest({ tier = 'full' } = {}) { // not vacuous. The last case pins the live specimen: the generated SDUI // manifest record, which moves on ui landings and which no family opens. const live = derivationInputs(); + t('liveDerivationInputs on this tree answers the measured set itself, never a degraded null', + liveDerivationInputs() === live); const trackedNow = new Set(trackedFiles()); const workflowFiles = readdirSync(nodePath.join(ROOT, '.github/workflows')).filter((f) => /\.ya?ml$/.test(f)); t('LIVE: every measured derivation input is a tracked file — no fabricated path', live.size > 0 && [...live].every((rel) => trackedNow.has(rel)));