Merged
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
156 changes: 149 additions & 7 deletions scripts/pm/dispatch-gates.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -9730,9 +9730,20 @@ export function parseRunRecord(text) {
* heading) and which deduplicates the two spellings of one family into one
* command before this function ever sees them. What the tool CAN still meet in
* a record it classifies itself — a CI-measured-only family, which `commandsFor`
* subtracts by design, and a pending-changeset family, derived against a path
* that did not exist at derivation time. Both are matched byte-exactly against
* sets this same derivation produced.
* subtracts by design; a VALUE-BEARING family, which `commandsFor` subtracts for
* the same reason one step later because its argv takes a value from the
* workflow (#15083); and a pending-changeset family, derived against a path
* that did not exist at derivation time. All three are matched byte-exactly
* against sets this same derivation produced.
*
* ⭐ The third bucket is not a convenience: this file's own rule is that an
* omission is disclosed WHERE the omission happens, and a class `commandsFor`
* deliberately withholds is a class the runner cannot be expected to have
* derived. Left in the remainder it reads as a command "named by nothing this
* run derived" — the one sentence that is false about it, because this run
* derived it and then classified it out. The likeliest recorder is a dev who
* writes the bare spelling of one of these scripts out of habit, and what they
* are owed is the reason, not a shrug (#15115).
*
* What is left for the runner to explain is therefore only what the tool
* genuinely cannot know: a gate that refused with its own prerequisite. That is
Expand All@@ -9751,6 +9762,12 @@ export function parseRunRecord(text) {
export function runReconciliation({
derived = [],
ciOnlyCommands = new Set(),
// Beside `ciOnlyCommands` and not after `pendingCommands`, because the file
// already groups them: these are the TWO subtractions `commandsFor` makes
// from the runnable union, in that order, and the pending families are a
// different fact (a path that did not exist at derivation time). Defaulting
// to empty keeps every existing caller's verdict byte-identical.
notRunnableCommands = new Set(),
pendingCommands = new Set(),
record = [],
} = {}) {
Expand DownExpand Up@@ -9792,6 +9809,7 @@ export function runReconciliation({
}

const explainedCiOnly = [];
const explainedNotRunnable = [];
const explainedPending = [];
const extra = [];
const nearMiss = [];
Expand All@@ -9804,6 +9822,14 @@ export function runReconciliation({
explainedCiOnly.push(entry.command);
continue;
}
// The order is the precedence, and it is the one `commandsFor` already
// states: `ciOnly` is the FIRST subtraction, so a family that were somehow
// both is reported as CI-measured — one bucket per command, chosen the same
// way in both places rather than two counts for one omission.
if (notRunnableCommands.has(entry.command)) {
explainedNotRunnable.push(entry.command);
continue;
}
if (pendingCommands.has(entry.command)) {
explainedPending.push(entry.command);
continue;
Expand All@@ -9823,6 +9849,7 @@ export function runReconciliation({
unrun,
notMeasured,
explainedCiOnly: explainedCiOnly.sort(),
explainedNotRunnable: explainedNotRunnable.sort(),
explainedPending: explainedPending.sort(),
extra: extra.sort(),
nearMiss,
Expand DownExpand Up@@ -9885,6 +9912,13 @@ export function runReconciliationLines(recon) {
);
for (const command of recon.explainedCiOnly) lines.push(` - ${command}`);
}
if (recon.explainedNotRunnable.length > 0) {
lines.push(
` Classified by this tool, no explanation owed (${recon.explainedNotRunnable.length}) — VALUE-BEARING famil(ies):`
+ ' its argv takes a value from the workflow, so it is recorded, not derived as runnable:',
);
for (const command of recon.explainedNotRunnable) lines.push(` - ${command}`);
}
if (recon.explainedPending.length > 0) {
lines.push(
` Classified by this tool, no explanation owed (${recon.explainedPending.length}) — pending-changeset famil(ies), derived against a path that did not exist at derivation time:`,
Expand DownExpand Up@@ -10159,9 +10193,10 @@ function derive(paths, { showResidue = false, mode = 'human', runRecord = [] } =
const pending = pendingChangesetFamilies([...byCheck], new Set(matched.keys()));

if (mode === 'ran') {
// Built from the SAME three expressions the other renderings read, in this
// process, on this tree: the union `--commands` prints, the CI-measured set
// that union subtracts, and the pending families it holds back. A second
// Built from the SAME four expressions the other renderings read, in this
// process, on this tree: the union `--commands` prints, the two sets that
// union subtracts — CI-measured, and value-bearing — and the pending
// families it holds back. A second
// traversal here would be a second answer to a question this file already
// answers once — and it would be the answer the reconciliation is judged
// against, which is the worst possible place to keep a duplicate.
Expand All@@ -10172,6 +10207,11 @@ function derive(paths, { showResidue = false, mode = 'human', runRecord = [] } =
// makes it a contract rather than a note (#14189).
derived: commandsFor({ matchedRows, kindGroups, alwaysRunsRows }),
ciOnlyCommands: ciOnlyCommandSet(matchedRows, alwaysRunsRows),
// The SECOND set `commandsFor` subtracts, read from the SAME rows by the
// SAME expression it uses — so the union and the reconciliation cannot
// drift about which invocations are withheld, exactly as they cannot for
// the CI-measured set above it (#15115).
notRunnableCommands: notRunnableCommandSet(matchedRows, alwaysRunsRows),
pendingCommands: new Set(pending.map(({ entry }) => runnableInvocation(entry))),
record: runRecord,
});
Expand DownExpand Up@@ -18770,16 +18810,51 @@ function selfTest() {
t('the malformed line is reported against its line number too', unexplained.malformed.length === 1 && unexplained.malformed[0].line === 1);

// ── What the TOOL classifies, so no prose has to ────────────────────────
// ⭐ The value-bearing spelling is the LIVE one — `pr-automation.yml`
// really passes `--base "$MERGE_BASE"` to this script — for the reason the
// neighbouring cases take theirs from the live workflows: a fixture
// invented here would keep passing after the renderer that produces the
// real one changed shape, which is the failure this whole file is about.
const valueBearing = 'node scripts/check-empty-changeset.mjs --base "$MERGE_BASE"';
const explained = runReconciliation({
derived: ['pnpm check:a'],
ciOnlyCommands: new Set(['node scripts/check-payload-guard.mjs']),
notRunnableCommands: new Set([valueBearing]),
pendingCommands: new Set(['pnpm check:changeset-shape']),
record: parseRunRecord(['pnpm check:a', 'node scripts/check-payload-guard.mjs', 'pnpm check:changeset-shape'].join('\n')),
record: parseRunRecord(['pnpm check:a', 'node scripts/check-payload-guard.mjs', valueBearing, 'pnpm check:changeset-shape'].join('\n')),
});
t(
'a CI-measured-only entry and a pending-changeset entry are classified by the tool, not dumped into the remainder',
explained.ok && explained.explainedCiOnly.length === 1 && explained.explainedPending.length === 1 && explained.extra.length === 0,
);
// ⭐ #15115: the THIRD class `commandsFor` withholds gets the same
// courtesy. Recorded, derived by this run, classified out of the union —
// so `extra`'s caption ("named by nothing this run derived") would be the
// one sentence that is false about it.
t(
'a recorded VALUE-BEARING family is classified by the tool too, never folded into the remainder',
explained.explainedNotRunnable.length === 1 && explained.explainedNotRunnable[0] === valueBearing && !explained.extra.includes(valueBearing),
);
// CONTROL, and it is the load-bearing half: the bucket explains the class
// it was given and nothing else. A command named by no set is still
// `extra` — a third bucket that swallowed unknowns would have deleted the
// remainder rather than shrunk it.
const unknownBeside = runReconciliation({
derived: ['pnpm check:a'],
notRunnableCommands: new Set([valueBearing]),
record: parseRunRecord(['pnpm check:a', valueBearing, 'pnpm check:not-a-family'].join('\n')),
});
t(
'and an unknown command beside it still reads `extra` — the new bucket explains its class only',
unknownBeside.explainedNotRunnable.length === 1 && unknownBeside.extra.length === 1 && unknownBeside.extra[0] === 'pnpm check:not-a-family',
);
// The bucket is DIAGNOSTIC, exactly like the two beside it: the verdict
// reads `unrun` and nothing else, so classifying an entry can never move
// it in either direction (#15115).
t(
'the new bucket cannot move the verdict — it is diagnostic, like the two beside it',
unknownBeside.ok && unknownBeside.unrun.length === 0 && unknownBeside.ran.length === 1,
);

// ── Bookkeeping the classes cannot lose ─────────────────────────────────
const dupes = runReconciliation({ derived: ['pnpm check:a'], record: parseRunRecord(['pnpm check:a', 'pnpm check:a'].join('\n')) });
Expand DownExpand Up@@ -18823,6 +18898,21 @@ function selfTest() {
'the near-miss line refuses the pairing out loud rather than quietly',
runReconciliationLines(nearMiss).join('\n').includes('is NOT paired with it'),
);
// ⭐ #15115, in the rendering: a bucket that classified an entry and then
// printed nothing would leave the runner exactly where `extra` left them.
const explainedText = runReconciliationLines(explained).join('\n');
t(
'the value-bearing bucket gets its OWN labelled line, naming the reason and the command',
explainedText.includes('VALUE-BEARING famil(ies)')
&& explainedText.includes('its argv takes a value from the workflow')
&& explainedText.includes(valueBearing),
);
t(
'and the two buckets beside it keep their own lines, with the remainder heading absent entirely',
explainedText.includes('CI-MEASURED ONLY')
&& explainedText.includes('pending-changeset famil(ies)')
&& !explainedText.includes("Outside this card's derivation"),
);

// ── argv: a two-token flag's value must not become a path ───────────────
const ranSplit = splitArgv([RAN_FLAG, 'ran.list', 'packages/spec/src/index.ts', '--residue']);
Expand DownExpand Up@@ -18891,6 +18981,58 @@ function selfTest() {
}
}

// ── END TO END: the VALUE-BEARING bucket is WIRED, not merely present (#15115) ──
//
// Every unit case above stays green if the `--ran` call site never PASSES
// the value-bearing set — a parameter that defaults to empty is exactly the
// shape that keeps its own tests green while the live mode still dumps the
// class in the remainder, which is the state this card was filed about.
// Only a real run reads the wiring.
//
// The fixture is this tool's OWN answer on this tree, read from `--json`,
// rather than an invocation typed here: a hardcoded spelling would keep
// passing after the renderer that produces the real one changed shape, and
// the two sides of the comparison would stop being the same strings — the
// property the whole `--ran` design rests on.
{
const vbTmp = mkdtempSync(nodePath.join(tmpdir(), 'dg-ran-vb-'));
try {
// A changeset path, because that is what reaches the live value-bearing
// families — and it is the tool's own constant rather than a spelling
// this test invented.
const vbCard = CHANGESET_PROBE_PATH;
const jsonRun = runCli(['--json', vbCard]);
const doc = jsonRun.status === 0 ? JSON.parse(jsonRun.stdout ?? '{}') : null;
const vbRows = [...(doc?.matched ?? []), ...(doc?.alwaysRunsPopulation ?? [])].filter((row) => row.notRunnable);
t('CONTROL: this tree still derives at least one VALUE-BEARING family for a changeset path', Boolean(doc) && vbRows.length >= 1);
if (doc && vbRows.length >= 1) {
const vbCommand = vbRows[0].command;
t('CONTROL: and the runnable union WITHHOLDS it — which is the whole reason the bucket exists', !doc.commands.includes(vbCommand));
const vbRecord = nodePath.join(vbTmp, 'ran-value-bearing.list');
writeFileSync(vbRecord, `${[...doc.commands, vbCommand].join('\n')}\n`);
const vbRun = runCli([RAN_FLAG, vbRecord, vbCard]);
const vbOut = vbRun.stdout ?? '';
t(
'⭐ a real run that RECORDS it lands it in the VALUE-BEARING bucket, with the remainder heading gone entirely',
vbRun.status === 0
&& vbOut.includes('VALUE-BEARING famil(ies)')
&& vbOut.includes(vbCommand)
&& !vbOut.includes("Outside this card's derivation"),
);
t(
'and the reason travels with it, so the runner learns why this invocation is not one they could have derived',
vbOut.includes('its argv takes a value from the workflow'),
);
t(
'while the verdict is unmoved — the bucket is diagnostic, and every derived family is still accounted for',
vbOut.includes(`${doc.commands.length} derived famil(ies) accounted for`),
);
}
} finally {
rmSync(vbTmp, { recursive: true, force: true });
}
}

// The per-case line already printed inside `t()`, streamed as each verdict
// was decided (#14281) — this tail is the summary only, unchanged in shape
// and wording from the pre-streaming version.
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all \u003cpre\u003e\u003ccode\u003e blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks"); } } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); } })(); (function(){ try { var __m = "github.com"; var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Merged
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
156 changes: 149 additions & 7 deletions scripts/pm/dispatch-gates.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -9730,9 +9730,20 @@ export function parseRunRecord(text) {
* heading) and which deduplicates the two spellings of one family into one
* command before this function ever sees them. What the tool CAN still meet in
* a record it classifies itself — a CI-measured-only family, which `commandsFor`
* subtracts by design, and a pending-changeset family, derived against a path
* that did not exist at derivation time. Both are matched byte-exactly against
* sets this same derivation produced.
* subtracts by design; a VALUE-BEARING family, which `commandsFor` subtracts for
* the same reason one step later because its argv takes a value from the
* workflow (#15083); and a pending-changeset family, derived against a path
* that did not exist at derivation time. All three are matched byte-exactly
* against sets this same derivation produced.
*
* ⭐ The third bucket is not a convenience: this file's own rule is that an
* omission is disclosed WHERE the omission happens, and a class `commandsFor`
* deliberately withholds is a class the runner cannot be expected to have
* derived. Left in the remainder it reads as a command "named by nothing this
* run derived" — the one sentence that is false about it, because this run
* derived it and then classified it out. The likeliest recorder is a dev who
* writes the bare spelling of one of these scripts out of habit, and what they
* are owed is the reason, not a shrug (#15115).
*
* What is left for the runner to explain is therefore only what the tool
* genuinely cannot know: a gate that refused with its own prerequisite. That is
Expand All@@ -9751,6 +9762,12 @@ export function parseRunRecord(text) {
export function runReconciliation({
derived = [],
ciOnlyCommands = new Set(),
// Beside `ciOnlyCommands` and not after `pendingCommands`, because the file
// already groups them: these are the TWO subtractions `commandsFor` makes
// from the runnable union, in that order, and the pending families are a
// different fact (a path that did not exist at derivation time). Defaulting
// to empty keeps every existing caller's verdict byte-identical.
notRunnableCommands = new Set(),
pendingCommands = new Set(),
record = [],
} = {}) {
Expand DownExpand Up@@ -9792,6 +9809,7 @@ export function runReconciliation({
}

const explainedCiOnly = [];
const explainedNotRunnable = [];
const explainedPending = [];
const extra = [];
const nearMiss = [];
Expand All@@ -9804,6 +9822,14 @@ export function runReconciliation({
explainedCiOnly.push(entry.command);
continue;
}
// The order is the precedence, and it is the one `commandsFor` already
// states: `ciOnly` is the FIRST subtraction, so a family that were somehow
// both is reported as CI-measured — one bucket per command, chosen the same
// way in both places rather than two counts for one omission.
if (notRunnableCommands.has(entry.command)) {
explainedNotRunnable.push(entry.command);
continue;
}
if (pendingCommands.has(entry.command)) {
explainedPending.push(entry.command);
continue;
Expand All@@ -9823,6 +9849,7 @@ export function runReconciliation({
unrun,
notMeasured,
explainedCiOnly: explainedCiOnly.sort(),
explainedNotRunnable: explainedNotRunnable.sort(),
explainedPending: explainedPending.sort(),
extra: extra.sort(),
nearMiss,
Expand DownExpand Up@@ -9885,6 +9912,13 @@ export function runReconciliationLines(recon) {
);
for (const command of recon.explainedCiOnly) lines.push(` - ${command}`);
}
if (recon.explainedNotRunnable.length > 0) {
lines.push(
` Classified by this tool, no explanation owed (${recon.explainedNotRunnable.length}) — VALUE-BEARING famil(ies):`
+ ' its argv takes a value from the workflow, so it is recorded, not derived as runnable:',
);
for (const command of recon.explainedNotRunnable) lines.push(` - ${command}`);
}
if (recon.explainedPending.length > 0) {
lines.push(
` Classified by this tool, no explanation owed (${recon.explainedPending.length}) — pending-changeset famil(ies), derived against a path that did not exist at derivation time:`,
Expand DownExpand Up@@ -10159,9 +10193,10 @@ function derive(paths, { showResidue = false, mode = 'human', runRecord = [] } =
const pending = pendingChangesetFamilies([...byCheck], new Set(matched.keys()));

if (mode === 'ran') {
// Built from the SAME three expressions the other renderings read, in this
// process, on this tree: the union `--commands` prints, the CI-measured set
// that union subtracts, and the pending families it holds back. A second
// Built from the SAME four expressions the other renderings read, in this
// process, on this tree: the union `--commands` prints, the two sets that
// union subtracts — CI-measured, and value-bearing — and the pending
// families it holds back. A second
// traversal here would be a second answer to a question this file already
// answers once — and it would be the answer the reconciliation is judged
// against, which is the worst possible place to keep a duplicate.
Expand All@@ -10172,6 +10207,11 @@ function derive(paths, { showResidue = false, mode = 'human', runRecord = [] } =
// makes it a contract rather than a note (#14189).
derived: commandsFor({ matchedRows, kindGroups, alwaysRunsRows }),
ciOnlyCommands: ciOnlyCommandSet(matchedRows, alwaysRunsRows),
// The SECOND set `commandsFor` subtracts, read from the SAME rows by the
// SAME expression it uses — so the union and the reconciliation cannot
// drift about which invocations are withheld, exactly as they cannot for
// the CI-measured set above it (#15115).
notRunnableCommands: notRunnableCommandSet(matchedRows, alwaysRunsRows),
pendingCommands: new Set(pending.map(({ entry }) => runnableInvocation(entry))),
record: runRecord,
});
Expand DownExpand Up@@ -18770,16 +18810,51 @@ function selfTest() {
t('the malformed line is reported against its line number too', unexplained.malformed.length === 1 && unexplained.malformed[0].line === 1);

// ── What the TOOL classifies, so no prose has to ────────────────────────
// ⭐ The value-bearing spelling is the LIVE one — `pr-automation.yml`
// really passes `--base "$MERGE_BASE"` to this script — for the reason the
// neighbouring cases take theirs from the live workflows: a fixture
// invented here would keep passing after the renderer that produces the
// real one changed shape, which is the failure this whole file is about.
const valueBearing = 'node scripts/check-empty-changeset.mjs --base "$MERGE_BASE"';
const explained = runReconciliation({
derived: ['pnpm check:a'],
ciOnlyCommands: new Set(['node scripts/check-payload-guard.mjs']),
notRunnableCommands: new Set([valueBearing]),
pendingCommands: new Set(['pnpm check:changeset-shape']),
record: parseRunRecord(['pnpm check:a', 'node scripts/check-payload-guard.mjs', 'pnpm check:changeset-shape'].join('\n')),
record: parseRunRecord(['pnpm check:a', 'node scripts/check-payload-guard.mjs', valueBearing, 'pnpm check:changeset-shape'].join('\n')),
});
t(
'a CI-measured-only entry and a pending-changeset entry are classified by the tool, not dumped into the remainder',
explained.ok && explained.explainedCiOnly.length === 1 && explained.explainedPending.length === 1 && explained.extra.length === 0,
);
// ⭐ #15115: the THIRD class `commandsFor` withholds gets the same
// courtesy. Recorded, derived by this run, classified out of the union —
// so `extra`'s caption ("named by nothing this run derived") would be the
// one sentence that is false about it.
t(
'a recorded VALUE-BEARING family is classified by the tool too, never folded into the remainder',
explained.explainedNotRunnable.length === 1 && explained.explainedNotRunnable[0] === valueBearing && !explained.extra.includes(valueBearing),
);
// CONTROL, and it is the load-bearing half: the bucket explains the class
// it was given and nothing else. A command named by no set is still
// `extra` — a third bucket that swallowed unknowns would have deleted the
// remainder rather than shrunk it.
const unknownBeside = runReconciliation({
derived: ['pnpm check:a'],
notRunnableCommands: new Set([valueBearing]),
record: parseRunRecord(['pnpm check:a', valueBearing, 'pnpm check:not-a-family'].join('\n')),
});
t(
'and an unknown command beside it still reads `extra` — the new bucket explains its class only',
unknownBeside.explainedNotRunnable.length === 1 && unknownBeside.extra.length === 1 && unknownBeside.extra[0] === 'pnpm check:not-a-family',
);
// The bucket is DIAGNOSTIC, exactly like the two beside it: the verdict
// reads `unrun` and nothing else, so classifying an entry can never move
// it in either direction (#15115).
t(
'the new bucket cannot move the verdict — it is diagnostic, like the two beside it',
unknownBeside.ok && unknownBeside.unrun.length === 0 && unknownBeside.ran.length === 1,
);

// ── Bookkeeping the classes cannot lose ─────────────────────────────────
const dupes = runReconciliation({ derived: ['pnpm check:a'], record: parseRunRecord(['pnpm check:a', 'pnpm check:a'].join('\n')) });
Expand DownExpand Up@@ -18823,6 +18898,21 @@ function selfTest() {
'the near-miss line refuses the pairing out loud rather than quietly',
runReconciliationLines(nearMiss).join('\n').includes('is NOT paired with it'),
);
// ⭐ #15115, in the rendering: a bucket that classified an entry and then
// printed nothing would leave the runner exactly where `extra` left them.
const explainedText = runReconciliationLines(explained).join('\n');
t(
'the value-bearing bucket gets its OWN labelled line, naming the reason and the command',
explainedText.includes('VALUE-BEARING famil(ies)')
&& explainedText.includes('its argv takes a value from the workflow')
&& explainedText.includes(valueBearing),
);
t(
'and the two buckets beside it keep their own lines, with the remainder heading absent entirely',
explainedText.includes('CI-MEASURED ONLY')
&& explainedText.includes('pending-changeset famil(ies)')
&& !explainedText.includes("Outside this card's derivation"),
);

// ── argv: a two-token flag's value must not become a path ───────────────
const ranSplit = splitArgv([RAN_FLAG, 'ran.list', 'packages/spec/src/index.ts', '--residue']);
Expand DownExpand Up@@ -18891,6 +18981,58 @@ function selfTest() {
}
}

// ── END TO END: the VALUE-BEARING bucket is WIRED, not merely present (#15115) ──
//
// Every unit case above stays green if the `--ran` call site never PASSES
// the value-bearing set — a parameter that defaults to empty is exactly the
// shape that keeps its own tests green while the live mode still dumps the
// class in the remainder, which is the state this card was filed about.
// Only a real run reads the wiring.
//
// The fixture is this tool's OWN answer on this tree, read from `--json`,
// rather than an invocation typed here: a hardcoded spelling would keep
// passing after the renderer that produces the real one changed shape, and
// the two sides of the comparison would stop being the same strings — the
// property the whole `--ran` design rests on.
{
const vbTmp = mkdtempSync(nodePath.join(tmpdir(), 'dg-ran-vb-'));
try {
// A changeset path, because that is what reaches the live value-bearing
// families — and it is the tool's own constant rather than a spelling
// this test invented.
const vbCard = CHANGESET_PROBE_PATH;
const jsonRun = runCli(['--json', vbCard]);
const doc = jsonRun.status === 0 ? JSON.parse(jsonRun.stdout ?? '{}') : null;
const vbRows = [...(doc?.matched ?? []), ...(doc?.alwaysRunsPopulation ?? [])].filter((row) => row.notRunnable);
t('CONTROL: this tree still derives at least one VALUE-BEARING family for a changeset path', Boolean(doc) && vbRows.length >= 1);
if (doc && vbRows.length >= 1) {
const vbCommand = vbRows[0].command;
t('CONTROL: and the runnable union WITHHOLDS it — which is the whole reason the bucket exists', !doc.commands.includes(vbCommand));
const vbRecord = nodePath.join(vbTmp, 'ran-value-bearing.list');
writeFileSync(vbRecord, `${[...doc.commands, vbCommand].join('\n')}\n`);
const vbRun = runCli([RAN_FLAG, vbRecord, vbCard]);
const vbOut = vbRun.stdout ?? '';
t(
'⭐ a real run that RECORDS it lands it in the VALUE-BEARING bucket, with the remainder heading gone entirely',
vbRun.status === 0
&& vbOut.includes('VALUE-BEARING famil(ies)')
&& vbOut.includes(vbCommand)
&& !vbOut.includes("Outside this card's derivation"),
);
t(
'and the reason travels with it, so the runner learns why this invocation is not one they could have derived',
vbOut.includes('its argv takes a value from the workflow'),
);
t(
'while the verdict is unmoved — the bucket is diagnostic, and every derived family is still accounted for',
vbOut.includes(`${doc.commands.length} derived famil(ies) accounted for`),
);
}
} finally {
rmSync(vbTmp, { recursive: true, force: true });
}
}

// The per-case line already printed inside `t()`, streamed as each verdict
// was decided (#14281) — this tail is the summary only, unchanged in shape
// and wording from the pre-streaming version.
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
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
156 changes: 149 additions & 7 deletions scripts/pm/dispatch-gates.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -9730,9 +9730,20 @@ export function parseRunRecord(text) {
* heading) and which deduplicates the two spellings of one family into one
* command before this function ever sees them. What the tool CAN still meet in
* a record it classifies itself — a CI-measured-only family, which `commandsFor`
* subtracts by design, and a pending-changeset family, derived against a path
* that did not exist at derivation time. Both are matched byte-exactly against
* sets this same derivation produced.
* subtracts by design; a VALUE-BEARING family, which `commandsFor` subtracts for
* the same reason one step later because its argv takes a value from the
* workflow (#15083); and a pending-changeset family, derived against a path
* that did not exist at derivation time. All three are matched byte-exactly
* against sets this same derivation produced.
*
* ⭐ The third bucket is not a convenience: this file's own rule is that an
* omission is disclosed WHERE the omission happens, and a class `commandsFor`
* deliberately withholds is a class the runner cannot be expected to have
* derived. Left in the remainder it reads as a command "named by nothing this
* run derived" — the one sentence that is false about it, because this run
* derived it and then classified it out. The likeliest recorder is a dev who
* writes the bare spelling of one of these scripts out of habit, and what they
* are owed is the reason, not a shrug (#15115).
*
* What is left for the runner to explain is therefore only what the tool
* genuinely cannot know: a gate that refused with its own prerequisite. That is
Expand All@@ -9751,6 +9762,12 @@ export function parseRunRecord(text) {
export function runReconciliation({
derived = [],
ciOnlyCommands = new Set(),
// Beside `ciOnlyCommands` and not after `pendingCommands`, because the file
// already groups them: these are the TWO subtractions `commandsFor` makes
// from the runnable union, in that order, and the pending families are a
// different fact (a path that did not exist at derivation time). Defaulting
// to empty keeps every existing caller's verdict byte-identical.
notRunnableCommands = new Set(),
pendingCommands = new Set(),
record = [],
} = {}) {
Expand DownExpand Up@@ -9792,6 +9809,7 @@ export function runReconciliation({
}

const explainedCiOnly = [];
const explainedNotRunnable = [];
const explainedPending = [];
const extra = [];
const nearMiss = [];
Expand All@@ -9804,6 +9822,14 @@ export function runReconciliation({
explainedCiOnly.push(entry.command);
continue;
}
// The order is the precedence, and it is the one `commandsFor` already
// states: `ciOnly` is the FIRST subtraction, so a family that were somehow
// both is reported as CI-measured — one bucket per command, chosen the same
// way in both places rather than two counts for one omission.
if (notRunnableCommands.has(entry.command)) {
explainedNotRunnable.push(entry.command);
continue;
}
if (pendingCommands.has(entry.command)) {
explainedPending.push(entry.command);
continue;
Expand All@@ -9823,6 +9849,7 @@ export function runReconciliation({
unrun,
notMeasured,
explainedCiOnly: explainedCiOnly.sort(),
explainedNotRunnable: explainedNotRunnable.sort(),
explainedPending: explainedPending.sort(),
extra: extra.sort(),
nearMiss,
Expand DownExpand Up@@ -9885,6 +9912,13 @@ export function runReconciliationLines(recon) {
);
for (const command of recon.explainedCiOnly) lines.push(` - ${command}`);
}
if (recon.explainedNotRunnable.length > 0) {
lines.push(
` Classified by this tool, no explanation owed (${recon.explainedNotRunnable.length}) — VALUE-BEARING famil(ies):`
+ ' its argv takes a value from the workflow, so it is recorded, not derived as runnable:',
);
for (const command of recon.explainedNotRunnable) lines.push(` - ${command}`);
}
if (recon.explainedPending.length > 0) {
lines.push(
` Classified by this tool, no explanation owed (${recon.explainedPending.length}) — pending-changeset famil(ies), derived against a path that did not exist at derivation time:`,
Expand DownExpand Up@@ -10159,9 +10193,10 @@ function derive(paths, { showResidue = false, mode = 'human', runRecord = [] } =
const pending = pendingChangesetFamilies([...byCheck], new Set(matched.keys()));

if (mode === 'ran') {
// Built from the SAME three expressions the other renderings read, in this
// process, on this tree: the union `--commands` prints, the CI-measured set
// that union subtracts, and the pending families it holds back. A second
// Built from the SAME four expressions the other renderings read, in this
// process, on this tree: the union `--commands` prints, the two sets that
// union subtracts — CI-measured, and value-bearing — and the pending
// families it holds back. A second
// traversal here would be a second answer to a question this file already
// answers once — and it would be the answer the reconciliation is judged
// against, which is the worst possible place to keep a duplicate.
Expand All@@ -10172,6 +10207,11 @@ function derive(paths, { showResidue = false, mode = 'human', runRecord = [] } =
// makes it a contract rather than a note (#14189).
derived: commandsFor({ matchedRows, kindGroups, alwaysRunsRows }),
ciOnlyCommands: ciOnlyCommandSet(matchedRows, alwaysRunsRows),
// The SECOND set `commandsFor` subtracts, read from the SAME rows by the
// SAME expression it uses — so the union and the reconciliation cannot
// drift about which invocations are withheld, exactly as they cannot for
// the CI-measured set above it (#15115).
notRunnableCommands: notRunnableCommandSet(matchedRows, alwaysRunsRows),
pendingCommands: new Set(pending.map(({ entry }) => runnableInvocation(entry))),
record: runRecord,
});
Expand DownExpand Up@@ -18770,16 +18810,51 @@ function selfTest() {
t('the malformed line is reported against its line number too', unexplained.malformed.length === 1 && unexplained.malformed[0].line === 1);

// ── What the TOOL classifies, so no prose has to ────────────────────────
// ⭐ The value-bearing spelling is the LIVE one — `pr-automation.yml`
// really passes `--base "$MERGE_BASE"` to this script — for the reason the
// neighbouring cases take theirs from the live workflows: a fixture
// invented here would keep passing after the renderer that produces the
// real one changed shape, which is the failure this whole file is about.
const valueBearing = 'node scripts/check-empty-changeset.mjs --base "$MERGE_BASE"';
const explained = runReconciliation({
derived: ['pnpm check:a'],
ciOnlyCommands: new Set(['node scripts/check-payload-guard.mjs']),
notRunnableCommands: new Set([valueBearing]),
pendingCommands: new Set(['pnpm check:changeset-shape']),
record: parseRunRecord(['pnpm check:a', 'node scripts/check-payload-guard.mjs', 'pnpm check:changeset-shape'].join('\n')),
record: parseRunRecord(['pnpm check:a', 'node scripts/check-payload-guard.mjs', valueBearing, 'pnpm check:changeset-shape'].join('\n')),
});
t(
'a CI-measured-only entry and a pending-changeset entry are classified by the tool, not dumped into the remainder',
explained.ok && explained.explainedCiOnly.length === 1 && explained.explainedPending.length === 1 && explained.extra.length === 0,
);
// ⭐ #15115: the THIRD class `commandsFor` withholds gets the same
// courtesy. Recorded, derived by this run, classified out of the union —
// so `extra`'s caption ("named by nothing this run derived") would be the
// one sentence that is false about it.
t(
'a recorded VALUE-BEARING family is classified by the tool too, never folded into the remainder',
explained.explainedNotRunnable.length === 1 && explained.explainedNotRunnable[0] === valueBearing && !explained.extra.includes(valueBearing),
);
// CONTROL, and it is the load-bearing half: the bucket explains the class
// it was given and nothing else. A command named by no set is still
// `extra` — a third bucket that swallowed unknowns would have deleted the
// remainder rather than shrunk it.
const unknownBeside = runReconciliation({
derived: ['pnpm check:a'],
notRunnableCommands: new Set([valueBearing]),
record: parseRunRecord(['pnpm check:a', valueBearing, 'pnpm check:not-a-family'].join('\n')),
});
t(
'and an unknown command beside it still reads `extra` — the new bucket explains its class only',
unknownBeside.explainedNotRunnable.length === 1 && unknownBeside.extra.length === 1 && unknownBeside.extra[0] === 'pnpm check:not-a-family',
);
// The bucket is DIAGNOSTIC, exactly like the two beside it: the verdict
// reads `unrun` and nothing else, so classifying an entry can never move
// it in either direction (#15115).
t(
'the new bucket cannot move the verdict — it is diagnostic, like the two beside it',
unknownBeside.ok && unknownBeside.unrun.length === 0 && unknownBeside.ran.length === 1,
);

// ── Bookkeeping the classes cannot lose ─────────────────────────────────
const dupes = runReconciliation({ derived: ['pnpm check:a'], record: parseRunRecord(['pnpm check:a', 'pnpm check:a'].join('\n')) });
Expand DownExpand Up@@ -18823,6 +18898,21 @@ function selfTest() {
'the near-miss line refuses the pairing out loud rather than quietly',
runReconciliationLines(nearMiss).join('\n').includes('is NOT paired with it'),
);
// ⭐ #15115, in the rendering: a bucket that classified an entry and then
// printed nothing would leave the runner exactly where `extra` left them.
const explainedText = runReconciliationLines(explained).join('\n');
t(
'the value-bearing bucket gets its OWN labelled line, naming the reason and the command',
explainedText.includes('VALUE-BEARING famil(ies)')
&& explainedText.includes('its argv takes a value from the workflow')
&& explainedText.includes(valueBearing),
);
t(
'and the two buckets beside it keep their own lines, with the remainder heading absent entirely',
explainedText.includes('CI-MEASURED ONLY')
&& explainedText.includes('pending-changeset famil(ies)')
&& !explainedText.includes("Outside this card's derivation"),
);

// ── argv: a two-token flag's value must not become a path ───────────────
const ranSplit = splitArgv([RAN_FLAG, 'ran.list', 'packages/spec/src/index.ts', '--residue']);
Expand DownExpand Up@@ -18891,6 +18981,58 @@ function selfTest() {
}
}

// ── END TO END: the VALUE-BEARING bucket is WIRED, not merely present (#15115) ──
//
// Every unit case above stays green if the `--ran` call site never PASSES
// the value-bearing set — a parameter that defaults to empty is exactly the
// shape that keeps its own tests green while the live mode still dumps the
// class in the remainder, which is the state this card was filed about.
// Only a real run reads the wiring.
//
// The fixture is this tool's OWN answer on this tree, read from `--json`,
// rather than an invocation typed here: a hardcoded spelling would keep
// passing after the renderer that produces the real one changed shape, and
// the two sides of the comparison would stop being the same strings — the
// property the whole `--ran` design rests on.
{
const vbTmp = mkdtempSync(nodePath.join(tmpdir(), 'dg-ran-vb-'));
try {
// A changeset path, because that is what reaches the live value-bearing
// families — and it is the tool's own constant rather than a spelling
// this test invented.
const vbCard = CHANGESET_PROBE_PATH;
const jsonRun = runCli(['--json', vbCard]);
const doc = jsonRun.status === 0 ? JSON.parse(jsonRun.stdout ?? '{}') : null;
const vbRows = [...(doc?.matched ?? []), ...(doc?.alwaysRunsPopulation ?? [])].filter((row) => row.notRunnable);
t('CONTROL: this tree still derives at least one VALUE-BEARING family for a changeset path', Boolean(doc) && vbRows.length >= 1);
if (doc && vbRows.length >= 1) {
const vbCommand = vbRows[0].command;
t('CONTROL: and the runnable union WITHHOLDS it — which is the whole reason the bucket exists', !doc.commands.includes(vbCommand));
const vbRecord = nodePath.join(vbTmp, 'ran-value-bearing.list');
writeFileSync(vbRecord, `${[...doc.commands, vbCommand].join('\n')}\n`);
const vbRun = runCli([RAN_FLAG, vbRecord, vbCard]);
const vbOut = vbRun.stdout ?? '';
t(
'⭐ a real run that RECORDS it lands it in the VALUE-BEARING bucket, with the remainder heading gone entirely',
vbRun.status === 0
&& vbOut.includes('VALUE-BEARING famil(ies)')
&& vbOut.includes(vbCommand)
&& !vbOut.includes("Outside this card's derivation"),
);
t(
'and the reason travels with it, so the runner learns why this invocation is not one they could have derived',
vbOut.includes('its argv takes a value from the workflow'),
);
t(
'while the verdict is unmoved — the bucket is diagnostic, and every derived family is still accounted for',
vbOut.includes(`${doc.commands.length} derived famil(ies) accounted for`),
);
}
} finally {
rmSync(vbTmp, { recursive: true, force: true });
}
}

// The per-case line already printed inside `t()`, streamed as each verdict
// was decided (#14281) — this tail is the summary only, unchanged in shape
// and wording from the pre-streaming version.
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length \u003e 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
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
156 changes: 149 additions & 7 deletions scripts/pm/dispatch-gates.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -9730,9 +9730,20 @@ export function parseRunRecord(text) {
* heading) and which deduplicates the two spellings of one family into one
* command before this function ever sees them. What the tool CAN still meet in
* a record it classifies itself — a CI-measured-only family, which `commandsFor`
* subtracts by design, and a pending-changeset family, derived against a path
* that did not exist at derivation time. Both are matched byte-exactly against
* sets this same derivation produced.
* subtracts by design; a VALUE-BEARING family, which `commandsFor` subtracts for
* the same reason one step later because its argv takes a value from the
* workflow (#15083); and a pending-changeset family, derived against a path
* that did not exist at derivation time. All three are matched byte-exactly
* against sets this same derivation produced.
*
* ⭐ The third bucket is not a convenience: this file's own rule is that an
* omission is disclosed WHERE the omission happens, and a class `commandsFor`
* deliberately withholds is a class the runner cannot be expected to have
* derived. Left in the remainder it reads as a command "named by nothing this
* run derived" — the one sentence that is false about it, because this run
* derived it and then classified it out. The likeliest recorder is a dev who
* writes the bare spelling of one of these scripts out of habit, and what they
* are owed is the reason, not a shrug (#15115).
*
* What is left for the runner to explain is therefore only what the tool
* genuinely cannot know: a gate that refused with its own prerequisite. That is
Expand All@@ -9751,6 +9762,12 @@ export function parseRunRecord(text) {
export function runReconciliation({
derived = [],
ciOnlyCommands = new Set(),
// Beside `ciOnlyCommands` and not after `pendingCommands`, because the file
// already groups them: these are the TWO subtractions `commandsFor` makes
// from the runnable union, in that order, and the pending families are a
// different fact (a path that did not exist at derivation time). Defaulting
// to empty keeps every existing caller's verdict byte-identical.
notRunnableCommands = new Set(),
pendingCommands = new Set(),
record = [],
} = {}) {
Expand DownExpand Up@@ -9792,6 +9809,7 @@ export function runReconciliation({
}

const explainedCiOnly = [];
const explainedNotRunnable = [];
const explainedPending = [];
const extra = [];
const nearMiss = [];
Expand All@@ -9804,6 +9822,14 @@ export function runReconciliation({
explainedCiOnly.push(entry.command);
continue;
}
// The order is the precedence, and it is the one `commandsFor` already
// states: `ciOnly` is the FIRST subtraction, so a family that were somehow
// both is reported as CI-measured — one bucket per command, chosen the same
// way in both places rather than two counts for one omission.
if (notRunnableCommands.has(entry.command)) {
explainedNotRunnable.push(entry.command);
continue;
}
if (pendingCommands.has(entry.command)) {
explainedPending.push(entry.command);
continue;
Expand All@@ -9823,6 +9849,7 @@ export function runReconciliation({
unrun,
notMeasured,
explainedCiOnly: explainedCiOnly.sort(),
explainedNotRunnable: explainedNotRunnable.sort(),
explainedPending: explainedPending.sort(),
extra: extra.sort(),
nearMiss,
Expand DownExpand Up@@ -9885,6 +9912,13 @@ export function runReconciliationLines(recon) {
);
for (const command of recon.explainedCiOnly) lines.push(` - ${command}`);
}
if (recon.explainedNotRunnable.length > 0) {
lines.push(
` Classified by this tool, no explanation owed (${recon.explainedNotRunnable.length}) — VALUE-BEARING famil(ies):`
+ ' its argv takes a value from the workflow, so it is recorded, not derived as runnable:',
);
for (const command of recon.explainedNotRunnable) lines.push(` - ${command}`);
}
if (recon.explainedPending.length > 0) {
lines.push(
` Classified by this tool, no explanation owed (${recon.explainedPending.length}) — pending-changeset famil(ies), derived against a path that did not exist at derivation time:`,
Expand DownExpand Up@@ -10159,9 +10193,10 @@ function derive(paths, { showResidue = false, mode = 'human', runRecord = [] } =
const pending = pendingChangesetFamilies([...byCheck], new Set(matched.keys()));

if (mode === 'ran') {
// Built from the SAME three expressions the other renderings read, in this
// process, on this tree: the union `--commands` prints, the CI-measured set
// that union subtracts, and the pending families it holds back. A second
// Built from the SAME four expressions the other renderings read, in this
// process, on this tree: the union `--commands` prints, the two sets that
// union subtracts — CI-measured, and value-bearing — and the pending
// families it holds back. A second
// traversal here would be a second answer to a question this file already
// answers once — and it would be the answer the reconciliation is judged
// against, which is the worst possible place to keep a duplicate.
Expand All@@ -10172,6 +10207,11 @@ function derive(paths, { showResidue = false, mode = 'human', runRecord = [] } =
// makes it a contract rather than a note (#14189).
derived: commandsFor({ matchedRows, kindGroups, alwaysRunsRows }),
ciOnlyCommands: ciOnlyCommandSet(matchedRows, alwaysRunsRows),
// The SECOND set `commandsFor` subtracts, read from the SAME rows by the
// SAME expression it uses — so the union and the reconciliation cannot
// drift about which invocations are withheld, exactly as they cannot for
// the CI-measured set above it (#15115).
notRunnableCommands: notRunnableCommandSet(matchedRows, alwaysRunsRows),
pendingCommands: new Set(pending.map(({ entry }) => runnableInvocation(entry))),
record: runRecord,
});
Expand DownExpand Up@@ -18770,16 +18810,51 @@ function selfTest() {
t('the malformed line is reported against its line number too', unexplained.malformed.length === 1 && unexplained.malformed[0].line === 1);

// ── What the TOOL classifies, so no prose has to ────────────────────────
// ⭐ The value-bearing spelling is the LIVE one — `pr-automation.yml`
// really passes `--base "$MERGE_BASE"` to this script — for the reason the
// neighbouring cases take theirs from the live workflows: a fixture
// invented here would keep passing after the renderer that produces the
// real one changed shape, which is the failure this whole file is about.
const valueBearing = 'node scripts/check-empty-changeset.mjs --base "$MERGE_BASE"';
const explained = runReconciliation({
derived: ['pnpm check:a'],
ciOnlyCommands: new Set(['node scripts/check-payload-guard.mjs']),
notRunnableCommands: new Set([valueBearing]),
pendingCommands: new Set(['pnpm check:changeset-shape']),
record: parseRunRecord(['pnpm check:a', 'node scripts/check-payload-guard.mjs', 'pnpm check:changeset-shape'].join('\n')),
record: parseRunRecord(['pnpm check:a', 'node scripts/check-payload-guard.mjs', valueBearing, 'pnpm check:changeset-shape'].join('\n')),
});
t(
'a CI-measured-only entry and a pending-changeset entry are classified by the tool, not dumped into the remainder',
explained.ok && explained.explainedCiOnly.length === 1 && explained.explainedPending.length === 1 && explained.extra.length === 0,
);
// ⭐ #15115: the THIRD class `commandsFor` withholds gets the same
// courtesy. Recorded, derived by this run, classified out of the union —
// so `extra`'s caption ("named by nothing this run derived") would be the
// one sentence that is false about it.
t(
'a recorded VALUE-BEARING family is classified by the tool too, never folded into the remainder',
explained.explainedNotRunnable.length === 1 && explained.explainedNotRunnable[0] === valueBearing && !explained.extra.includes(valueBearing),
);
// CONTROL, and it is the load-bearing half: the bucket explains the class
// it was given and nothing else. A command named by no set is still
// `extra` — a third bucket that swallowed unknowns would have deleted the
// remainder rather than shrunk it.
const unknownBeside = runReconciliation({
derived: ['pnpm check:a'],
notRunnableCommands: new Set([valueBearing]),
record: parseRunRecord(['pnpm check:a', valueBearing, 'pnpm check:not-a-family'].join('\n')),
});
t(
'and an unknown command beside it still reads `extra` — the new bucket explains its class only',
unknownBeside.explainedNotRunnable.length === 1 && unknownBeside.extra.length === 1 && unknownBeside.extra[0] === 'pnpm check:not-a-family',
);
// The bucket is DIAGNOSTIC, exactly like the two beside it: the verdict
// reads `unrun` and nothing else, so classifying an entry can never move
// it in either direction (#15115).
t(
'the new bucket cannot move the verdict — it is diagnostic, like the two beside it',
unknownBeside.ok && unknownBeside.unrun.length === 0 && unknownBeside.ran.length === 1,
);

// ── Bookkeeping the classes cannot lose ─────────────────────────────────
const dupes = runReconciliation({ derived: ['pnpm check:a'], record: parseRunRecord(['pnpm check:a', 'pnpm check:a'].join('\n')) });
Expand DownExpand Up@@ -18823,6 +18898,21 @@ function selfTest() {
'the near-miss line refuses the pairing out loud rather than quietly',
runReconciliationLines(nearMiss).join('\n').includes('is NOT paired with it'),
);
// ⭐ #15115, in the rendering: a bucket that classified an entry and then
// printed nothing would leave the runner exactly where `extra` left them.
const explainedText = runReconciliationLines(explained).join('\n');
t(
'the value-bearing bucket gets its OWN labelled line, naming the reason and the command',
explainedText.includes('VALUE-BEARING famil(ies)')
&& explainedText.includes('its argv takes a value from the workflow')
&& explainedText.includes(valueBearing),
);
t(
'and the two buckets beside it keep their own lines, with the remainder heading absent entirely',
explainedText.includes('CI-MEASURED ONLY')
&& explainedText.includes('pending-changeset famil(ies)')
&& !explainedText.includes("Outside this card's derivation"),
);

// ── argv: a two-token flag's value must not become a path ───────────────
const ranSplit = splitArgv([RAN_FLAG, 'ran.list', 'packages/spec/src/index.ts', '--residue']);
Expand DownExpand Up@@ -18891,6 +18981,58 @@ function selfTest() {
}
}

// ── END TO END: the VALUE-BEARING bucket is WIRED, not merely present (#15115) ──
//
// Every unit case above stays green if the `--ran` call site never PASSES
// the value-bearing set — a parameter that defaults to empty is exactly the
// shape that keeps its own tests green while the live mode still dumps the
// class in the remainder, which is the state this card was filed about.
// Only a real run reads the wiring.
//
// The fixture is this tool's OWN answer on this tree, read from `--json`,
// rather than an invocation typed here: a hardcoded spelling would keep
// passing after the renderer that produces the real one changed shape, and
// the two sides of the comparison would stop being the same strings — the
// property the whole `--ran` design rests on.
{
const vbTmp = mkdtempSync(nodePath.join(tmpdir(), 'dg-ran-vb-'));
try {
// A changeset path, because that is what reaches the live value-bearing
// families — and it is the tool's own constant rather than a spelling
// this test invented.
const vbCard = CHANGESET_PROBE_PATH;
const jsonRun = runCli(['--json', vbCard]);
const doc = jsonRun.status === 0 ? JSON.parse(jsonRun.stdout ?? '{}') : null;
const vbRows = [...(doc?.matched ?? []), ...(doc?.alwaysRunsPopulation ?? [])].filter((row) => row.notRunnable);
t('CONTROL: this tree still derives at least one VALUE-BEARING family for a changeset path', Boolean(doc) && vbRows.length >= 1);
if (doc && vbRows.length >= 1) {
const vbCommand = vbRows[0].command;
t('CONTROL: and the runnable union WITHHOLDS it — which is the whole reason the bucket exists', !doc.commands.includes(vbCommand));
const vbRecord = nodePath.join(vbTmp, 'ran-value-bearing.list');
writeFileSync(vbRecord, `${[...doc.commands, vbCommand].join('\n')}\n`);
const vbRun = runCli([RAN_FLAG, vbRecord, vbCard]);
const vbOut = vbRun.stdout ?? '';
t(
'⭐ a real run that RECORDS it lands it in the VALUE-BEARING bucket, with the remainder heading gone entirely',
vbRun.status === 0
&& vbOut.includes('VALUE-BEARING famil(ies)')
&& vbOut.includes(vbCommand)
&& !vbOut.includes("Outside this card's derivation"),
);
t(
'and the reason travels with it, so the runner learns why this invocation is not one they could have derived',
vbOut.includes('its argv takes a value from the workflow'),
);
t(
'while the verdict is unmoved — the bucket is diagnostic, and every derived family is still accounted for',
vbOut.includes(`${doc.commands.length} derived famil(ies) accounted for`),
);
}
} finally {
rmSync(vbTmp, { recursive: true, force: true });
}
}

// The per-case line already printed inside `t()`, streamed as each verdict
// was decided (#14281) — this tail is the summary only, unchanged in shape
// and wording from the pre-streaming version.
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Merged
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
156 changes: 149 additions & 7 deletions scripts/pm/dispatch-gates.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -9730,9 +9730,20 @@ export function parseRunRecord(text) {
* heading) and which deduplicates the two spellings of one family into one
* command before this function ever sees them. What the tool CAN still meet in
* a record it classifies itself — a CI-measured-only family, which `commandsFor`
* subtracts by design, and a pending-changeset family, derived against a path
* that did not exist at derivation time. Both are matched byte-exactly against
* sets this same derivation produced.
* subtracts by design; a VALUE-BEARING family, which `commandsFor` subtracts for
* the same reason one step later because its argv takes a value from the
* workflow (#15083); and a pending-changeset family, derived against a path
* that did not exist at derivation time. All three are matched byte-exactly
* against sets this same derivation produced.
*
* ⭐ The third bucket is not a convenience: this file's own rule is that an
* omission is disclosed WHERE the omission happens, and a class `commandsFor`
* deliberately withholds is a class the runner cannot be expected to have
* derived. Left in the remainder it reads as a command "named by nothing this
* run derived" — the one sentence that is false about it, because this run
* derived it and then classified it out. The likeliest recorder is a dev who
* writes the bare spelling of one of these scripts out of habit, and what they
* are owed is the reason, not a shrug (#15115).
*
* What is left for the runner to explain is therefore only what the tool
* genuinely cannot know: a gate that refused with its own prerequisite. That is
Expand All@@ -9751,6 +9762,12 @@ export function parseRunRecord(text) {
export function runReconciliation({
derived = [],
ciOnlyCommands = new Set(),
// Beside `ciOnlyCommands` and not after `pendingCommands`, because the file
// already groups them: these are the TWO subtractions `commandsFor` makes
// from the runnable union, in that order, and the pending families are a
// different fact (a path that did not exist at derivation time). Defaulting
// to empty keeps every existing caller's verdict byte-identical.
notRunnableCommands = new Set(),
pendingCommands = new Set(),
record = [],
} = {}) {
Expand DownExpand Up@@ -9792,6 +9809,7 @@ export function runReconciliation({
}

const explainedCiOnly = [];
const explainedNotRunnable = [];
const explainedPending = [];
const extra = [];
const nearMiss = [];
Expand All@@ -9804,6 +9822,14 @@ export function runReconciliation({
explainedCiOnly.push(entry.command);
continue;
}
// The order is the precedence, and it is the one `commandsFor` already
// states: `ciOnly` is the FIRST subtraction, so a family that were somehow
// both is reported as CI-measured — one bucket per command, chosen the same
// way in both places rather than two counts for one omission.
if (notRunnableCommands.has(entry.command)) {
explainedNotRunnable.push(entry.command);
continue;
}
if (pendingCommands.has(entry.command)) {
explainedPending.push(entry.command);
continue;
Expand All@@ -9823,6 +9849,7 @@ export function runReconciliation({
unrun,
notMeasured,
explainedCiOnly: explainedCiOnly.sort(),
explainedNotRunnable: explainedNotRunnable.sort(),
explainedPending: explainedPending.sort(),
extra: extra.sort(),
nearMiss,
Expand DownExpand Up@@ -9885,6 +9912,13 @@ export function runReconciliationLines(recon) {
);
for (const command of recon.explainedCiOnly) lines.push(` - ${command}`);
}
if (recon.explainedNotRunnable.length > 0) {
lines.push(
` Classified by this tool, no explanation owed (${recon.explainedNotRunnable.length}) — VALUE-BEARING famil(ies):`
+ ' its argv takes a value from the workflow, so it is recorded, not derived as runnable:',
);
for (const command of recon.explainedNotRunnable) lines.push(` - ${command}`);
}
if (recon.explainedPending.length > 0) {
lines.push(
` Classified by this tool, no explanation owed (${recon.explainedPending.length}) — pending-changeset famil(ies), derived against a path that did not exist at derivation time:`,
Expand DownExpand Up@@ -10159,9 +10193,10 @@ function derive(paths, { showResidue = false, mode = 'human', runRecord = [] } =
const pending = pendingChangesetFamilies([...byCheck], new Set(matched.keys()));

if (mode === 'ran') {
// Built from the SAME three expressions the other renderings read, in this
// process, on this tree: the union `--commands` prints, the CI-measured set
// that union subtracts, and the pending families it holds back. A second
// Built from the SAME four expressions the other renderings read, in this
// process, on this tree: the union `--commands` prints, the two sets that
// union subtracts — CI-measured, and value-bearing — and the pending
// families it holds back. A second
// traversal here would be a second answer to a question this file already
// answers once — and it would be the answer the reconciliation is judged
// against, which is the worst possible place to keep a duplicate.
Expand All@@ -10172,6 +10207,11 @@ function derive(paths, { showResidue = false, mode = 'human', runRecord = [] } =
// makes it a contract rather than a note (#14189).
derived: commandsFor({ matchedRows, kindGroups, alwaysRunsRows }),
ciOnlyCommands: ciOnlyCommandSet(matchedRows, alwaysRunsRows),
// The SECOND set `commandsFor` subtracts, read from the SAME rows by the
// SAME expression it uses — so the union and the reconciliation cannot
// drift about which invocations are withheld, exactly as they cannot for
// the CI-measured set above it (#15115).
notRunnableCommands: notRunnableCommandSet(matchedRows, alwaysRunsRows),
pendingCommands: new Set(pending.map(({ entry }) => runnableInvocation(entry))),
record: runRecord,
});
Expand DownExpand Up@@ -18770,16 +18810,51 @@ function selfTest() {
t('the malformed line is reported against its line number too', unexplained.malformed.length === 1 && unexplained.malformed[0].line === 1);

// ── What the TOOL classifies, so no prose has to ────────────────────────
// ⭐ The value-bearing spelling is the LIVE one — `pr-automation.yml`
// really passes `--base "$MERGE_BASE"` to this script — for the reason the
// neighbouring cases take theirs from the live workflows: a fixture
// invented here would keep passing after the renderer that produces the
// real one changed shape, which is the failure this whole file is about.
const valueBearing = 'node scripts/check-empty-changeset.mjs --base "$MERGE_BASE"';
const explained = runReconciliation({
derived: ['pnpm check:a'],
ciOnlyCommands: new Set(['node scripts/check-payload-guard.mjs']),
notRunnableCommands: new Set([valueBearing]),
pendingCommands: new Set(['pnpm check:changeset-shape']),
record: parseRunRecord(['pnpm check:a', 'node scripts/check-payload-guard.mjs', 'pnpm check:changeset-shape'].join('\n')),
record: parseRunRecord(['pnpm check:a', 'node scripts/check-payload-guard.mjs', valueBearing, 'pnpm check:changeset-shape'].join('\n')),
});
t(
'a CI-measured-only entry and a pending-changeset entry are classified by the tool, not dumped into the remainder',
explained.ok && explained.explainedCiOnly.length === 1 && explained.explainedPending.length === 1 && explained.extra.length === 0,
);
// ⭐ #15115: the THIRD class `commandsFor` withholds gets the same
// courtesy. Recorded, derived by this run, classified out of the union —
// so `extra`'s caption ("named by nothing this run derived") would be the
// one sentence that is false about it.
t(
'a recorded VALUE-BEARING family is classified by the tool too, never folded into the remainder',
explained.explainedNotRunnable.length === 1 && explained.explainedNotRunnable[0] === valueBearing && !explained.extra.includes(valueBearing),
);
// CONTROL, and it is the load-bearing half: the bucket explains the class
// it was given and nothing else. A command named by no set is still
// `extra` — a third bucket that swallowed unknowns would have deleted the
// remainder rather than shrunk it.
const unknownBeside = runReconciliation({
derived: ['pnpm check:a'],
notRunnableCommands: new Set([valueBearing]),
record: parseRunRecord(['pnpm check:a', valueBearing, 'pnpm check:not-a-family'].join('\n')),
});
t(
'and an unknown command beside it still reads `extra` — the new bucket explains its class only',
unknownBeside.explainedNotRunnable.length === 1 && unknownBeside.extra.length === 1 && unknownBeside.extra[0] === 'pnpm check:not-a-family',
);
// The bucket is DIAGNOSTIC, exactly like the two beside it: the verdict
// reads `unrun` and nothing else, so classifying an entry can never move
// it in either direction (#15115).
t(
'the new bucket cannot move the verdict — it is diagnostic, like the two beside it',
unknownBeside.ok && unknownBeside.unrun.length === 0 && unknownBeside.ran.length === 1,
);

// ── Bookkeeping the classes cannot lose ─────────────────────────────────
const dupes = runReconciliation({ derived: ['pnpm check:a'], record: parseRunRecord(['pnpm check:a', 'pnpm check:a'].join('\n')) });
Expand DownExpand Up@@ -18823,6 +18898,21 @@ function selfTest() {
'the near-miss line refuses the pairing out loud rather than quietly',
runReconciliationLines(nearMiss).join('\n').includes('is NOT paired with it'),
);
// ⭐ #15115, in the rendering: a bucket that classified an entry and then
// printed nothing would leave the runner exactly where `extra` left them.
const explainedText = runReconciliationLines(explained).join('\n');
t(
'the value-bearing bucket gets its OWN labelled line, naming the reason and the command',
explainedText.includes('VALUE-BEARING famil(ies)')
&& explainedText.includes('its argv takes a value from the workflow')
&& explainedText.includes(valueBearing),
);
t(
'and the two buckets beside it keep their own lines, with the remainder heading absent entirely',
explainedText.includes('CI-MEASURED ONLY')
&& explainedText.includes('pending-changeset famil(ies)')
&& !explainedText.includes("Outside this card's derivation"),
);

// ── argv: a two-token flag's value must not become a path ───────────────
const ranSplit = splitArgv([RAN_FLAG, 'ran.list', 'packages/spec/src/index.ts', '--residue']);
Expand DownExpand Up@@ -18891,6 +18981,58 @@ function selfTest() {
}
}

// ── END TO END: the VALUE-BEARING bucket is WIRED, not merely present (#15115) ──
//
// Every unit case above stays green if the `--ran` call site never PASSES
// the value-bearing set — a parameter that defaults to empty is exactly the
// shape that keeps its own tests green while the live mode still dumps the
// class in the remainder, which is the state this card was filed about.
// Only a real run reads the wiring.
//
// The fixture is this tool's OWN answer on this tree, read from `--json`,
// rather than an invocation typed here: a hardcoded spelling would keep
// passing after the renderer that produces the real one changed shape, and
// the two sides of the comparison would stop being the same strings — the
// property the whole `--ran` design rests on.
{
const vbTmp = mkdtempSync(nodePath.join(tmpdir(), 'dg-ran-vb-'));
try {
// A changeset path, because that is what reaches the live value-bearing
// families — and it is the tool's own constant rather than a spelling
// this test invented.
const vbCard = CHANGESET_PROBE_PATH;
const jsonRun = runCli(['--json', vbCard]);
const doc = jsonRun.status === 0 ? JSON.parse(jsonRun.stdout ?? '{}') : null;
const vbRows = [...(doc?.matched ?? []), ...(doc?.alwaysRunsPopulation ?? [])].filter((row) => row.notRunnable);
t('CONTROL: this tree still derives at least one VALUE-BEARING family for a changeset path', Boolean(doc) && vbRows.length >= 1);
if (doc && vbRows.length >= 1) {
const vbCommand = vbRows[0].command;
t('CONTROL: and the runnable union WITHHOLDS it — which is the whole reason the bucket exists', !doc.commands.includes(vbCommand));
const vbRecord = nodePath.join(vbTmp, 'ran-value-bearing.list');
writeFileSync(vbRecord, `${[...doc.commands, vbCommand].join('\n')}\n`);
const vbRun = runCli([RAN_FLAG, vbRecord, vbCard]);
const vbOut = vbRun.stdout ?? '';
t(
'⭐ a real run that RECORDS it lands it in the VALUE-BEARING bucket, with the remainder heading gone entirely',
vbRun.status === 0
&& vbOut.includes('VALUE-BEARING famil(ies)')
&& vbOut.includes(vbCommand)
&& !vbOut.includes("Outside this card's derivation"),
);
t(
'and the reason travels with it, so the runner learns why this invocation is not one they could have derived',
vbOut.includes('its argv takes a value from the workflow'),
);
t(
'while the verdict is unmoved — the bucket is diagnostic, and every derived family is still accounted for',
vbOut.includes(`${doc.commands.length} derived famil(ies) accounted for`),
);
}
} finally {
rmSync(vbTmp, { recursive: true, force: true });
}
}

// The per-case line already printed inside `t()`, streamed as each verdict
// was decided (#14281) — this tail is the summary only, unchanged in shape
// and wording from the pre-streaming version.
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
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
156 changes: 149 additions & 7 deletions scripts/pm/dispatch-gates.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -9730,9 +9730,20 @@ export function parseRunRecord(text) {
* heading) and which deduplicates the two spellings of one family into one
* command before this function ever sees them. What the tool CAN still meet in
* a record it classifies itself — a CI-measured-only family, which `commandsFor`
* subtracts by design, and a pending-changeset family, derived against a path
* that did not exist at derivation time. Both are matched byte-exactly against
* sets this same derivation produced.
* subtracts by design; a VALUE-BEARING family, which `commandsFor` subtracts for
* the same reason one step later because its argv takes a value from the
* workflow (#15083); and a pending-changeset family, derived against a path
* that did not exist at derivation time. All three are matched byte-exactly
* against sets this same derivation produced.
*
* ⭐ The third bucket is not a convenience: this file's own rule is that an
* omission is disclosed WHERE the omission happens, and a class `commandsFor`
* deliberately withholds is a class the runner cannot be expected to have
* derived. Left in the remainder it reads as a command "named by nothing this
* run derived" — the one sentence that is false about it, because this run
* derived it and then classified it out. The likeliest recorder is a dev who
* writes the bare spelling of one of these scripts out of habit, and what they
* are owed is the reason, not a shrug (#15115).
*
* What is left for the runner to explain is therefore only what the tool
* genuinely cannot know: a gate that refused with its own prerequisite. That is
Expand All@@ -9751,6 +9762,12 @@ export function parseRunRecord(text) {
export function runReconciliation({
derived = [],
ciOnlyCommands = new Set(),
// Beside `ciOnlyCommands` and not after `pendingCommands`, because the file
// already groups them: these are the TWO subtractions `commandsFor` makes
// from the runnable union, in that order, and the pending families are a
// different fact (a path that did not exist at derivation time). Defaulting
// to empty keeps every existing caller's verdict byte-identical.
notRunnableCommands = new Set(),
pendingCommands = new Set(),
record = [],
} = {}) {
Expand DownExpand Up@@ -9792,6 +9809,7 @@ export function runReconciliation({
}

const explainedCiOnly = [];
const explainedNotRunnable = [];
const explainedPending = [];
const extra = [];
const nearMiss = [];
Expand All@@ -9804,6 +9822,14 @@ export function runReconciliation({
explainedCiOnly.push(entry.command);
continue;
}
// The order is the precedence, and it is the one `commandsFor` already
// states: `ciOnly` is the FIRST subtraction, so a family that were somehow
// both is reported as CI-measured — one bucket per command, chosen the same
// way in both places rather than two counts for one omission.
if (notRunnableCommands.has(entry.command)) {
explainedNotRunnable.push(entry.command);
continue;
}
if (pendingCommands.has(entry.command)) {
explainedPending.push(entry.command);
continue;
Expand All@@ -9823,6 +9849,7 @@ export function runReconciliation({
unrun,
notMeasured,
explainedCiOnly: explainedCiOnly.sort(),
explainedNotRunnable: explainedNotRunnable.sort(),
explainedPending: explainedPending.sort(),
extra: extra.sort(),
nearMiss,
Expand DownExpand Up@@ -9885,6 +9912,13 @@ export function runReconciliationLines(recon) {
);
for (const command of recon.explainedCiOnly) lines.push(` - ${command}`);
}
if (recon.explainedNotRunnable.length > 0) {
lines.push(
` Classified by this tool, no explanation owed (${recon.explainedNotRunnable.length}) — VALUE-BEARING famil(ies):`
+ ' its argv takes a value from the workflow, so it is recorded, not derived as runnable:',
);
for (const command of recon.explainedNotRunnable) lines.push(` - ${command}`);
}
if (recon.explainedPending.length > 0) {
lines.push(
` Classified by this tool, no explanation owed (${recon.explainedPending.length}) — pending-changeset famil(ies), derived against a path that did not exist at derivation time:`,
Expand DownExpand Up@@ -10159,9 +10193,10 @@ function derive(paths, { showResidue = false, mode = 'human', runRecord = [] } =
const pending = pendingChangesetFamilies([...byCheck], new Set(matched.keys()));

if (mode === 'ran') {
// Built from the SAME three expressions the other renderings read, in this
// process, on this tree: the union `--commands` prints, the CI-measured set
// that union subtracts, and the pending families it holds back. A second
// Built from the SAME four expressions the other renderings read, in this
// process, on this tree: the union `--commands` prints, the two sets that
// union subtracts — CI-measured, and value-bearing — and the pending
// families it holds back. A second
// traversal here would be a second answer to a question this file already
// answers once — and it would be the answer the reconciliation is judged
// against, which is the worst possible place to keep a duplicate.
Expand All@@ -10172,6 +10207,11 @@ function derive(paths, { showResidue = false, mode = 'human', runRecord = [] } =
// makes it a contract rather than a note (#14189).
derived: commandsFor({ matchedRows, kindGroups, alwaysRunsRows }),
ciOnlyCommands: ciOnlyCommandSet(matchedRows, alwaysRunsRows),
// The SECOND set `commandsFor` subtracts, read from the SAME rows by the
// SAME expression it uses — so the union and the reconciliation cannot
// drift about which invocations are withheld, exactly as they cannot for
// the CI-measured set above it (#15115).
notRunnableCommands: notRunnableCommandSet(matchedRows, alwaysRunsRows),
pendingCommands: new Set(pending.map(({ entry }) => runnableInvocation(entry))),
record: runRecord,
});
Expand DownExpand Up@@ -18770,16 +18810,51 @@ function selfTest() {
t('the malformed line is reported against its line number too', unexplained.malformed.length === 1 && unexplained.malformed[0].line === 1);

// ── What the TOOL classifies, so no prose has to ────────────────────────
// ⭐ The value-bearing spelling is the LIVE one — `pr-automation.yml`
// really passes `--base "$MERGE_BASE"` to this script — for the reason the
// neighbouring cases take theirs from the live workflows: a fixture
// invented here would keep passing after the renderer that produces the
// real one changed shape, which is the failure this whole file is about.
const valueBearing = 'node scripts/check-empty-changeset.mjs --base "$MERGE_BASE"';
const explained = runReconciliation({
derived: ['pnpm check:a'],
ciOnlyCommands: new Set(['node scripts/check-payload-guard.mjs']),
notRunnableCommands: new Set([valueBearing]),
pendingCommands: new Set(['pnpm check:changeset-shape']),
record: parseRunRecord(['pnpm check:a', 'node scripts/check-payload-guard.mjs', 'pnpm check:changeset-shape'].join('\n')),
record: parseRunRecord(['pnpm check:a', 'node scripts/check-payload-guard.mjs', valueBearing, 'pnpm check:changeset-shape'].join('\n')),
});
t(
'a CI-measured-only entry and a pending-changeset entry are classified by the tool, not dumped into the remainder',
explained.ok && explained.explainedCiOnly.length === 1 && explained.explainedPending.length === 1 && explained.extra.length === 0,
);
// ⭐ #15115: the THIRD class `commandsFor` withholds gets the same
// courtesy. Recorded, derived by this run, classified out of the union —
// so `extra`'s caption ("named by nothing this run derived") would be the
// one sentence that is false about it.
t(
'a recorded VALUE-BEARING family is classified by the tool too, never folded into the remainder',
explained.explainedNotRunnable.length === 1 && explained.explainedNotRunnable[0] === valueBearing && !explained.extra.includes(valueBearing),
);
// CONTROL, and it is the load-bearing half: the bucket explains the class
// it was given and nothing else. A command named by no set is still
// `extra` — a third bucket that swallowed unknowns would have deleted the
// remainder rather than shrunk it.
const unknownBeside = runReconciliation({
derived: ['pnpm check:a'],
notRunnableCommands: new Set([valueBearing]),
record: parseRunRecord(['pnpm check:a', valueBearing, 'pnpm check:not-a-family'].join('\n')),
});
t(
'and an unknown command beside it still reads `extra` — the new bucket explains its class only',
unknownBeside.explainedNotRunnable.length === 1 && unknownBeside.extra.length === 1 && unknownBeside.extra[0] === 'pnpm check:not-a-family',
);
// The bucket is DIAGNOSTIC, exactly like the two beside it: the verdict
// reads `unrun` and nothing else, so classifying an entry can never move
// it in either direction (#15115).
t(
'the new bucket cannot move the verdict — it is diagnostic, like the two beside it',
unknownBeside.ok && unknownBeside.unrun.length === 0 && unknownBeside.ran.length === 1,
);

// ── Bookkeeping the classes cannot lose ─────────────────────────────────
const dupes = runReconciliation({ derived: ['pnpm check:a'], record: parseRunRecord(['pnpm check:a', 'pnpm check:a'].join('\n')) });
Expand DownExpand Up@@ -18823,6 +18898,21 @@ function selfTest() {
'the near-miss line refuses the pairing out loud rather than quietly',
runReconciliationLines(nearMiss).join('\n').includes('is NOT paired with it'),
);
// ⭐ #15115, in the rendering: a bucket that classified an entry and then
// printed nothing would leave the runner exactly where `extra` left them.
const explainedText = runReconciliationLines(explained).join('\n');
t(
'the value-bearing bucket gets its OWN labelled line, naming the reason and the command',
explainedText.includes('VALUE-BEARING famil(ies)')
&& explainedText.includes('its argv takes a value from the workflow')
&& explainedText.includes(valueBearing),
);
t(
'and the two buckets beside it keep their own lines, with the remainder heading absent entirely',
explainedText.includes('CI-MEASURED ONLY')
&& explainedText.includes('pending-changeset famil(ies)')
&& !explainedText.includes("Outside this card's derivation"),
);

// ── argv: a two-token flag's value must not become a path ───────────────
const ranSplit = splitArgv([RAN_FLAG, 'ran.list', 'packages/spec/src/index.ts', '--residue']);
Expand DownExpand Up@@ -18891,6 +18981,58 @@ function selfTest() {
}
}

// ── END TO END: the VALUE-BEARING bucket is WIRED, not merely present (#15115) ──
//
// Every unit case above stays green if the `--ran` call site never PASSES
// the value-bearing set — a parameter that defaults to empty is exactly the
// shape that keeps its own tests green while the live mode still dumps the
// class in the remainder, which is the state this card was filed about.
// Only a real run reads the wiring.
//
// The fixture is this tool's OWN answer on this tree, read from `--json`,
// rather than an invocation typed here: a hardcoded spelling would keep
// passing after the renderer that produces the real one changed shape, and
// the two sides of the comparison would stop being the same strings — the
// property the whole `--ran` design rests on.
{
const vbTmp = mkdtempSync(nodePath.join(tmpdir(), 'dg-ran-vb-'));
try {
// A changeset path, because that is what reaches the live value-bearing
// families — and it is the tool's own constant rather than a spelling
// this test invented.
const vbCard = CHANGESET_PROBE_PATH;
const jsonRun = runCli(['--json', vbCard]);
const doc = jsonRun.status === 0 ? JSON.parse(jsonRun.stdout ?? '{}') : null;
const vbRows = [...(doc?.matched ?? []), ...(doc?.alwaysRunsPopulation ?? [])].filter((row) => row.notRunnable);
t('CONTROL: this tree still derives at least one VALUE-BEARING family for a changeset path', Boolean(doc) && vbRows.length >= 1);
if (doc && vbRows.length >= 1) {
const vbCommand = vbRows[0].command;
t('CONTROL: and the runnable union WITHHOLDS it — which is the whole reason the bucket exists', !doc.commands.includes(vbCommand));
const vbRecord = nodePath.join(vbTmp, 'ran-value-bearing.list');
writeFileSync(vbRecord, `${[...doc.commands, vbCommand].join('\n')}\n`);
const vbRun = runCli([RAN_FLAG, vbRecord, vbCard]);
const vbOut = vbRun.stdout ?? '';
t(
'⭐ a real run that RECORDS it lands it in the VALUE-BEARING bucket, with the remainder heading gone entirely',
vbRun.status === 0
&& vbOut.includes('VALUE-BEARING famil(ies)')
&& vbOut.includes(vbCommand)
&& !vbOut.includes("Outside this card's derivation"),
);
t(
'and the reason travels with it, so the runner learns why this invocation is not one they could have derived',
vbOut.includes('its argv takes a value from the workflow'),
);
t(
'while the verdict is unmoved — the bucket is diagnostic, and every derived family is still accounted for',
vbOut.includes(`${doc.commands.length} derived famil(ies) accounted for`),
);
}
} finally {
rmSync(vbTmp, { recursive: true, force: true });
}
}

// The per-case line already printed inside `t()`, streamed as each verdict
// was decided (#14281) — this tail is the summary only, unchanged in shape
// and wording from the pre-streaming version.
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
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
156 changes: 149 additions & 7 deletions scripts/pm/dispatch-gates.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -9730,9 +9730,20 @@ export function parseRunRecord(text) {
* heading) and which deduplicates the two spellings of one family into one
* command before this function ever sees them. What the tool CAN still meet in
* a record it classifies itself — a CI-measured-only family, which `commandsFor`
* subtracts by design, and a pending-changeset family, derived against a path
* that did not exist at derivation time. Both are matched byte-exactly against
* sets this same derivation produced.
* subtracts by design; a VALUE-BEARING family, which `commandsFor` subtracts for
* the same reason one step later because its argv takes a value from the
* workflow (#15083); and a pending-changeset family, derived against a path
* that did not exist at derivation time. All three are matched byte-exactly
* against sets this same derivation produced.
*
* ⭐ The third bucket is not a convenience: this file's own rule is that an
* omission is disclosed WHERE the omission happens, and a class `commandsFor`
* deliberately withholds is a class the runner cannot be expected to have
* derived. Left in the remainder it reads as a command "named by nothing this
* run derived" — the one sentence that is false about it, because this run
* derived it and then classified it out. The likeliest recorder is a dev who
* writes the bare spelling of one of these scripts out of habit, and what they
* are owed is the reason, not a shrug (#15115).
*
* What is left for the runner to explain is therefore only what the tool
* genuinely cannot know: a gate that refused with its own prerequisite. That is
Expand All@@ -9751,6 +9762,12 @@ export function parseRunRecord(text) {
export function runReconciliation({
derived = [],
ciOnlyCommands = new Set(),
// Beside `ciOnlyCommands` and not after `pendingCommands`, because the file
// already groups them: these are the TWO subtractions `commandsFor` makes
// from the runnable union, in that order, and the pending families are a
// different fact (a path that did not exist at derivation time). Defaulting
// to empty keeps every existing caller's verdict byte-identical.
notRunnableCommands = new Set(),
pendingCommands = new Set(),
record = [],
} = {}) {
Expand DownExpand Up@@ -9792,6 +9809,7 @@ export function runReconciliation({
}

const explainedCiOnly = [];
const explainedNotRunnable = [];
const explainedPending = [];
const extra = [];
const nearMiss = [];
Expand All@@ -9804,6 +9822,14 @@ export function runReconciliation({
explainedCiOnly.push(entry.command);
continue;
}
// The order is the precedence, and it is the one `commandsFor` already
// states: `ciOnly` is the FIRST subtraction, so a family that were somehow
// both is reported as CI-measured — one bucket per command, chosen the same
// way in both places rather than two counts for one omission.
if (notRunnableCommands.has(entry.command)) {
explainedNotRunnable.push(entry.command);
continue;
}
if (pendingCommands.has(entry.command)) {
explainedPending.push(entry.command);
continue;
Expand All@@ -9823,6 +9849,7 @@ export function runReconciliation({
unrun,
notMeasured,
explainedCiOnly: explainedCiOnly.sort(),
explainedNotRunnable: explainedNotRunnable.sort(),
explainedPending: explainedPending.sort(),
extra: extra.sort(),
nearMiss,
Expand DownExpand Up@@ -9885,6 +9912,13 @@ export function runReconciliationLines(recon) {
);
for (const command of recon.explainedCiOnly) lines.push(` - ${command}`);
}
if (recon.explainedNotRunnable.length > 0) {
lines.push(
` Classified by this tool, no explanation owed (${recon.explainedNotRunnable.length}) — VALUE-BEARING famil(ies):`
+ ' its argv takes a value from the workflow, so it is recorded, not derived as runnable:',
);
for (const command of recon.explainedNotRunnable) lines.push(` - ${command}`);
}
if (recon.explainedPending.length > 0) {
lines.push(
` Classified by this tool, no explanation owed (${recon.explainedPending.length}) — pending-changeset famil(ies), derived against a path that did not exist at derivation time:`,
Expand DownExpand Up@@ -10159,9 +10193,10 @@ function derive(paths, { showResidue = false, mode = 'human', runRecord = [] } =
const pending = pendingChangesetFamilies([...byCheck], new Set(matched.keys()));

if (mode === 'ran') {
// Built from the SAME three expressions the other renderings read, in this
// process, on this tree: the union `--commands` prints, the CI-measured set
// that union subtracts, and the pending families it holds back. A second
// Built from the SAME four expressions the other renderings read, in this
// process, on this tree: the union `--commands` prints, the two sets that
// union subtracts — CI-measured, and value-bearing — and the pending
// families it holds back. A second
// traversal here would be a second answer to a question this file already
// answers once — and it would be the answer the reconciliation is judged
// against, which is the worst possible place to keep a duplicate.
Expand All@@ -10172,6 +10207,11 @@ function derive(paths, { showResidue = false, mode = 'human', runRecord = [] } =
// makes it a contract rather than a note (#14189).
derived: commandsFor({ matchedRows, kindGroups, alwaysRunsRows }),
ciOnlyCommands: ciOnlyCommandSet(matchedRows, alwaysRunsRows),
// The SECOND set `commandsFor` subtracts, read from the SAME rows by the
// SAME expression it uses — so the union and the reconciliation cannot
// drift about which invocations are withheld, exactly as they cannot for
// the CI-measured set above it (#15115).
notRunnableCommands: notRunnableCommandSet(matchedRows, alwaysRunsRows),
pendingCommands: new Set(pending.map(({ entry }) => runnableInvocation(entry))),
record: runRecord,
});
Expand DownExpand Up@@ -18770,16 +18810,51 @@ function selfTest() {
t('the malformed line is reported against its line number too', unexplained.malformed.length === 1 && unexplained.malformed[0].line === 1);

// ── What the TOOL classifies, so no prose has to ────────────────────────
// ⭐ The value-bearing spelling is the LIVE one — `pr-automation.yml`
// really passes `--base "$MERGE_BASE"` to this script — for the reason the
// neighbouring cases take theirs from the live workflows: a fixture
// invented here would keep passing after the renderer that produces the
// real one changed shape, which is the failure this whole file is about.
const valueBearing = 'node scripts/check-empty-changeset.mjs --base "$MERGE_BASE"';
const explained = runReconciliation({
derived: ['pnpm check:a'],
ciOnlyCommands: new Set(['node scripts/check-payload-guard.mjs']),
notRunnableCommands: new Set([valueBearing]),
pendingCommands: new Set(['pnpm check:changeset-shape']),
record: parseRunRecord(['pnpm check:a', 'node scripts/check-payload-guard.mjs', 'pnpm check:changeset-shape'].join('\n')),
record: parseRunRecord(['pnpm check:a', 'node scripts/check-payload-guard.mjs', valueBearing, 'pnpm check:changeset-shape'].join('\n')),
});
t(
'a CI-measured-only entry and a pending-changeset entry are classified by the tool, not dumped into the remainder',
explained.ok && explained.explainedCiOnly.length === 1 && explained.explainedPending.length === 1 && explained.extra.length === 0,
);
// ⭐ #15115: the THIRD class `commandsFor` withholds gets the same
// courtesy. Recorded, derived by this run, classified out of the union —
// so `extra`'s caption ("named by nothing this run derived") would be the
// one sentence that is false about it.
t(
'a recorded VALUE-BEARING family is classified by the tool too, never folded into the remainder',
explained.explainedNotRunnable.length === 1 && explained.explainedNotRunnable[0] === valueBearing && !explained.extra.includes(valueBearing),
);
// CONTROL, and it is the load-bearing half: the bucket explains the class
// it was given and nothing else. A command named by no set is still
// `extra` — a third bucket that swallowed unknowns would have deleted the
// remainder rather than shrunk it.
const unknownBeside = runReconciliation({
derived: ['pnpm check:a'],
notRunnableCommands: new Set([valueBearing]),
record: parseRunRecord(['pnpm check:a', valueBearing, 'pnpm check:not-a-family'].join('\n')),
});
t(
'and an unknown command beside it still reads `extra` — the new bucket explains its class only',
unknownBeside.explainedNotRunnable.length === 1 && unknownBeside.extra.length === 1 && unknownBeside.extra[0] === 'pnpm check:not-a-family',
);
// The bucket is DIAGNOSTIC, exactly like the two beside it: the verdict
// reads `unrun` and nothing else, so classifying an entry can never move
// it in either direction (#15115).
t(
'the new bucket cannot move the verdict — it is diagnostic, like the two beside it',
unknownBeside.ok && unknownBeside.unrun.length === 0 && unknownBeside.ran.length === 1,
);

// ── Bookkeeping the classes cannot lose ─────────────────────────────────
const dupes = runReconciliation({ derived: ['pnpm check:a'], record: parseRunRecord(['pnpm check:a', 'pnpm check:a'].join('\n')) });
Expand DownExpand Up@@ -18823,6 +18898,21 @@ function selfTest() {
'the near-miss line refuses the pairing out loud rather than quietly',
runReconciliationLines(nearMiss).join('\n').includes('is NOT paired with it'),
);
// ⭐ #15115, in the rendering: a bucket that classified an entry and then
// printed nothing would leave the runner exactly where `extra` left them.
const explainedText = runReconciliationLines(explained).join('\n');
t(
'the value-bearing bucket gets its OWN labelled line, naming the reason and the command',
explainedText.includes('VALUE-BEARING famil(ies)')
&& explainedText.includes('its argv takes a value from the workflow')
&& explainedText.includes(valueBearing),
);
t(
'and the two buckets beside it keep their own lines, with the remainder heading absent entirely',
explainedText.includes('CI-MEASURED ONLY')
&& explainedText.includes('pending-changeset famil(ies)')
&& !explainedText.includes("Outside this card's derivation"),
);

// ── argv: a two-token flag's value must not become a path ───────────────
const ranSplit = splitArgv([RAN_FLAG, 'ran.list', 'packages/spec/src/index.ts', '--residue']);
Expand DownExpand Up@@ -18891,6 +18981,58 @@ function selfTest() {
}
}

// ── END TO END: the VALUE-BEARING bucket is WIRED, not merely present (#15115) ──
//
// Every unit case above stays green if the `--ran` call site never PASSES
// the value-bearing set — a parameter that defaults to empty is exactly the
// shape that keeps its own tests green while the live mode still dumps the
// class in the remainder, which is the state this card was filed about.
// Only a real run reads the wiring.
//
// The fixture is this tool's OWN answer on this tree, read from `--json`,
// rather than an invocation typed here: a hardcoded spelling would keep
// passing after the renderer that produces the real one changed shape, and
// the two sides of the comparison would stop being the same strings — the
// property the whole `--ran` design rests on.
{
const vbTmp = mkdtempSync(nodePath.join(tmpdir(), 'dg-ran-vb-'));
try {
// A changeset path, because that is what reaches the live value-bearing
// families — and it is the tool's own constant rather than a spelling
// this test invented.
const vbCard = CHANGESET_PROBE_PATH;
const jsonRun = runCli(['--json', vbCard]);
const doc = jsonRun.status === 0 ? JSON.parse(jsonRun.stdout ?? '{}') : null;
const vbRows = [...(doc?.matched ?? []), ...(doc?.alwaysRunsPopulation ?? [])].filter((row) => row.notRunnable);
t('CONTROL: this tree still derives at least one VALUE-BEARING family for a changeset path', Boolean(doc) && vbRows.length >= 1);
if (doc && vbRows.length >= 1) {
const vbCommand = vbRows[0].command;
t('CONTROL: and the runnable union WITHHOLDS it — which is the whole reason the bucket exists', !doc.commands.includes(vbCommand));
const vbRecord = nodePath.join(vbTmp, 'ran-value-bearing.list');
writeFileSync(vbRecord, `${[...doc.commands, vbCommand].join('\n')}\n`);
const vbRun = runCli([RAN_FLAG, vbRecord, vbCard]);
const vbOut = vbRun.stdout ?? '';
t(
'⭐ a real run that RECORDS it lands it in the VALUE-BEARING bucket, with the remainder heading gone entirely',
vbRun.status === 0
&& vbOut.includes('VALUE-BEARING famil(ies)')
&& vbOut.includes(vbCommand)
&& !vbOut.includes("Outside this card's derivation"),
);
t(
'and the reason travels with it, so the runner learns why this invocation is not one they could have derived',
vbOut.includes('its argv takes a value from the workflow'),
);
t(
'while the verdict is unmoved — the bucket is diagnostic, and every derived family is still accounted for',
vbOut.includes(`${doc.commands.length} derived famil(ies) accounted for`),
);
}
} finally {
rmSync(vbTmp, { recursive: true, force: true });
}
}

// The per-case line already printed inside `t()`, streamed as each verdict
// was decided (#14281) — this tail is the summary only, unchanged in shape
// and wording from the pre-streaming version.
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Merged
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
156 changes: 149 additions & 7 deletions scripts/pm/dispatch-gates.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -9730,9 +9730,20 @@ export function parseRunRecord(text) {
* heading) and which deduplicates the two spellings of one family into one
* command before this function ever sees them. What the tool CAN still meet in
* a record it classifies itself — a CI-measured-only family, which `commandsFor`
* subtracts by design, and a pending-changeset family, derived against a path
* that did not exist at derivation time. Both are matched byte-exactly against
* sets this same derivation produced.
* subtracts by design; a VALUE-BEARING family, which `commandsFor` subtracts for
* the same reason one step later because its argv takes a value from the
* workflow (#15083); and a pending-changeset family, derived against a path
* that did not exist at derivation time. All three are matched byte-exactly
* against sets this same derivation produced.
*
* ⭐ The third bucket is not a convenience: this file's own rule is that an
* omission is disclosed WHERE the omission happens, and a class `commandsFor`
* deliberately withholds is a class the runner cannot be expected to have
* derived. Left in the remainder it reads as a command "named by nothing this
* run derived" — the one sentence that is false about it, because this run
* derived it and then classified it out. The likeliest recorder is a dev who
* writes the bare spelling of one of these scripts out of habit, and what they
* are owed is the reason, not a shrug (#15115).
*
* What is left for the runner to explain is therefore only what the tool
* genuinely cannot know: a gate that refused with its own prerequisite. That is
Expand All@@ -9751,6 +9762,12 @@ export function parseRunRecord(text) {
export function runReconciliation({
derived = [],
ciOnlyCommands = new Set(),
// Beside `ciOnlyCommands` and not after `pendingCommands`, because the file
// already groups them: these are the TWO subtractions `commandsFor` makes
// from the runnable union, in that order, and the pending families are a
// different fact (a path that did not exist at derivation time). Defaulting
// to empty keeps every existing caller's verdict byte-identical.
notRunnableCommands = new Set(),
pendingCommands = new Set(),
record = [],
} = {}) {
Expand DownExpand Up@@ -9792,6 +9809,7 @@ export function runReconciliation({
}

const explainedCiOnly = [];
const explainedNotRunnable = [];
const explainedPending = [];
const extra = [];
const nearMiss = [];
Expand All@@ -9804,6 +9822,14 @@ export function runReconciliation({
explainedCiOnly.push(entry.command);
continue;
}
// The order is the precedence, and it is the one `commandsFor` already
// states: `ciOnly` is the FIRST subtraction, so a family that were somehow
// both is reported as CI-measured — one bucket per command, chosen the same
// way in both places rather than two counts for one omission.
if (notRunnableCommands.has(entry.command)) {
explainedNotRunnable.push(entry.command);
continue;
}
if (pendingCommands.has(entry.command)) {
explainedPending.push(entry.command);
continue;
Expand All@@ -9823,6 +9849,7 @@ export function runReconciliation({
unrun,
notMeasured,
explainedCiOnly: explainedCiOnly.sort(),
explainedNotRunnable: explainedNotRunnable.sort(),
explainedPending: explainedPending.sort(),
extra: extra.sort(),
nearMiss,
Expand DownExpand Up@@ -9885,6 +9912,13 @@ export function runReconciliationLines(recon) {
);
for (const command of recon.explainedCiOnly) lines.push(` - ${command}`);
}
if (recon.explainedNotRunnable.length > 0) {
lines.push(
` Classified by this tool, no explanation owed (${recon.explainedNotRunnable.length}) — VALUE-BEARING famil(ies):`
+ ' its argv takes a value from the workflow, so it is recorded, not derived as runnable:',
);
for (const command of recon.explainedNotRunnable) lines.push(` - ${command}`);
}
if (recon.explainedPending.length > 0) {
lines.push(
` Classified by this tool, no explanation owed (${recon.explainedPending.length}) — pending-changeset famil(ies), derived against a path that did not exist at derivation time:`,
Expand DownExpand Up@@ -10159,9 +10193,10 @@ function derive(paths, { showResidue = false, mode = 'human', runRecord = [] } =
const pending = pendingChangesetFamilies([...byCheck], new Set(matched.keys()));

if (mode === 'ran') {
// Built from the SAME three expressions the other renderings read, in this
// process, on this tree: the union `--commands` prints, the CI-measured set
// that union subtracts, and the pending families it holds back. A second
// Built from the SAME four expressions the other renderings read, in this
// process, on this tree: the union `--commands` prints, the two sets that
// union subtracts — CI-measured, and value-bearing — and the pending
// families it holds back. A second
// traversal here would be a second answer to a question this file already
// answers once — and it would be the answer the reconciliation is judged
// against, which is the worst possible place to keep a duplicate.
Expand All@@ -10172,6 +10207,11 @@ function derive(paths, { showResidue = false, mode = 'human', runRecord = [] } =
// makes it a contract rather than a note (#14189).
derived: commandsFor({ matchedRows, kindGroups, alwaysRunsRows }),
ciOnlyCommands: ciOnlyCommandSet(matchedRows, alwaysRunsRows),
// The SECOND set `commandsFor` subtracts, read from the SAME rows by the
// SAME expression it uses — so the union and the reconciliation cannot
// drift about which invocations are withheld, exactly as they cannot for
// the CI-measured set above it (#15115).
notRunnableCommands: notRunnableCommandSet(matchedRows, alwaysRunsRows),
pendingCommands: new Set(pending.map(({ entry }) => runnableInvocation(entry))),
record: runRecord,
});
Expand DownExpand Up@@ -18770,16 +18810,51 @@ function selfTest() {
t('the malformed line is reported against its line number too', unexplained.malformed.length === 1 && unexplained.malformed[0].line === 1);

// ── What the TOOL classifies, so no prose has to ────────────────────────
// ⭐ The value-bearing spelling is the LIVE one — `pr-automation.yml`
// really passes `--base "$MERGE_BASE"` to this script — for the reason the
// neighbouring cases take theirs from the live workflows: a fixture
// invented here would keep passing after the renderer that produces the
// real one changed shape, which is the failure this whole file is about.
const valueBearing = 'node scripts/check-empty-changeset.mjs --base "$MERGE_BASE"';
const explained = runReconciliation({
derived: ['pnpm check:a'],
ciOnlyCommands: new Set(['node scripts/check-payload-guard.mjs']),
notRunnableCommands: new Set([valueBearing]),
pendingCommands: new Set(['pnpm check:changeset-shape']),
record: parseRunRecord(['pnpm check:a', 'node scripts/check-payload-guard.mjs', 'pnpm check:changeset-shape'].join('\n')),
record: parseRunRecord(['pnpm check:a', 'node scripts/check-payload-guard.mjs', valueBearing, 'pnpm check:changeset-shape'].join('\n')),
});
t(
'a CI-measured-only entry and a pending-changeset entry are classified by the tool, not dumped into the remainder',
explained.ok && explained.explainedCiOnly.length === 1 && explained.explainedPending.length === 1 && explained.extra.length === 0,
);
// ⭐ #15115: the THIRD class `commandsFor` withholds gets the same
// courtesy. Recorded, derived by this run, classified out of the union —
// so `extra`'s caption ("named by nothing this run derived") would be the
// one sentence that is false about it.
t(
'a recorded VALUE-BEARING family is classified by the tool too, never folded into the remainder',
explained.explainedNotRunnable.length === 1 && explained.explainedNotRunnable[0] === valueBearing && !explained.extra.includes(valueBearing),
);
// CONTROL, and it is the load-bearing half: the bucket explains the class
// it was given and nothing else. A command named by no set is still
// `extra` — a third bucket that swallowed unknowns would have deleted the
// remainder rather than shrunk it.
const unknownBeside = runReconciliation({
derived: ['pnpm check:a'],
notRunnableCommands: new Set([valueBearing]),
record: parseRunRecord(['pnpm check:a', valueBearing, 'pnpm check:not-a-family'].join('\n')),
});
t(
'and an unknown command beside it still reads `extra` — the new bucket explains its class only',
unknownBeside.explainedNotRunnable.length === 1 && unknownBeside.extra.length === 1 && unknownBeside.extra[0] === 'pnpm check:not-a-family',
);
// The bucket is DIAGNOSTIC, exactly like the two beside it: the verdict
// reads `unrun` and nothing else, so classifying an entry can never move
// it in either direction (#15115).
t(
'the new bucket cannot move the verdict — it is diagnostic, like the two beside it',
unknownBeside.ok && unknownBeside.unrun.length === 0 && unknownBeside.ran.length === 1,
);

// ── Bookkeeping the classes cannot lose ─────────────────────────────────
const dupes = runReconciliation({ derived: ['pnpm check:a'], record: parseRunRecord(['pnpm check:a', 'pnpm check:a'].join('\n')) });
Expand DownExpand Up@@ -18823,6 +18898,21 @@ function selfTest() {
'the near-miss line refuses the pairing out loud rather than quietly',
runReconciliationLines(nearMiss).join('\n').includes('is NOT paired with it'),
);
// ⭐ #15115, in the rendering: a bucket that classified an entry and then
// printed nothing would leave the runner exactly where `extra` left them.
const explainedText = runReconciliationLines(explained).join('\n');
t(
'the value-bearing bucket gets its OWN labelled line, naming the reason and the command',
explainedText.includes('VALUE-BEARING famil(ies)')
&& explainedText.includes('its argv takes a value from the workflow')
&& explainedText.includes(valueBearing),
);
t(
'and the two buckets beside it keep their own lines, with the remainder heading absent entirely',
explainedText.includes('CI-MEASURED ONLY')
&& explainedText.includes('pending-changeset famil(ies)')
&& !explainedText.includes("Outside this card's derivation"),
);

// ── argv: a two-token flag's value must not become a path ───────────────
const ranSplit = splitArgv([RAN_FLAG, 'ran.list', 'packages/spec/src/index.ts', '--residue']);
Expand DownExpand Up@@ -18891,6 +18981,58 @@ function selfTest() {
}
}

// ── END TO END: the VALUE-BEARING bucket is WIRED, not merely present (#15115) ──
//
// Every unit case above stays green if the `--ran` call site never PASSES
// the value-bearing set — a parameter that defaults to empty is exactly the
// shape that keeps its own tests green while the live mode still dumps the
// class in the remainder, which is the state this card was filed about.
// Only a real run reads the wiring.
//
// The fixture is this tool's OWN answer on this tree, read from `--json`,
// rather than an invocation typed here: a hardcoded spelling would keep
// passing after the renderer that produces the real one changed shape, and
// the two sides of the comparison would stop being the same strings — the
// property the whole `--ran` design rests on.
{
const vbTmp = mkdtempSync(nodePath.join(tmpdir(), 'dg-ran-vb-'));
try {
// A changeset path, because that is what reaches the live value-bearing
// families — and it is the tool's own constant rather than a spelling
// this test invented.
const vbCard = CHANGESET_PROBE_PATH;
const jsonRun = runCli(['--json', vbCard]);
const doc = jsonRun.status === 0 ? JSON.parse(jsonRun.stdout ?? '{}') : null;
const vbRows = [...(doc?.matched ?? []), ...(doc?.alwaysRunsPopulation ?? [])].filter((row) => row.notRunnable);
t('CONTROL: this tree still derives at least one VALUE-BEARING family for a changeset path', Boolean(doc) && vbRows.length >= 1);
if (doc && vbRows.length >= 1) {
const vbCommand = vbRows[0].command;
t('CONTROL: and the runnable union WITHHOLDS it — which is the whole reason the bucket exists', !doc.commands.includes(vbCommand));
const vbRecord = nodePath.join(vbTmp, 'ran-value-bearing.list');
writeFileSync(vbRecord, `${[...doc.commands, vbCommand].join('\n')}\n`);
const vbRun = runCli([RAN_FLAG, vbRecord, vbCard]);
const vbOut = vbRun.stdout ?? '';
t(
'⭐ a real run that RECORDS it lands it in the VALUE-BEARING bucket, with the remainder heading gone entirely',
vbRun.status === 0
&& vbOut.includes('VALUE-BEARING famil(ies)')
&& vbOut.includes(vbCommand)
&& !vbOut.includes("Outside this card's derivation"),
);
t(
'and the reason travels with it, so the runner learns why this invocation is not one they could have derived',
vbOut.includes('its argv takes a value from the workflow'),
);
t(
'while the verdict is unmoved — the bucket is diagnostic, and every derived family is still accounted for',
vbOut.includes(`${doc.commands.length} derived famil(ies) accounted for`),
);
}
} finally {
rmSync(vbTmp, { recursive: true, force: true });
}
}

// The per-case line already printed inside `t()`, streamed as each verdict
// was decided (#14281) — this tail is the summary only, unchanged in shape
// and wording from the pre-streaming version.
Expand Down
Loading