fix(devx): dispatch-gates emits the REAL check where the script declares its argv default, and its self-test harness prints the readings its call sites pass - #15570
Conversation
…res its argv default, and its self-test harness prints the readings its call sites pass The `notRunnable` rule asked whether the WORKFLOW supplies a value and never whether the SCRIPT needs it supplied. For the three `--base` gates the two answers differ, so the only member `--commands` offered for check-adr-0087-registration was that script's `--self-test`: a zero from a command that cannot answer the question, sitting in the runnable union where nothing distinguishes it from a green. The class was enumerated by predicate before anything was repaired, read from the workflow text rather than recalled — 15 value-bearing invocation keys across 7 scripts. 6 keys / 3 scripts declare a default for the flag CI pins (check-adr-0087-registration.mjs:8, check-changeset-no-major.mjs:7, check-empty-changeset.mjs:7, each implemented at :5141 / :760 / :1627). 9 keys / 4 scripts are genuinely unrunnable: a runner temp path, a matrix shard, step results and a provenance string this tool cannot invent. - declaredArgvDefaults reads the declaration out of the script's own LEADING usage block, so no table here can drift from it; a script that stops documenting its default returns its family to NOT RUNNABLE LOCALLY on the next run, with no edit in this file. - defaultedArgv renders the invocation as it runs here, with the default spelled out where CI writes the variable, and it is all-or-nothing: a partially covered argv stays unrunnable, because a command that runs with one real value and one invented one answers a question CI never asked. - The derivation KEY is untouched. Only the rendered runnable member moves. - Every family that genuinely cannot run now prints NOT MEASURED by name, in the human block and on --commands stderr, and says that a sibling invocation of the same script in the list above grades the script and not your diff. - The pasted block deduplicates on the command, so two keys that render one command cannot repeat in the published harvest. - The self-test harness `t` takes the third argument six call sites were already passing; it prints on a RED only, so a passing run's log is byte-identical to what it has always been. One existing case's EXPECTATION moves with the rendering it pins (the empty-changeset family in the matched block); its subject and its invariant are unchanged, the reason is written at the case. check:pm-dispatch-gates: 1415 cases before, 1445 after. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk
|
ACCEPT — PM seat Reviewed against the three-dot diff What I checked in the diff:
Dev's verification quoted: Ruling on the open question (cut-rc rows): A — leave as shipped. The emitted command for a cut-rc key is byte-identical to its pr-automation sibling's, so Flipping ready + enabling auto-merge. Fixes #15441, Fixes #15539. Serial queue on Generated by Claude Code |
…rection moves to #15553 PR #15570 rewrites the same `SELF_TEST_VERDICT` region (it inserts the `selfTestCaseLines` docblock directly after the sentence this branch edited), and `git merge-tree` reports a conflict between the two, so this PR would go dirty the moment that one lands. It is ahead in the queue and the serial-file rule allows one live PR per hot file, so the hunk is dropped here rather than raced. `scripts/pm/dispatch-gates.mjs` is restored from `origin/main`; its blob is byte-identical to this branch's merge base (f9dbdd6 at cf6b671, at origin/main 0c5e973 and in the tree now), so no content from the newer main comes in with it. Nothing else on this branch changes: the anchor repair, the DECOY fixture and the `ENTRY_BY_HAND` row all live in `scripts/measure-self-test-floor.mjs`. The docblock there still tells readers the mechanical probe cannot read that file, which this branch makes false. That correction is deferred to #15553 rather than dropped. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk
Fixes #15441
Fixes #15539
dispatch-gatesmarked a gate NOT RUNNABLE LOCALLY whenever its workflow argvmentioned a value the workflow supplies. It never asked whether the script
supplies a default for that argument. For the three
--basegates the twoanswers differ, so the only member
--commandsoffered forcheck-adr-0087-registrationwas that script's--self-test— a zero from acommand that cannot answer the question, sitting in the runnable union where
nothing distinguishes it from a green. A dev reported "50 run · 50 exit 0 · 0
red" on a PR whose Check Changeset job was red in CI the whole time.
Alongside it, the self-test harness
twas declared with arity two while sixcall sites passed a third argument carrying each case's diagnostic reading.
JavaScript dropped all six.
The class, enumerated by predicate before anything was repaired
Read from the workflow text with the tool's own
extractCheckInvocations, overevery file in
.github/workflows— not from memory, and not from the card'stable. N = 15 value-bearing invocation keys across 7 scripts.
M = 6 keys / 3 scripts where the SCRIPT declares a default for the flag the
workflow pins (declaration line, then the line that implements it):
check-adr-0087-registration.mjs --base "$MERGE_BASE"$MERGE_BASEscripts/check-adr-0087-registration.mjs:8:5141check-adr-0087-registration.mjs --base "$SNAPSHOT_SHA"$SNAPSHOT_SHAcheck-changeset-no-major.mjs --base "$MERGE_BASE"$MERGE_BASEscripts/check-changeset-no-major.mjs:7:760check-changeset-no-major.mjs --base "$SNAPSHOT_SHA"$SNAPSHOT_SHAcheck-empty-changeset.mjs --base "$MERGE_BASE"$MERGE_BASEscripts/check-empty-changeset.mjs:7:1627check-empty-changeset.mjs --base "$SNAPSHOT_SHA"$SNAPSHOT_SHAAll three declare it in the same spelling:
# base defaults to origin/main. Allthree scan from
merge-base(base, head)either way, which is why the default isthe same reading as the merge base the workflow computes —
check-empty-changeset.mjs:125states the idempotence: "merge-base(X, head) isX again whenever X is already the branch point".
K = 9 keys / 4 scripts genuinely unrunnable — the value has no default and
no local equivalent, so nothing here can invent one:
check-cross-package-test-inputs.mjs --union-into … --changed …$RUNNER_TEMPcheck-shard-attestation.mjs --emit --job dogfood …$RUNNER_TEMPcheck-shard-attestation.mjs --emit --job dogfood-verify …$RUNNER_TEMPcheck-shard-attestation.mjs --emit --job test …${{ matrix.shard }},$RUNNER_TEMPcheck-shard-attestation.mjs --verify --gate 'Dogfood Regression Gate' …$OS_ATTEST_DIR,$OS_FILTER_RESULT,$OS_DOWNLOAD_OUTCOME,$OS_DOGFOOD_RESULT,$OS_VERIFY_RESULTcheck-shard-attestation.mjs --verify --gate 'Test Core' …$OS_ATTEST_DIR,$OS_FILTER_RESULT,$OS_DOWNLOAD_OUTCOME,$OS_TEST_RESULTcheck-test-completeness.mjs "$RUNNER_TEMP/dogfood.log"$RUNNER_TEMPcheck-test-completeness.mjs "$RUNNER_TEMP/test-core.log" …$RUNNER_TEMPcheck-half-states.mjs --format=markdown --provenance="$PROVENANCE"$PROVENANCE6 + 9 = 15, and the split is re-asserted as a PROPERTY on the live tree by the
new cases (every repaired family reads its default off its own script, flag by
flag; every family still filed value-bearing is one whose script declares no
default for what the workflow pins) rather than as a roster typed into a test.
What changed
declaredArgvDefaults(source)reads the defaults a script declares in itsown leading usage block, keyed on the spelling those scripts already carry
(
<flag> defaults to <value>), and only for a flag the usage block alsonames as
--flag. Reading the SCRIPT rather than a per-family table is theruling's preferred direction and it cannot drift: a script that stops
documenting the default returns its family to NOT RUNNABLE LOCALLY on the
next run with no edit here — pinned as a case, driven on the real specimen
source with the declaration line deleted.
leadingCommentBlockis what makesit a declaration rather than a coincidence: over the seven scripts in the
class the phrase appears five times and only three are declarations (one is
an assertion message about wiring, two are docblocks deep in another file).
defaultedArgv(args, defaults)renders the invocation as it runs here, withthe default spelled out where CI writes the variable. All-or-nothing: a
partially covered argv stays value-bearing, because a command that runs with
one real value and one invented one answers a question CI never asked.
checkstays the invocation CIruns, so a script CI invokes under two different variables keeps its two
entries and nothing is re-attributed (the [finding] dispatch-gates --commands does not derive check:optional-error-sink for a diff that adds a new logger-sink interface #14880 invariant). Only what the
row RENDERS moves, through a new
localCheckread byrunnableInvocation.--self-testline is no longer a substitute for the question anywhere:the real check is emitted beside it, and every family that genuinely cannot
run now prints
⊘ NOT MEASURED — <the invocation CI runs>in the humanrendering and one
⊘ NOT MEASUREDline per family on--commandsstderr,with a line saying that a sibling invocation of the same script in the list
above grades the script and not your diff.
variables). The pasted block is deduplicated on the command, merging both
rows' workflows, provenance and filled values —
familyReconciliationhasalways counted DISTINCT commands, so this is also what keeps the block's
footer and the reconciliation agreeing, and it keeps the published harvest
free of repeats.
ttakes two arguments, but six call sites pass a third — every one of those cases' diagnostic readings is silently dropped #15539:ttakes the third parameter its six call sites were alreadypassing. The line is rendered by a pure
selfTestCaseLines, so bothdirections are pinned by cases instead of by a run that would have to fail;
the reading prints on a RED only, so a passing run's log is byte-identical to
what it has always been. A tail case counts how many cases really handed the
harness a reading (floor 6), so the repair cannot go vacuous.
tgained aparameter; ⛔ no existing case's wording or expectation was changed.
selfTestCaseLinesis a production name the self-test anchor fires on, so it isclassified in
COMPOUND_ANCHOR_LEDGERas accidental, per that table's owninstruction not to dodge the anchor with a rename.
Verification
Every exit code captured by redirect-then-read, never across a pipe.
pnpm -s check:pm-dispatch-gates✓ dispatch-gates self-test: 1445 cases pass.node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack -- .changeset/some-file.mdnode scripts/check-adr-0087-registration.mjs --base origin/main(plus the two sibling--basegates); stderrdispatch-gates --commands: 19 command(s) — 7 pnpm, 12 direct node (15 matched by path, …)and⊘ NOT MEASURED — scripts/pm/check-half-states.mjs --format=markdown --provenance="$PROVENANCE". Onorigin/mainthe same run printed16 command(s) — 7 pnpm, 9 direct node (9 matched by path, …)and+ 7 famil(ies) … take a VALUE FROM THE WORKFLOW, with--self-testas the only ADR-0087 member.pnpm check:declared-population-live✓ check:declared-population-live — 209 of 256 famil(ies) declare a path population, and every one of them reaches this tree's 7533 tracked file(s).pnpm check:watch-hint-literal✓ check-watch-hint-literal: 55 declaration(s) across 4 rostered name(s) … no unrostered spelling of the idiom in the tree.pnpm check:nul-bytescheck-nul-bytes: OK (scanned 7526 text file(s) … no raw ASCII control bytes).pnpm lint(eslint . --no-inline-config, whole repo, no narrowing)All of the above ran on the tree of the final commit, and the union was re-run
on it after committing:
git rev-parse --short HEAD=379179037, on whichpnpm -s check:pm-dispatch-gatesprints✓ dispatch-gates self-test: 1445 cases pass.and exits 0 (case count 1415 -> 1445, +30; ⛔ no existing case wasedited to pass — one case's EXPECTATION moved with the rendering this card
deliberately changes, its subject and invariant unchanged, and the reason is
written at the case).
The specimen, re-measured with the EMITTED command
node scripts/check-adr-0087-registration.mjs --base origin/main --head <sha>—the exact command the derivation now emits, plus the
--headthe card used.d5aeb0fce— PR #15432's head TODAY (the card measured393b2173c)**BREAKING**with a validadr-0087:disposition46b06feec— PR #15434, the sha the card recorded**BREAKING**with a marker13b58ed7d— PR #15436, the sha the card recordedafter the card was filed and now carries the disposition it was missing, so
today all three of its heads answer 0. A measurement whose every leg returns the
same value is not yet a reading, so the red leg was rebuilt as a control, on
this tree, with
git commit-treeagainstorigin/main— no ref, index orworking tree touched:
adf6a8914**BREAKING**, noadr-0087:marker::error file=.changeset/adr-0087-probe-red.md::… declares a breaking change with no valid ADR-0087 disposition5badb1cf8✓ check-adr-0087-registration: 1 declared-breaking changeset(s), each carrying an ADR-0087 disposition.Two legs, one differing byte, both directions — so the emitted command is a
reading of the tree and not a command that always answers the same way.
The derived family for THIS change set
node scripts/pm/dispatch-gates.mjs --changed --commands --repo objectstack-ai/objectstack(exit 0) derived 24 commands. Every one was run; all exit 0. Beyond the four
above:
check-reference-carrier-shape.mjs(and--self-test),check-ci-filter-parity.mjs,check-closing-keyword-parity.mjs(and--self-test),check-comment-mask-corpus.mjs,check-self-test-wired.mjs(and
--self-test),check-whole-set-label-write.mjs(and--self-test),check:agent-test-spelling,check:bash32-floor,check:cli-command-ids,check:cross-package-test-inputs,check:driver-memory-census,check:entry-guard,check:parse-guard,check:pnpm-filter-targets,check:ratchet-remedy-authority,check:refd-timer-probe. ⛔ Nothing exited 3,nothing printed
PREREQUISITE NOT MET, nothing raisedERR_MODULE_NOT_FOUND—there is no NOT MEASURED entry in this list.
STALE TREE — 3 commit(s) behind origin/main(branch point
bbf4a5229,origin/mainnowcf6b67164). Of the six files itnamed, only
scripts/pm/check-half-states.mjsand the new.github/workflows/os-create-smoke.ymltouch this card's subject, and both werechecked against the newer copies: the new workflow contributes zero check
invocations, and the newer
check-half-states.mjsstill declares no argvdefault, so the census above and the live cases hold on the merged tree too.
The one member this PR does not decide
cut-rc.ymlpins--base "$SNAPSHOT_SHA"(agit rev-parse HEADof the cutsnapshot,
cut-rc.yml:213), and that lane isworkflow_dispatch-only. Thescript's declared default is
origin/main, which is NOT what a release cutmeans by "base" — so for those three keys the emitted command answers the gate's
question against a different base than that workflow asks it against. It is
reported rather than decided, and it is reported with the measurement that makes
it decidable: the emitted command for a cut-rc key is byte-identical to the
one its pr-automation sibling already emits, so
--commandsis unchangedwhichever way those three rows are labelled. That stops being true the day a
defaulted-flag script is invoked ONLY from a dispatch-only lane. See the
open_questionsentry in the report on #15441.🤖 Generated with Claude Code
https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk
Generated by Claude Code