From a1d5df1da41234afa039ed95e5ca7535d9ffeac8 Mon Sep 17 00:00:00 2001 From: Claude Code Date: Sun, 30 Aug 2026 10:55:39 +0000 Subject: [PATCH] fix(devx): the drift banner's quiet line scopes itself to the visible range and refuses the unqualified all-clear MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The quiet branch printed 'nothing this answer derives from changed across that range' from a reading whose range ends at the last-fetched snapshot of the base ref. Measured on a production incident: the snapshot was at most ~13 minutes old, the visible reading exact, and upstream landed four derivation-surface commits between derivation and the CI run that consumed the answer — one carrying the very family whose absence turned CI red. The sentence is not earnable by any local instrument, so it now states what it measured (the VISIBLE commits are surface-clear), states the unseen half as untellable, and hands over the fetch. A second unearned-reassurance door closed with it: a failed three-dot diff read collapsed into changed: [] and rendered the same quiet clear sentence from no reading at all. A shallow clone plus one shallow fetch reaches it (measured: the count reads 1, the diff dies with no merge base). changed is now null when unread, unmeasuredDrift gains the third door, and the self-test pins both doors from literals and from a real depth-1 fixture. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC --- scripts/pm/dispatch-gates.mjs | 194 +++++++++++++++++++++++++++++----- 1 file changed, 165 insertions(+), 29 deletions(-) diff --git a/scripts/pm/dispatch-gates.mjs b/scripts/pm/dispatch-gates.mjs index 74ec1f41f4..6df877f354 100644 --- a/scripts/pm/dispatch-gates.mjs +++ b/scripts/pm/dispatch-gates.mjs @@ -6335,6 +6335,23 @@ export const DERIVATION_SURFACE = ['.github/workflows', 'package.json', 'scripts * make the true number bigger, never smaller — which is what lets this stay * honest without the derivation reaching for the network. * + * 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 + * this reads: an empty `changed` means "nothing changed that THIS SNAPSHOT can + * see", never "nothing changed". The measured failure sat exactly in that gap: + * a run whose snapshot was at most ~13 minutes old read `behind: 1, changed: + * []` — exact for its visible range — and rendered it as "nothing this answer + * derives from changed", while upstream landed four surface commits between + * that reading and the CI run that consumed the answer, one of them carrying + * the very family whose absence turned CI red. At this repo's landing cadence + * (a merge-queue landing every few minutes) NO local instrument can earn the + * unqualified sentence — not even a freshness check on the snapshot, because + * fresh-at-derivation is not true-at-consumption. So `driftLines` scopes the + * quiet sentence to the visible range and states the remainder as untellable, + * rather than gating a reassurance on a freshness reading this function cannot + * take honestly. + * * Every field degrades to null rather than throwing. No base ref, a shallow * clone and no git at all are real states, and none of them is an error here. * They are not a NON-EVENT either: `driftLines` renders EVERY degraded field as @@ -6342,12 +6359,19 @@ export const DERIVATION_SURFACE = ['.github/workflows', 'package.json', 'scripts * degrading here costs the caller a reading and never costs it the news. * * "Every" is load-bearing and was once only "the base ref". This function - * degrades in TWO places — `base` when the ref does not resolve, and `behind` - * when the ref resolves and the COUNT cannot be read — and the second one is - * not a corner of the first: a shallow clone reads a distance fine (measured: - * `--depth=1`, before and after the upstream moves), while an unborn HEAD makes - * `rev-list --count` fail with a resolvable ref in hand. `unmeasuredDrift` - * below is the single predicate both degraded fields are read through. + * degrades in THREE places — `base` when the ref does not resolve, `behind` + * when the ref resolves and the COUNT cannot be read, and `changed` when the + * count reads and the DIFF does not — and none is a corner of another: a + * shallow clone reads a distance fine (measured: `--depth=1`, before and after + * the upstream moves), while an unborn HEAD makes `rev-list --count` fail with + * a resolvable ref in hand, and that same shallow clone, one shallow fetch + * later, counts a distance of 1 while `HEAD...ref` dies with `no merge base` + * (measured, exit 128) because the boundary cut the history the three-dot form + * needs. The third door used to collapse into `changed: []` — a FAILED read + * rendered as the quiet visible-range-clear sentence, the least earned + * reassurance of all (#13392) — so `changed` is now `null` when the diff was + * 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 } = {}) { const read = (args) => { @@ -6359,14 +6383,19 @@ export function baseDrift({ cwd = ROOT } = {}) { } }; const base = read(['rev-parse', '--short', DEFAULT_BASE_REF]); - if (base === null) return { base: null, behind: null, changed: [], headDate: null, baseDate: null }; + if (base === null) return { base: null, behind: null, changed: null, headDate: null, baseDate: null }; const counted = read(['rev-list', '--count', `HEAD..${DEFAULT_BASE_REF}`]); const behind = /^\d+$/.test(counted ?? '') ? Number(counted) : null; - const names = behind ? read(['diff', '--name-only', `HEAD...${DEFAULT_BASE_REF}`, '--', ...DERIVATION_SURFACE]) : ''; + // 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; return { base, behind, - changed: names ? names.split('\n').filter(Boolean) : [], + // `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), headDate: read(['log', '-1', '--format=%cI', 'HEAD']), baseDate: read(['log', '-1', '--format=%cI', DEFAULT_BASE_REF]), }; @@ -6376,13 +6405,15 @@ export function baseDrift({ cwd = ROOT } = {}) { * WHICH step of the measurement failed — or `null` when a reading was taken, * whatever its value. * - * `baseDrift` has TWO doors to "no reading was taken", and to a reader they are - * one state. The base ref may not resolve (`base: null` — a fresh checkout, a - * clone nobody fetched, a graft), or the ref may resolve and the DISTANCE from - * it be unreadable (`behind: null`): `rev-list --count` fails on an unborn - * HEAD, which an ordinary fully-fetched clone reaches with one ordinary - * command, and also on a git that dies mid-run or a count that comes back - * non-numeric. + * `baseDrift` has THREE doors to "no reading was taken", and to a reader they + * are one state. The base ref may not resolve (`base: null` — a fresh + * checkout, a clone nobody fetched, a graft), the ref may resolve and the + * DISTANCE from it be unreadable (`behind: null`): `rev-list --count` fails on + * an unborn HEAD, which an ordinary fully-fetched clone reaches with one + * ordinary command, and also on a git that dies mid-run or a count that comes + * back non-numeric — or the distance may read and the CHANGED SET not + * (`changed: null`): the three-dot diff needs a merge base the checkout may + * not hold, which a shallow clone reaches with one shallow fetch. * * The second door used to fall through to `!drift.behind` and render * byte-identically to `behind: 0` — the same collapse the first door was fixed @@ -6403,9 +6434,11 @@ export function baseDrift({ cwd = ROOT } = {}) { * silent below. */ function unmeasuredDrift(drift) { + const distanceUnknown = `This tree's distance from ${DEFAULT_BASE_REF} is UNKNOWN. Not zero: no reading was taken.`; if (drift.base === null) { return { what: `${DEFAULT_BASE_REF} does not resolve in this checkout`, + unknown: distanceUnknown, how: 'A fresh checkout, a clone nobody fetched or a graft all reach here.', fix: `Run 'git fetch ${DEFAULT_BASE_REMOTE} ${DEFAULT_BASE_BRANCH}' and derive again for a reading.`, }; @@ -6413,19 +6446,57 @@ function unmeasuredDrift(drift) { if (!Number.isFinite(drift.behind)) { return { what: `${DEFAULT_BASE_REF} resolves here (${drift.base}), but counting from HEAD to it failed`, + unknown: distanceUnknown, how: `An unborn HEAD — 'git checkout --orphan', or a ref fetched into a repo holding no commit of its own — a git that died mid-run, or a non-numeric count all reach here.`, fix: `Run 'git rev-list --count HEAD..${DEFAULT_BASE_REF}' here to see which, then derive again for a reading.`, }; } + // The THIRD door (#13392): the ref resolves, the distance reads, and the diff + // that names WHICH files moved across it does not. This is the least safe of + // the three to be silent about, because it used to collapse into + // `changed: []` and render as the quiet visible-range-clear sentence — a + // reassurance manufactured from a failed read. It is a state of ordinary + // working checkouts, not of broken ones: a shallow clone plus one shallow + // fetch counts a distance fine and has no merge base for the three-dot form + // (measured — and this fleet's containers clone shallow). + if (!Array.isArray(drift.changed)) { + return { + what: `${DEFAULT_BASE_REF} resolves here (${drift.base}) and HEAD counts at least ${drift.behind} commit(s) behind it, but reading WHICH files changed across that range failed`, + unknown: `The changed set is UNKNOWN. Not empty: no reading was taken.`, + how: `A shallow checkout holding no merge base — one shallow fetch after a '--depth' clone — or a git that died mid-run reach here.`, + fix: `Run 'git diff --name-only HEAD...${DEFAULT_BASE_REF} -- ${DERIVATION_SURFACE.join(' ')}' here to see why, then derive again for a reading.`, + }; + } return null; } /** - * Render the drift. Loud when it can have changed the answer, quiet when it - * demonstrably cannot, and SILENT at zero — the last one for the same reason - * the banner has no "all paths present" twin: against a base ref nobody - * refreshed, a clean bill of health is precisely the reading the measured - * failure would have passed. + * Render the drift. Loud when it can have changed the answer, scoped and + * self-limiting when the VISIBLE range is clear, and SILENT at zero — the last + * one for the same reason the banner has no "all paths present" twin: against + * a base ref nobody refreshed, a clean bill of health is precisely the reading + * the measured failure would have passed. + * + * The quiet branch used to be "quiet when it demonstrably cannot [have changed + * the answer]", and that classification was measured false (#13392). It + * printed "nothing this answer derives from changed across that range" from a + * reading whose range ends at the last-fetched snapshot, and a reader takes + * "that range" to reach upstream. On the incident run the snapshot was at most + * ~13 minutes old and the visible reading exact — `behind: 1`, one off-surface + * commit — yet by the time CI consumed the answer, upstream had landed four + * derivation-surface commits the sentence had vouched could not exist, one + * carrying the family whose absence turned CI red. The dev who read the line + * did not ignore a warning; it COMPLIED with one. That is worse than the loud + * case being missed: a false reassurance recruits the reader's trust against + * them. And no local instrument fixes it — a freshness gate on the snapshot + * would have called that base fresh and reassured anyway, because + * fresh-at-derivation is not true-at-consumption against a queue that lands + * every few minutes. So the quiet branch now states exactly what it measured + * (the VISIBLE commits are surface-clear), states the half it cannot measure + * as untellable rather than clear, and hands over the fetch. An "I cannot + * tell" that is true beats a "nothing changed" that is sometimes false — the + * sentence a reader can safely comply with is the only kind this tool may + * print. * * That silence at zero is what makes the UNMEASURABLE case a defect rather * than a fourth flavour of quiet. `baseDrift` degrades a field to null in two @@ -6456,7 +6527,7 @@ export function driftLines(drift) { const unmeasured = unmeasuredDrift(drift); if (unmeasured) { return [ - ` ⚠️ STALENESS NOT MEASURED — ${unmeasured.what}. This tree's distance from ${DEFAULT_BASE_REF} is UNKNOWN. Not zero: no reading was taken.`, + ` ⚠️ STALENESS NOT MEASURED — ${unmeasured.what}. ${unmeasured.unknown}`, ` ${unmeasured.how} An unmeasured tree is where the families below are LEAST trustworthy, not most. ${unmeasured.fix}`, ]; } @@ -6464,7 +6535,10 @@ export function driftLines(drift) { const { behind, base, changed, headDate, baseDate } = drift; const span = `HEAD${headDate ? ` ${headDate}` : ''} vs ${DEFAULT_BASE_REF} ${base}${baseDate ? ` ${baseDate}` : ''}`; if (changed.length === 0) { - return [` At least ${behind} commit(s) behind ${DEFAULT_BASE_REF}, but nothing this answer derives from changed across that range — ${span}.`]; + 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}.`, + ` 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.`, + ]; } return [ ` ⚠️ STALE TREE — this answer is derived from a tree at least ${behind} commit(s) behind ${DEFAULT_BASE_REF}, and ${changed.length} file(s) it derives from CHANGED across that range.`, @@ -11017,8 +11091,43 @@ function selfTest() { t('a drift carrying no distance FIELD at all reads unmeasured too — absent is not a reading either', driftLines({ base: 'aaaaaaa', changed: [] }).join('\n').includes('STALENESS NOT MEASURED')); const benign = driftLines({ base: 'aaaaaaa', behind: 7, changed: [], headDate: '2026-01-01T00:00:00Z', baseDate: '2026-01-02T00:00:00Z' }); - t('behind, but with the derivation surface untouched, states the distance in ONE quiet line', benign.length === 1 && benign[0].includes('7 commit(s) behind')); - t('and that quiet line does not cry stale, so the loud spelling stays rare', !benign.join('\n').includes('STALE TREE')); + const benignText = benign.join('\n'); + t('behind with the VISIBLE surface untouched states the distance, and scopes the claim to what the tree can see', + benign.length === 2 && benign[0].includes('7 commit(s) behind') && benign[0].includes('can SEE')); + t('and it states the unseen half as untellable instead of clear — the sentence a reader may safely comply with', + benignText.includes('cannot tell') && benignText.includes('LOCAL snapshot')); + // The departure pin for the measured false reassurance (#13392). The old + // spelling asserted "nothing this answer derives from changed across that + // range" from a reading whose range ends at the last fetch; a dev complied + // with it and CI reddened on a family that landed upstream inside the gap + // the sentence had vouched empty. The length-and-content conjunct is what + // keeps this from passing vacuously — an empty render also contains no + // reassurance, and that species of green pin is the one this block already + // buried once. + t('the reassurance spelling is GONE — no quiet line asserts that nothing this answer derives from changed', + 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 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 + // read, the least earned of all. + const unreadSet = driftLines({ base: 'aaaaaaa', behind: 7, changed: null, headDate: '2026-01-01T00:00:00Z', baseDate: '2026-01-02T00:00:00Z' }); + const unreadSetText = unreadSet.join('\n'); + t('a distance that READS beside a changed set that does NOT refuses as a third unmeasured door, never as quiet', + unreadSet.length === 2 && unreadSetText.includes('STALENESS NOT MEASURED') && unreadSetText.includes('Not empty')); + t('it names the base it resolved AND the distance it counted, so a reader can tell WHICH step failed this time', + unreadSetText.includes('aaaaaaa') && unreadSetText.includes('7 commit(s)')); + t('its remedy is the diff — not the fetch, not the count', + unreadSetText.includes(`git diff --name-only HEAD...${DEFAULT_BASE_REF}`) + && !unreadSetText.includes(`git fetch ${DEFAULT_BASE_REMOTE} ${DEFAULT_BASE_BRANCH}`) + && !unreadSetText.includes('git rev-list --count')); + t('and it neither reassures nor cries stale — no reading, no claim in either direction', + unreadSet.length === 2 && !unreadSetText.includes('can SEE') && !unreadSetText.includes('STALE TREE')); + t('so the three unmeasured doors are told apart rather than flattened by the shared predicate', + new Set([unmeasuredText, uncountedText, unreadSetText]).size === 3); + t('a drift carrying no changed FIELD at all reads unmeasured too — absent is not a reading either', + driftLines({ base: 'aaaaaaa', behind: 7 }).join('\n').includes('STALENESS NOT MEASURED')); const loud = driftLines({ base: 'aaaaaaa', behind: 120, changed: ['scripts/pm/dispatch-gates.mjs', '.github/workflows/lint.yml'], headDate: '2026-01-01T00:00:00Z', baseDate: '2026-01-08T00:00:00Z' }); const loudText = loud.join('\n'); t('a changed derivation surface is LOUD, and names what it compared', loudText.includes('STALE TREE') && loudText.includes('HEAD') && loudText.includes(DEFAULT_BASE_REF) && loudText.includes('120 commit(s)')); @@ -11047,7 +11156,7 @@ function selfTest() { // above only describe. Its reading must not be the zero the clone reads. const unresolvableRepo = baseDrift({ cwd: up }); t('a checkout with no such remote measures NO base, and does not fall back to zero', - unresolvableRepo.base === null && unresolvableRepo.behind === null); + unresolvableRepo.base === null && unresolvableRepo.behind === null && unresolvableRepo.changed === null); t('and from a real repo too it arrives as a sentence, not as the silence the level clone gets', driftLines(unresolvableRepo).join('\n').includes('STALENESS NOT MEASURED') && driftLines(baseDrift({ cwd: clone })).length === 0); @@ -11062,8 +11171,8 @@ function selfTest() { gd(['remote', 'add', DEFAULT_BASE_REMOTE, up], unborn); gd(['fetch', '-q', DEFAULT_BASE_REMOTE, `${DEFAULT_BASE_BRANCH}:refs/remotes/${DEFAULT_BASE_REF}`], unborn); const unbornRepo = baseDrift({ cwd: unborn }); - t('a checkout whose base ref RESOLVES but whose own HEAD is unborn measures a base and NO distance', - typeof unbornRepo.base === 'string' && unbornRepo.behind === null); + t('a checkout whose base ref RESOLVES but whose own HEAD is unborn measures a base, NO distance and NO changed set', + typeof unbornRepo.base === 'string' && unbornRepo.behind === null && unbornRepo.changed === null); t('and that door speaks from a real repo too, rather than reading as the silence the level clone gets', driftLines(unbornRepo).join('\n').includes('STALENESS NOT MEASURED') && driftLines(baseDrift({ cwd: clone })).length === 0); @@ -11085,7 +11194,8 @@ function selfTest() { gd(['fetch', '-q', DEFAULT_BASE_REMOTE], clone); const offSurface = baseDrift({ cwd: clone }); 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 leaves the loud list empty', offSurface.changed.length === 0 && driftLines(offSurface).length === 1); + 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')); // Now upstream moves a file the answer IS derived from — the measured shape. mkdirSync(join(up, 'scripts'), { recursive: true }); writeFileSync(join(up, 'scripts', 'check-thing.mjs'), 'export const a = 1;\n'); @@ -11105,6 +11215,32 @@ function selfTest() { typeof orphaned.base === 'string' && orphaned.behind === null); t('and the clone that measured a distance one command ago says so instead of falling silent', driftLines(orphaned).join('\n').includes('STALENESS NOT MEASURED')); + // The THIRD door, measured from a real repo rather than hand-built + // (#13392): a SHALLOW clone. Depth-1 cloning is how this fleet's own + // containers arrive, which is what makes this door a state of production + // checkouts and not of repos built to show it. One shallow fetch after + // upstream moves leaves the distance countable — the fetched tip is + // visible — while the three-dot diff dies with no merge base, because the + // shallow boundary cut it out of the checkout. Under the pre-fix shape + // this exact state collapsed into `changed: []` and rendered the quiet + // clear sentence from a FAILED read — so the last assertion here is the + // required red: it fails if a reassurance can ever again be manufactured + // without an established reading. + const shallow = join(driftTmp, 'shallow'); + gd(['clone', '-q', '--depth', '1', `file://${up}`, shallow], driftTmp); + t('a fresh shallow clone still counts a distance fine — shallowness alone breaks nothing (positive control)', + baseDrift({ cwd: shallow }).behind === 0); + writeFileSync(join(up, 'scripts', 'check-thing.mjs'), 'export const a = 3;\n'); + gd(['add', '-A'], up); gd(['commit', '-qm', 'move a check script beyond the shallow boundary'], up); + gd(['fetch', '-q', '--depth', '1', DEFAULT_BASE_REMOTE], shallow); + const shallowRepo = baseDrift({ cwd: shallow }); + t('one shallow fetch later the distance still READS and the changed set does NOT — null, never an empty array', + shallowRepo.behind >= 1 && shallowRepo.changed === null); + const shallowText = driftLines(shallowRepo).join('\n'); + t('and that run REFUSES from the real repo too, naming the changed set as the step that failed', + shallowText.includes('STALENESS NOT MEASURED') && shallowText.includes('Not empty')); + t('with an upstream SURFACE commit sitting in the unreadable range right now, no line reassures — not the visible-range sentence, not the retired unqualified one', + !shallowText.includes('can SEE') && !shallowText.includes('nothing this answer derives from changed')); } finally { rmSync(driftTmp, { recursive: true, force: true }); }