tooling(pm): give dispatch-gates --ran an explained bucket for the value-bearing class - #15165

Merged
os-steve merged 1 commit into
mainfrom
claude/issue-15115-ran-value-bearing-bucket
Sep 4, 2026
Merged

tooling(pm): give dispatch-gates --ran an explained bucket for the value-bearing class#15165
os-steve merged 1 commit into
mainfrom
claude/issue-15115-ran-value-bearing-bucket

Conversation

@claude

@claudeclaudeBot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Fixes#15115

runReconciliation in scripts/pm/dispatch-gates.mjs classifies a command the
runner RECORDED that is not in the derived runnable set. It had three outcomes,
two of which are "explained, not a mistake": a CI-measured-only family, and a
pending-changeset family. The value-bearing class — an invocation whose argv
takes a value from the workflow, added by PR #15114 and subtracted from
--commands by the second subtraction in commandsFor — had no such bucket, so
a recorded one landed in extra.

Reproduced at the merge base, before any edit

Merge base 4283b72a. Record = the 15 commands --commands derives for a
changeset path, plus the live pr-automation.yml invocation
node scripts/check-empty-changeset.mjs --base "$MERGE_BASE":

$ node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack \
--ran repro-ran.list .changeset/foo.md
Run reconciliation — 15 derived, 15 run, 0 NOT-MEASURED, 0 UNRUN.
Outside this card's derivation (1) — recorded, and named by nothing this run derived. Not an error: a run beyond the union costs nothing.
- node scripts/check-empty-changeset.mjs --base "$MERGE_BASE"
✓ dispatch-gates --ran: 15 derived famil(ies) accounted for — 15 run, 0 NOT-MEASURED.

That caption is the defect in one sentence: this run DID derive that family and
then classified it out of the union on purpose, so "named by nothing this run
derived" is the one thing that is false about it. No reason is attached, and the
runner is left to guess whether they typed something wrong.

The bucket

  • runReconciliation takes notRunnableCommands, defaulting to an empty set so
    every existing caller's verdict is byte-identical.
  • It is checked BESIDE ciOnlyCommands and before pendingCommands, because
    the file already groups those two: they are the two subtractions commandsFor
    makes from the runnable union, in that order, while the pending families are a
    different fact (a path that did not exist at derivation time). ciOnly keeps
    first precedence, matching commandsFor, so one command can never produce two
    counts for one omission.
  • The set is built at the --ran call site by notRunnableCommandSet(matchedRows, alwaysRunsRows) — the SAME expression commandsFor subtracts with, off the
    same rows — so the union and the reconciliation cannot drift about which
    invocations are withheld.
  • recon.ok still reads unrun.length only. --commands output, the
    classifier, the derivation keys and every live key are untouched.

Rendered on its own labelled line, in the shape the two beside it use. Same
record, same card, on this branch:

Run reconciliation — 15 derived, 15 run, 0 NOT-MEASURED, 0 UNRUN.
Classified by this tool, no explanation owed (1) — VALUE-BEARING famil(ies): its argv takes a value from the workflow, so it is recorded, not derived as runnable:
- node scripts/check-empty-changeset.mjs --base "$MERGE_BASE"
✓ dispatch-gates --ran: 15 derived famil(ies) accounted for — 15 run, 0 NOT-MEASURED.

The remainder heading is gone entirely and the verdict is unmoved.

Self-test — 7 new cases, 1353 total

Unit, beside the existing explained case (whose fixture now carries a fourth
recorded entry, the live value-bearing spelling):

  • a recorded value-bearing command classifies into explainedNotRunnable and is
    NOT in extra;
  • CONTROL: an unknown command recorded beside it still reads extra — the
    bucket explains its class and nothing else, which is the half that keeps this
    from deleting the remainder rather than shrinking it;
  • the two existing buckets keep their verdicts in the same run
    (explainedCiOnly 1, explainedPending 1, extra 0);
  • the bucket cannot move the verdict: ok true, unrun 0;
  • the rendering carries its own labelled line, the reason, and the command, with
    the two neighbouring lines still present and the remainder heading absent.

End to end, a real CLI run (three cases), because every unit case above stays
green if the --ran call site never PASSES the set — a parameter defaulting to
empty is exactly the shape that keeps its own tests green while the live mode
still dumps the class in the remainder. Its fixture is the tool's own --json
answer on this tree rather than a spelling typed into the test, so it cannot
outlive a change to the renderer that produces the real invocation.

Gates — all green, run at head 5fb6e3a7

Re-derived from the real change set with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands (no paths) after the commit; the
20 families match the dispatch list exactly, nothing added. Each exit code was
captured by redirecting first, never after a pipe; every line below is the
gate's own verdict line.

gateverdict line
check-ci-filter-parity.mjsOK: all 143 declared cross-package glob(s) (99 unique) are covered by core or crosspkg, every crosspkg entry still covers one, and the test job's if: still names both filters.
check-closing-keyword-parity.mjscheck-closing-keyword-parity: OK (3 parsers agree on all 9 keywords and both measured separators; sweep found 5 file(s) carrying the grammar across 8245 tracked file(s), all registered).
check-closing-keyword-parity.mjs --self-test✓ 24 assertions, 5 mutations of the shipped parsers each driven to red.
check-comment-mask-corpus.mjs✓ comment-mask corpus sweep: 5858 files, 0 disagree, 0 unparseable, 49.0s (comparator self-test: 12 cases pass).
check-self-test-wired.mjs✓ every one of the 166 script(s) CI runs that ship a --self-test has that self-test run by CI.
check-self-test-wired.mjs --self-test4 live ledger row(s) verified ... 7 declared batteries, 50 cases registered, every battery at or above its pinned floor.
check-whole-set-label-write.mjs✓ 0 violations — 258 file(s) over 3 root(s) · 160 uses: pin(s) over 18 distinct action(s) judged · 0 allowlist entr(ies)
check-whole-set-label-write.mjs --self-test✓ all cases pass (24 fixture trees + 5 refusals + 1 allowlist hatch)
pnpm check:agent-test-spelling✓ 0 violations — 438 file(s) · 5916 bare -- token(s) · 1408 launcher-rooted run(s) · 9 separator(s) JUDGED
pnpm check:bash32-floor✓ self-test: 153 cases pass. / ✓ 26 tracked shell file(s) ... name no bash 4+ construct
pnpm check:cli-command-ids✓ 324 command-id literal(s) across 114 file(s) outside packages/cli all resolve to a real command path
pnpm check:cross-package-test-inputsOK: 26 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob.
pnpm check:declared-population-live✓ 198 of 249 famil(ies) declare a path population, and every one of them reaches this tree's 8245 tracked file(s).
pnpm check:entry-guard✓ 207 scripts/ file(s) — every entry guard goes through invoked-as.mjs; 153 export bindings, 153 of them inert on import
pnpm check:nul-bytescheck-nul-bytes: OK (scanned 8238 text file(s) ... no raw ASCII control bytes).
pnpm check:parse-guard✓ 206 scripts/ file(s) — every TypeScript parse goes through ts-parse.mjs.
pnpm check:pm-dispatch-gates✓ dispatch-gates self-test: 1353 cases pass.
pnpm check:pnpm-filter-targets✓ 142/181 --filter occurrence(s) across 33 file(s) resolve against 79 workspace package(s)
pnpm check:refd-timer-probeOK 5853 source file(s) swept; the process-global timer probe is read in packages/qa/refd-timer-testkit/src/index.ts and nowhere else.
pnpm check:watch-hint-literal✓ 48 declaration(s) across 4 rostered name(s) ... no unrostered spelling of the idiom in the tree.
bare-root-worklist.mjs --self-test (not in the derivation; asserted green because a parallel flight edits that file)OK self-test: 54 live row(s), 46 unreachable as spelled, 46 recorded verdict(s) — none stale, none missing, none contradicted.

Repo-wide ESLint, not narrowed: pnpm lint (eslint . --no-inline-config) through
the shared verify lock — os-verify-lock: VERDICT command-exit 0 · held the lock 66s.
The dispatch-gates --self-test also ran through the lock: VERDICT command-exit 0 · held the lock 354s. Both wall-clock figures are shared-box seconds, as the lock's
own verdict line states.

NOT MEASURED, in its own words: check-test-completeness is itself one of this
diff's value-bearing families — its argv takes $RUNNER_TEMP from the workflow,
so it is outside --commands and has no local run. Same for
check-shard-attestation.mjs --emit ... and the --union-into spelling of
check-cross-package-test-inputs (the bare pnpm check:cross-package-test-inputs
spelling of that family IS in the table above and ran green).

Nothing is published from any package (scripts/pm/**, no .md), so
skip-changeset is applied.


Generated by Claude Code

…lue-bearing class
`runReconciliation` classified a recorded command that is not in the derived
runnable set into three outcomes, two of which are "explained, not a mistake":
a CI-measured-only family and a pending-changeset family. The value-bearing
class added for #15083 — an invocation whose argv takes a value from the
workflow, which `commandsFor` subtracts from the union for the same reason it
subtracts the CI-measured one — had no such bucket, so a dev who recorded one
landed in `extra` under a caption that reads "named by nothing this run
derived": the one sentence that is false about it, because this run derived it
and then classified it out.
Third bucket, built from `notRunnableCommandSet` off the same rows the
CI-measured set is built from, checked beside it (the two subtractions
`commandsFor` makes, in that order) and before the `extra` fallback, and
rendered on its own labelled line naming the reason. Diagnostic only:
`recon.ok` still reads `unrun.length`, `--commands` is untouched, and no
derivation key moves.
Measured at the merge base 4283b72: a record of the 15 derived commands for a
changeset path plus the live `node scripts/check-empty-changeset.mjs --base
"$MERGE_BASE"` invocation reported
Outside this card's derivation (1) — recorded, and named by nothing this run
derived.
- node scripts/check-empty-changeset.mjs --base "$MERGE_BASE"
and the same record on this tree reports the labelled bucket instead, with the
remainder heading gone and the verdict unmoved (15 derived, 15 run, exit 0).
Self-test: seven cases, three of them a real CLI run whose fixture is the
tool's own `--json` answer on this tree — a parameter defaulting to empty is
exactly the shape that keeps unit cases green while the live mode still dumps
the class in the remainder.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019RfFHiRCSs3JXLK4cwcfox
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/mskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants

@os-steve@claude
, '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

tooling(pm): give dispatch-gates --ran an explained bucket for the value-bearing class - #15165

Merged
os-steve merged 1 commit into
mainfrom
claude/issue-15115-ran-value-bearing-bucket
Sep 4, 2026
Merged

tooling(pm): give dispatch-gates --ran an explained bucket for the value-bearing class#15165
os-steve merged 1 commit into
mainfrom
claude/issue-15115-ran-value-bearing-bucket

Conversation

@claude

@claudeclaudeBot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Fixes#15115

runReconciliation in scripts/pm/dispatch-gates.mjs classifies a command the
runner RECORDED that is not in the derived runnable set. It had three outcomes,
two of which are "explained, not a mistake": a CI-measured-only family, and a
pending-changeset family. The value-bearing class — an invocation whose argv
takes a value from the workflow, added by PR #15114 and subtracted from
--commands by the second subtraction in commandsFor — had no such bucket, so
a recorded one landed in extra.

Reproduced at the merge base, before any edit

Merge base 4283b72a. Record = the 15 commands --commands derives for a
changeset path, plus the live pr-automation.yml invocation
node scripts/check-empty-changeset.mjs --base "$MERGE_BASE":

$ node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack \
--ran repro-ran.list .changeset/foo.md
Run reconciliation — 15 derived, 15 run, 0 NOT-MEASURED, 0 UNRUN.
Outside this card's derivation (1) — recorded, and named by nothing this run derived. Not an error: a run beyond the union costs nothing.
- node scripts/check-empty-changeset.mjs --base "$MERGE_BASE"
✓ dispatch-gates --ran: 15 derived famil(ies) accounted for — 15 run, 0 NOT-MEASURED.

That caption is the defect in one sentence: this run DID derive that family and
then classified it out of the union on purpose, so "named by nothing this run
derived" is the one thing that is false about it. No reason is attached, and the
runner is left to guess whether they typed something wrong.

The bucket

  • runReconciliation takes notRunnableCommands, defaulting to an empty set so
    every existing caller's verdict is byte-identical.
  • It is checked BESIDE ciOnlyCommands and before pendingCommands, because
    the file already groups those two: they are the two subtractions commandsFor
    makes from the runnable union, in that order, while the pending families are a
    different fact (a path that did not exist at derivation time). ciOnly keeps
    first precedence, matching commandsFor, so one command can never produce two
    counts for one omission.
  • The set is built at the --ran call site by notRunnableCommandSet(matchedRows, alwaysRunsRows) — the SAME expression commandsFor subtracts with, off the
    same rows — so the union and the reconciliation cannot drift about which
    invocations are withheld.
  • recon.ok still reads unrun.length only. --commands output, the
    classifier, the derivation keys and every live key are untouched.

Rendered on its own labelled line, in the shape the two beside it use. Same
record, same card, on this branch:

Run reconciliation — 15 derived, 15 run, 0 NOT-MEASURED, 0 UNRUN.
Classified by this tool, no explanation owed (1) — VALUE-BEARING famil(ies): its argv takes a value from the workflow, so it is recorded, not derived as runnable:
- node scripts/check-empty-changeset.mjs --base "$MERGE_BASE"
✓ dispatch-gates --ran: 15 derived famil(ies) accounted for — 15 run, 0 NOT-MEASURED.

The remainder heading is gone entirely and the verdict is unmoved.

Self-test — 7 new cases, 1353 total

Unit, beside the existing explained case (whose fixture now carries a fourth
recorded entry, the live value-bearing spelling):

  • a recorded value-bearing command classifies into explainedNotRunnable and is
    NOT in extra;
  • CONTROL: an unknown command recorded beside it still reads extra — the
    bucket explains its class and nothing else, which is the half that keeps this
    from deleting the remainder rather than shrinking it;
  • the two existing buckets keep their verdicts in the same run
    (explainedCiOnly 1, explainedPending 1, extra 0);
  • the bucket cannot move the verdict: ok true, unrun 0;
  • the rendering carries its own labelled line, the reason, and the command, with
    the two neighbouring lines still present and the remainder heading absent.

End to end, a real CLI run (three cases), because every unit case above stays
green if the --ran call site never PASSES the set — a parameter defaulting to
empty is exactly the shape that keeps its own tests green while the live mode
still dumps the class in the remainder. Its fixture is the tool's own --json
answer on this tree rather than a spelling typed into the test, so it cannot
outlive a change to the renderer that produces the real invocation.

Gates — all green, run at head 5fb6e3a7

Re-derived from the real change set with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands (no paths) after the commit; the
20 families match the dispatch list exactly, nothing added. Each exit code was
captured by redirecting first, never after a pipe; every line below is the
gate's own verdict line.

gateverdict line
check-ci-filter-parity.mjsOK: all 143 declared cross-package glob(s) (99 unique) are covered by core or crosspkg, every crosspkg entry still covers one, and the test job's if: still names both filters.
check-closing-keyword-parity.mjscheck-closing-keyword-parity: OK (3 parsers agree on all 9 keywords and both measured separators; sweep found 5 file(s) carrying the grammar across 8245 tracked file(s), all registered).
check-closing-keyword-parity.mjs --self-test✓ 24 assertions, 5 mutations of the shipped parsers each driven to red.
check-comment-mask-corpus.mjs✓ comment-mask corpus sweep: 5858 files, 0 disagree, 0 unparseable, 49.0s (comparator self-test: 12 cases pass).
check-self-test-wired.mjs✓ every one of the 166 script(s) CI runs that ship a --self-test has that self-test run by CI.
check-self-test-wired.mjs --self-test4 live ledger row(s) verified ... 7 declared batteries, 50 cases registered, every battery at or above its pinned floor.
check-whole-set-label-write.mjs✓ 0 violations — 258 file(s) over 3 root(s) · 160 uses: pin(s) over 18 distinct action(s) judged · 0 allowlist entr(ies)
check-whole-set-label-write.mjs --self-test✓ all cases pass (24 fixture trees + 5 refusals + 1 allowlist hatch)
pnpm check:agent-test-spelling✓ 0 violations — 438 file(s) · 5916 bare -- token(s) · 1408 launcher-rooted run(s) · 9 separator(s) JUDGED
pnpm check:bash32-floor✓ self-test: 153 cases pass. / ✓ 26 tracked shell file(s) ... name no bash 4+ construct
pnpm check:cli-command-ids✓ 324 command-id literal(s) across 114 file(s) outside packages/cli all resolve to a real command path
pnpm check:cross-package-test-inputsOK: 26 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob.
pnpm check:declared-population-live✓ 198 of 249 famil(ies) declare a path population, and every one of them reaches this tree's 8245 tracked file(s).
pnpm check:entry-guard✓ 207 scripts/ file(s) — every entry guard goes through invoked-as.mjs; 153 export bindings, 153 of them inert on import
pnpm check:nul-bytescheck-nul-bytes: OK (scanned 8238 text file(s) ... no raw ASCII control bytes).
pnpm check:parse-guard✓ 206 scripts/ file(s) — every TypeScript parse goes through ts-parse.mjs.
pnpm check:pm-dispatch-gates✓ dispatch-gates self-test: 1353 cases pass.
pnpm check:pnpm-filter-targets✓ 142/181 --filter occurrence(s) across 33 file(s) resolve against 79 workspace package(s)
pnpm check:refd-timer-probeOK 5853 source file(s) swept; the process-global timer probe is read in packages/qa/refd-timer-testkit/src/index.ts and nowhere else.
pnpm check:watch-hint-literal✓ 48 declaration(s) across 4 rostered name(s) ... no unrostered spelling of the idiom in the tree.
bare-root-worklist.mjs --self-test (not in the derivation; asserted green because a parallel flight edits that file)OK self-test: 54 live row(s), 46 unreachable as spelled, 46 recorded verdict(s) — none stale, none missing, none contradicted.

Repo-wide ESLint, not narrowed: pnpm lint (eslint . --no-inline-config) through
the shared verify lock — os-verify-lock: VERDICT command-exit 0 · held the lock 66s.
The dispatch-gates --self-test also ran through the lock: VERDICT command-exit 0 · held the lock 354s. Both wall-clock figures are shared-box seconds, as the lock's
own verdict line states.

NOT MEASURED, in its own words: check-test-completeness is itself one of this
diff's value-bearing families — its argv takes $RUNNER_TEMP from the workflow,
so it is outside --commands and has no local run. Same for
check-shard-attestation.mjs --emit ... and the --union-into spelling of
check-cross-package-test-inputs (the bare pnpm check:cross-package-test-inputs
spelling of that family IS in the table above and ran green).

Nothing is published from any package (scripts/pm/**, no .md), so
skip-changeset is applied.


Generated by Claude Code

…lue-bearing class
`runReconciliation` classified a recorded command that is not in the derived
runnable set into three outcomes, two of which are "explained, not a mistake":
a CI-measured-only family and a pending-changeset family. The value-bearing
class added for #15083 — an invocation whose argv takes a value from the
workflow, which `commandsFor` subtracts from the union for the same reason it
subtracts the CI-measured one — had no such bucket, so a dev who recorded one
landed in `extra` under a caption that reads "named by nothing this run
derived": the one sentence that is false about it, because this run derived it
and then classified it out.
Third bucket, built from `notRunnableCommandSet` off the same rows the
CI-measured set is built from, checked beside it (the two subtractions
`commandsFor` makes, in that order) and before the `extra` fallback, and
rendered on its own labelled line naming the reason. Diagnostic only:
`recon.ok` still reads `unrun.length`, `--commands` is untouched, and no
derivation key moves.
Measured at the merge base 4283b72: a record of the 15 derived commands for a
changeset path plus the live `node scripts/check-empty-changeset.mjs --base
"$MERGE_BASE"` invocation reported
Outside this card's derivation (1) — recorded, and named by nothing this run
derived.
- node scripts/check-empty-changeset.mjs --base "$MERGE_BASE"
and the same record on this tree reports the labelled bucket instead, with the
remainder heading gone and the verdict unmoved (15 derived, 15 run, exit 0).
Self-test: seven cases, three of them a real CLI run whose fixture is the
tool's own `--json` answer on this tree — a parameter defaulting to empty is
exactly the shape that keeps unit cases green while the live mode still dumps
the class in the remainder.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019RfFHiRCSs3JXLK4cwcfox
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/mskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants

@os-steve@claude
, '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

tooling(pm): give dispatch-gates --ran an explained bucket for the value-bearing class - #15165

Merged
os-steve merged 1 commit into
mainfrom
claude/issue-15115-ran-value-bearing-bucket
Sep 4, 2026
Merged

tooling(pm): give dispatch-gates --ran an explained bucket for the value-bearing class#15165
os-steve merged 1 commit into
mainfrom
claude/issue-15115-ran-value-bearing-bucket

Conversation

@claude

@claudeclaudeBot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Fixes#15115

runReconciliation in scripts/pm/dispatch-gates.mjs classifies a command the
runner RECORDED that is not in the derived runnable set. It had three outcomes,
two of which are "explained, not a mistake": a CI-measured-only family, and a
pending-changeset family. The value-bearing class — an invocation whose argv
takes a value from the workflow, added by PR #15114 and subtracted from
--commands by the second subtraction in commandsFor — had no such bucket, so
a recorded one landed in extra.

Reproduced at the merge base, before any edit

Merge base 4283b72a. Record = the 15 commands --commands derives for a
changeset path, plus the live pr-automation.yml invocation
node scripts/check-empty-changeset.mjs --base "$MERGE_BASE":

$ node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack \
--ran repro-ran.list .changeset/foo.md
Run reconciliation — 15 derived, 15 run, 0 NOT-MEASURED, 0 UNRUN.
Outside this card's derivation (1) — recorded, and named by nothing this run derived. Not an error: a run beyond the union costs nothing.
- node scripts/check-empty-changeset.mjs --base "$MERGE_BASE"
✓ dispatch-gates --ran: 15 derived famil(ies) accounted for — 15 run, 0 NOT-MEASURED.

That caption is the defect in one sentence: this run DID derive that family and
then classified it out of the union on purpose, so "named by nothing this run
derived" is the one thing that is false about it. No reason is attached, and the
runner is left to guess whether they typed something wrong.

The bucket

  • runReconciliation takes notRunnableCommands, defaulting to an empty set so
    every existing caller's verdict is byte-identical.
  • It is checked BESIDE ciOnlyCommands and before pendingCommands, because
    the file already groups those two: they are the two subtractions commandsFor
    makes from the runnable union, in that order, while the pending families are a
    different fact (a path that did not exist at derivation time). ciOnly keeps
    first precedence, matching commandsFor, so one command can never produce two
    counts for one omission.
  • The set is built at the --ran call site by notRunnableCommandSet(matchedRows, alwaysRunsRows) — the SAME expression commandsFor subtracts with, off the
    same rows — so the union and the reconciliation cannot drift about which
    invocations are withheld.
  • recon.ok still reads unrun.length only. --commands output, the
    classifier, the derivation keys and every live key are untouched.

Rendered on its own labelled line, in the shape the two beside it use. Same
record, same card, on this branch:

Run reconciliation — 15 derived, 15 run, 0 NOT-MEASURED, 0 UNRUN.
Classified by this tool, no explanation owed (1) — VALUE-BEARING famil(ies): its argv takes a value from the workflow, so it is recorded, not derived as runnable:
- node scripts/check-empty-changeset.mjs --base "$MERGE_BASE"
✓ dispatch-gates --ran: 15 derived famil(ies) accounted for — 15 run, 0 NOT-MEASURED.

The remainder heading is gone entirely and the verdict is unmoved.

Self-test — 7 new cases, 1353 total

Unit, beside the existing explained case (whose fixture now carries a fourth
recorded entry, the live value-bearing spelling):

  • a recorded value-bearing command classifies into explainedNotRunnable and is
    NOT in extra;
  • CONTROL: an unknown command recorded beside it still reads extra — the
    bucket explains its class and nothing else, which is the half that keeps this
    from deleting the remainder rather than shrinking it;
  • the two existing buckets keep their verdicts in the same run
    (explainedCiOnly 1, explainedPending 1, extra 0);
  • the bucket cannot move the verdict: ok true, unrun 0;
  • the rendering carries its own labelled line, the reason, and the command, with
    the two neighbouring lines still present and the remainder heading absent.

End to end, a real CLI run (three cases), because every unit case above stays
green if the --ran call site never PASSES the set — a parameter defaulting to
empty is exactly the shape that keeps its own tests green while the live mode
still dumps the class in the remainder. Its fixture is the tool's own --json
answer on this tree rather than a spelling typed into the test, so it cannot
outlive a change to the renderer that produces the real invocation.

Gates — all green, run at head 5fb6e3a7

Re-derived from the real change set with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands (no paths) after the commit; the
20 families match the dispatch list exactly, nothing added. Each exit code was
captured by redirecting first, never after a pipe; every line below is the
gate's own verdict line.

gateverdict line
check-ci-filter-parity.mjsOK: all 143 declared cross-package glob(s) (99 unique) are covered by core or crosspkg, every crosspkg entry still covers one, and the test job's if: still names both filters.
check-closing-keyword-parity.mjscheck-closing-keyword-parity: OK (3 parsers agree on all 9 keywords and both measured separators; sweep found 5 file(s) carrying the grammar across 8245 tracked file(s), all registered).
check-closing-keyword-parity.mjs --self-test✓ 24 assertions, 5 mutations of the shipped parsers each driven to red.
check-comment-mask-corpus.mjs✓ comment-mask corpus sweep: 5858 files, 0 disagree, 0 unparseable, 49.0s (comparator self-test: 12 cases pass).
check-self-test-wired.mjs✓ every one of the 166 script(s) CI runs that ship a --self-test has that self-test run by CI.
check-self-test-wired.mjs --self-test4 live ledger row(s) verified ... 7 declared batteries, 50 cases registered, every battery at or above its pinned floor.
check-whole-set-label-write.mjs✓ 0 violations — 258 file(s) over 3 root(s) · 160 uses: pin(s) over 18 distinct action(s) judged · 0 allowlist entr(ies)
check-whole-set-label-write.mjs --self-test✓ all cases pass (24 fixture trees + 5 refusals + 1 allowlist hatch)
pnpm check:agent-test-spelling✓ 0 violations — 438 file(s) · 5916 bare -- token(s) · 1408 launcher-rooted run(s) · 9 separator(s) JUDGED
pnpm check:bash32-floor✓ self-test: 153 cases pass. / ✓ 26 tracked shell file(s) ... name no bash 4+ construct
pnpm check:cli-command-ids✓ 324 command-id literal(s) across 114 file(s) outside packages/cli all resolve to a real command path
pnpm check:cross-package-test-inputsOK: 26 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob.
pnpm check:declared-population-live✓ 198 of 249 famil(ies) declare a path population, and every one of them reaches this tree's 8245 tracked file(s).
pnpm check:entry-guard✓ 207 scripts/ file(s) — every entry guard goes through invoked-as.mjs; 153 export bindings, 153 of them inert on import
pnpm check:nul-bytescheck-nul-bytes: OK (scanned 8238 text file(s) ... no raw ASCII control bytes).
pnpm check:parse-guard✓ 206 scripts/ file(s) — every TypeScript parse goes through ts-parse.mjs.
pnpm check:pm-dispatch-gates✓ dispatch-gates self-test: 1353 cases pass.
pnpm check:pnpm-filter-targets✓ 142/181 --filter occurrence(s) across 33 file(s) resolve against 79 workspace package(s)
pnpm check:refd-timer-probeOK 5853 source file(s) swept; the process-global timer probe is read in packages/qa/refd-timer-testkit/src/index.ts and nowhere else.
pnpm check:watch-hint-literal✓ 48 declaration(s) across 4 rostered name(s) ... no unrostered spelling of the idiom in the tree.
bare-root-worklist.mjs --self-test (not in the derivation; asserted green because a parallel flight edits that file)OK self-test: 54 live row(s), 46 unreachable as spelled, 46 recorded verdict(s) — none stale, none missing, none contradicted.

Repo-wide ESLint, not narrowed: pnpm lint (eslint . --no-inline-config) through
the shared verify lock — os-verify-lock: VERDICT command-exit 0 · held the lock 66s.
The dispatch-gates --self-test also ran through the lock: VERDICT command-exit 0 · held the lock 354s. Both wall-clock figures are shared-box seconds, as the lock's
own verdict line states.

NOT MEASURED, in its own words: check-test-completeness is itself one of this
diff's value-bearing families — its argv takes $RUNNER_TEMP from the workflow,
so it is outside --commands and has no local run. Same for
check-shard-attestation.mjs --emit ... and the --union-into spelling of
check-cross-package-test-inputs (the bare pnpm check:cross-package-test-inputs
spelling of that family IS in the table above and ran green).

Nothing is published from any package (scripts/pm/**, no .md), so
skip-changeset is applied.


Generated by Claude Code

…lue-bearing class
`runReconciliation` classified a recorded command that is not in the derived
runnable set into three outcomes, two of which are "explained, not a mistake":
a CI-measured-only family and a pending-changeset family. The value-bearing
class added for #15083 — an invocation whose argv takes a value from the
workflow, which `commandsFor` subtracts from the union for the same reason it
subtracts the CI-measured one — had no such bucket, so a dev who recorded one
landed in `extra` under a caption that reads "named by nothing this run
derived": the one sentence that is false about it, because this run derived it
and then classified it out.
Third bucket, built from `notRunnableCommandSet` off the same rows the
CI-measured set is built from, checked beside it (the two subtractions
`commandsFor` makes, in that order) and before the `extra` fallback, and
rendered on its own labelled line naming the reason. Diagnostic only:
`recon.ok` still reads `unrun.length`, `--commands` is untouched, and no
derivation key moves.
Measured at the merge base 4283b72: a record of the 15 derived commands for a
changeset path plus the live `node scripts/check-empty-changeset.mjs --base
"$MERGE_BASE"` invocation reported
Outside this card's derivation (1) — recorded, and named by nothing this run
derived.
- node scripts/check-empty-changeset.mjs --base "$MERGE_BASE"
and the same record on this tree reports the labelled bucket instead, with the
remainder heading gone and the verdict unmoved (15 derived, 15 run, exit 0).
Self-test: seven cases, three of them a real CLI run whose fixture is the
tool's own `--json` answer on this tree — a parameter defaulting to empty is
exactly the shape that keeps unit cases green while the live mode still dumps
the class in the remainder.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019RfFHiRCSs3JXLK4cwcfox
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/mskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants

@os-steve@claude
, '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

tooling(pm): give dispatch-gates --ran an explained bucket for the value-bearing class - #15165

Merged
os-steve merged 1 commit into
mainfrom
claude/issue-15115-ran-value-bearing-bucket
Sep 4, 2026
Merged

tooling(pm): give dispatch-gates --ran an explained bucket for the value-bearing class#15165
os-steve merged 1 commit into
mainfrom
claude/issue-15115-ran-value-bearing-bucket

Conversation

@claude

@claudeclaudeBot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Fixes#15115

runReconciliation in scripts/pm/dispatch-gates.mjs classifies a command the
runner RECORDED that is not in the derived runnable set. It had three outcomes,
two of which are "explained, not a mistake": a CI-measured-only family, and a
pending-changeset family. The value-bearing class — an invocation whose argv
takes a value from the workflow, added by PR #15114 and subtracted from
--commands by the second subtraction in commandsFor — had no such bucket, so
a recorded one landed in extra.

Reproduced at the merge base, before any edit

Merge base 4283b72a. Record = the 15 commands --commands derives for a
changeset path, plus the live pr-automation.yml invocation
node scripts/check-empty-changeset.mjs --base "$MERGE_BASE":

$ node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack \
--ran repro-ran.list .changeset/foo.md
Run reconciliation — 15 derived, 15 run, 0 NOT-MEASURED, 0 UNRUN.
Outside this card's derivation (1) — recorded, and named by nothing this run derived. Not an error: a run beyond the union costs nothing.
- node scripts/check-empty-changeset.mjs --base "$MERGE_BASE"
✓ dispatch-gates --ran: 15 derived famil(ies) accounted for — 15 run, 0 NOT-MEASURED.

That caption is the defect in one sentence: this run DID derive that family and
then classified it out of the union on purpose, so "named by nothing this run
derived" is the one thing that is false about it. No reason is attached, and the
runner is left to guess whether they typed something wrong.

The bucket

  • runReconciliation takes notRunnableCommands, defaulting to an empty set so
    every existing caller's verdict is byte-identical.
  • It is checked BESIDE ciOnlyCommands and before pendingCommands, because
    the file already groups those two: they are the two subtractions commandsFor
    makes from the runnable union, in that order, while the pending families are a
    different fact (a path that did not exist at derivation time). ciOnly keeps
    first precedence, matching commandsFor, so one command can never produce two
    counts for one omission.
  • The set is built at the --ran call site by notRunnableCommandSet(matchedRows, alwaysRunsRows) — the SAME expression commandsFor subtracts with, off the
    same rows — so the union and the reconciliation cannot drift about which
    invocations are withheld.
  • recon.ok still reads unrun.length only. --commands output, the
    classifier, the derivation keys and every live key are untouched.

Rendered on its own labelled line, in the shape the two beside it use. Same
record, same card, on this branch:

Run reconciliation — 15 derived, 15 run, 0 NOT-MEASURED, 0 UNRUN.
Classified by this tool, no explanation owed (1) — VALUE-BEARING famil(ies): its argv takes a value from the workflow, so it is recorded, not derived as runnable:
- node scripts/check-empty-changeset.mjs --base "$MERGE_BASE"
✓ dispatch-gates --ran: 15 derived famil(ies) accounted for — 15 run, 0 NOT-MEASURED.

The remainder heading is gone entirely and the verdict is unmoved.

Self-test — 7 new cases, 1353 total

Unit, beside the existing explained case (whose fixture now carries a fourth
recorded entry, the live value-bearing spelling):

  • a recorded value-bearing command classifies into explainedNotRunnable and is
    NOT in extra;
  • CONTROL: an unknown command recorded beside it still reads extra — the
    bucket explains its class and nothing else, which is the half that keeps this
    from deleting the remainder rather than shrinking it;
  • the two existing buckets keep their verdicts in the same run
    (explainedCiOnly 1, explainedPending 1, extra 0);
  • the bucket cannot move the verdict: ok true, unrun 0;
  • the rendering carries its own labelled line, the reason, and the command, with
    the two neighbouring lines still present and the remainder heading absent.

End to end, a real CLI run (three cases), because every unit case above stays
green if the --ran call site never PASSES the set — a parameter defaulting to
empty is exactly the shape that keeps its own tests green while the live mode
still dumps the class in the remainder. Its fixture is the tool's own --json
answer on this tree rather than a spelling typed into the test, so it cannot
outlive a change to the renderer that produces the real invocation.

Gates — all green, run at head 5fb6e3a7

Re-derived from the real change set with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands (no paths) after the commit; the
20 families match the dispatch list exactly, nothing added. Each exit code was
captured by redirecting first, never after a pipe; every line below is the
gate's own verdict line.

gateverdict line
check-ci-filter-parity.mjsOK: all 143 declared cross-package glob(s) (99 unique) are covered by core or crosspkg, every crosspkg entry still covers one, and the test job's if: still names both filters.
check-closing-keyword-parity.mjscheck-closing-keyword-parity: OK (3 parsers agree on all 9 keywords and both measured separators; sweep found 5 file(s) carrying the grammar across 8245 tracked file(s), all registered).
check-closing-keyword-parity.mjs --self-test✓ 24 assertions, 5 mutations of the shipped parsers each driven to red.
check-comment-mask-corpus.mjs✓ comment-mask corpus sweep: 5858 files, 0 disagree, 0 unparseable, 49.0s (comparator self-test: 12 cases pass).
check-self-test-wired.mjs✓ every one of the 166 script(s) CI runs that ship a --self-test has that self-test run by CI.
check-self-test-wired.mjs --self-test4 live ledger row(s) verified ... 7 declared batteries, 50 cases registered, every battery at or above its pinned floor.
check-whole-set-label-write.mjs✓ 0 violations — 258 file(s) over 3 root(s) · 160 uses: pin(s) over 18 distinct action(s) judged · 0 allowlist entr(ies)
check-whole-set-label-write.mjs --self-test✓ all cases pass (24 fixture trees + 5 refusals + 1 allowlist hatch)
pnpm check:agent-test-spelling✓ 0 violations — 438 file(s) · 5916 bare -- token(s) · 1408 launcher-rooted run(s) · 9 separator(s) JUDGED
pnpm check:bash32-floor✓ self-test: 153 cases pass. / ✓ 26 tracked shell file(s) ... name no bash 4+ construct
pnpm check:cli-command-ids✓ 324 command-id literal(s) across 114 file(s) outside packages/cli all resolve to a real command path
pnpm check:cross-package-test-inputsOK: 26 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob.
pnpm check:declared-population-live✓ 198 of 249 famil(ies) declare a path population, and every one of them reaches this tree's 8245 tracked file(s).
pnpm check:entry-guard✓ 207 scripts/ file(s) — every entry guard goes through invoked-as.mjs; 153 export bindings, 153 of them inert on import
pnpm check:nul-bytescheck-nul-bytes: OK (scanned 8238 text file(s) ... no raw ASCII control bytes).
pnpm check:parse-guard✓ 206 scripts/ file(s) — every TypeScript parse goes through ts-parse.mjs.
pnpm check:pm-dispatch-gates✓ dispatch-gates self-test: 1353 cases pass.
pnpm check:pnpm-filter-targets✓ 142/181 --filter occurrence(s) across 33 file(s) resolve against 79 workspace package(s)
pnpm check:refd-timer-probeOK 5853 source file(s) swept; the process-global timer probe is read in packages/qa/refd-timer-testkit/src/index.ts and nowhere else.
pnpm check:watch-hint-literal✓ 48 declaration(s) across 4 rostered name(s) ... no unrostered spelling of the idiom in the tree.
bare-root-worklist.mjs --self-test (not in the derivation; asserted green because a parallel flight edits that file)OK self-test: 54 live row(s), 46 unreachable as spelled, 46 recorded verdict(s) — none stale, none missing, none contradicted.

Repo-wide ESLint, not narrowed: pnpm lint (eslint . --no-inline-config) through
the shared verify lock — os-verify-lock: VERDICT command-exit 0 · held the lock 66s.
The dispatch-gates --self-test also ran through the lock: VERDICT command-exit 0 · held the lock 354s. Both wall-clock figures are shared-box seconds, as the lock's
own verdict line states.

NOT MEASURED, in its own words: check-test-completeness is itself one of this
diff's value-bearing families — its argv takes $RUNNER_TEMP from the workflow,
so it is outside --commands and has no local run. Same for
check-shard-attestation.mjs --emit ... and the --union-into spelling of
check-cross-package-test-inputs (the bare pnpm check:cross-package-test-inputs
spelling of that family IS in the table above and ran green).

Nothing is published from any package (scripts/pm/**, no .md), so
skip-changeset is applied.


Generated by Claude Code

…lue-bearing class
`runReconciliation` classified a recorded command that is not in the derived
runnable set into three outcomes, two of which are "explained, not a mistake":
a CI-measured-only family and a pending-changeset family. The value-bearing
class added for #15083 — an invocation whose argv takes a value from the
workflow, which `commandsFor` subtracts from the union for the same reason it
subtracts the CI-measured one — had no such bucket, so a dev who recorded one
landed in `extra` under a caption that reads "named by nothing this run
derived": the one sentence that is false about it, because this run derived it
and then classified it out.
Third bucket, built from `notRunnableCommandSet` off the same rows the
CI-measured set is built from, checked beside it (the two subtractions
`commandsFor` makes, in that order) and before the `extra` fallback, and
rendered on its own labelled line naming the reason. Diagnostic only:
`recon.ok` still reads `unrun.length`, `--commands` is untouched, and no
derivation key moves.
Measured at the merge base 4283b72: a record of the 15 derived commands for a
changeset path plus the live `node scripts/check-empty-changeset.mjs --base
"$MERGE_BASE"` invocation reported
Outside this card's derivation (1) — recorded, and named by nothing this run
derived.
- node scripts/check-empty-changeset.mjs --base "$MERGE_BASE"
and the same record on this tree reports the labelled bucket instead, with the
remainder heading gone and the verdict unmoved (15 derived, 15 run, exit 0).
Self-test: seven cases, three of them a real CLI run whose fixture is the
tool's own `--json` answer on this tree — a parameter defaulting to empty is
exactly the shape that keeps unit cases green while the live mode still dumps
the class in the remainder.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019RfFHiRCSs3JXLK4cwcfox
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/mskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants

@os-steve@claude
, '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

tooling(pm): give dispatch-gates --ran an explained bucket for the value-bearing class - #15165

Merged
os-steve merged 1 commit into
mainfrom
claude/issue-15115-ran-value-bearing-bucket
Sep 4, 2026
Merged

tooling(pm): give dispatch-gates --ran an explained bucket for the value-bearing class#15165
os-steve merged 1 commit into
mainfrom
claude/issue-15115-ran-value-bearing-bucket

Conversation

@claude

@claudeclaudeBot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Fixes#15115

runReconciliation in scripts/pm/dispatch-gates.mjs classifies a command the
runner RECORDED that is not in the derived runnable set. It had three outcomes,
two of which are "explained, not a mistake": a CI-measured-only family, and a
pending-changeset family. The value-bearing class — an invocation whose argv
takes a value from the workflow, added by PR #15114 and subtracted from
--commands by the second subtraction in commandsFor — had no such bucket, so
a recorded one landed in extra.

Reproduced at the merge base, before any edit

Merge base 4283b72a. Record = the 15 commands --commands derives for a
changeset path, plus the live pr-automation.yml invocation
node scripts/check-empty-changeset.mjs --base "$MERGE_BASE":

$ node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack \
--ran repro-ran.list .changeset/foo.md
Run reconciliation — 15 derived, 15 run, 0 NOT-MEASURED, 0 UNRUN.
Outside this card's derivation (1) — recorded, and named by nothing this run derived. Not an error: a run beyond the union costs nothing.
- node scripts/check-empty-changeset.mjs --base "$MERGE_BASE"
✓ dispatch-gates --ran: 15 derived famil(ies) accounted for — 15 run, 0 NOT-MEASURED.

That caption is the defect in one sentence: this run DID derive that family and
then classified it out of the union on purpose, so "named by nothing this run
derived" is the one thing that is false about it. No reason is attached, and the
runner is left to guess whether they typed something wrong.

The bucket

  • runReconciliation takes notRunnableCommands, defaulting to an empty set so
    every existing caller's verdict is byte-identical.
  • It is checked BESIDE ciOnlyCommands and before pendingCommands, because
    the file already groups those two: they are the two subtractions commandsFor
    makes from the runnable union, in that order, while the pending families are a
    different fact (a path that did not exist at derivation time). ciOnly keeps
    first precedence, matching commandsFor, so one command can never produce two
    counts for one omission.
  • The set is built at the --ran call site by notRunnableCommandSet(matchedRows, alwaysRunsRows) — the SAME expression commandsFor subtracts with, off the
    same rows — so the union and the reconciliation cannot drift about which
    invocations are withheld.
  • recon.ok still reads unrun.length only. --commands output, the
    classifier, the derivation keys and every live key are untouched.

Rendered on its own labelled line, in the shape the two beside it use. Same
record, same card, on this branch:

Run reconciliation — 15 derived, 15 run, 0 NOT-MEASURED, 0 UNRUN.
Classified by this tool, no explanation owed (1) — VALUE-BEARING famil(ies): its argv takes a value from the workflow, so it is recorded, not derived as runnable:
- node scripts/check-empty-changeset.mjs --base "$MERGE_BASE"
✓ dispatch-gates --ran: 15 derived famil(ies) accounted for — 15 run, 0 NOT-MEASURED.

The remainder heading is gone entirely and the verdict is unmoved.

Self-test — 7 new cases, 1353 total

Unit, beside the existing explained case (whose fixture now carries a fourth
recorded entry, the live value-bearing spelling):

  • a recorded value-bearing command classifies into explainedNotRunnable and is
    NOT in extra;
  • CONTROL: an unknown command recorded beside it still reads extra — the
    bucket explains its class and nothing else, which is the half that keeps this
    from deleting the remainder rather than shrinking it;
  • the two existing buckets keep their verdicts in the same run
    (explainedCiOnly 1, explainedPending 1, extra 0);
  • the bucket cannot move the verdict: ok true, unrun 0;
  • the rendering carries its own labelled line, the reason, and the command, with
    the two neighbouring lines still present and the remainder heading absent.

End to end, a real CLI run (three cases), because every unit case above stays
green if the --ran call site never PASSES the set — a parameter defaulting to
empty is exactly the shape that keeps its own tests green while the live mode
still dumps the class in the remainder. Its fixture is the tool's own --json
answer on this tree rather than a spelling typed into the test, so it cannot
outlive a change to the renderer that produces the real invocation.

Gates — all green, run at head 5fb6e3a7

Re-derived from the real change set with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands (no paths) after the commit; the
20 families match the dispatch list exactly, nothing added. Each exit code was
captured by redirecting first, never after a pipe; every line below is the
gate's own verdict line.

gateverdict line
check-ci-filter-parity.mjsOK: all 143 declared cross-package glob(s) (99 unique) are covered by core or crosspkg, every crosspkg entry still covers one, and the test job's if: still names both filters.
check-closing-keyword-parity.mjscheck-closing-keyword-parity: OK (3 parsers agree on all 9 keywords and both measured separators; sweep found 5 file(s) carrying the grammar across 8245 tracked file(s), all registered).
check-closing-keyword-parity.mjs --self-test✓ 24 assertions, 5 mutations of the shipped parsers each driven to red.
check-comment-mask-corpus.mjs✓ comment-mask corpus sweep: 5858 files, 0 disagree, 0 unparseable, 49.0s (comparator self-test: 12 cases pass).
check-self-test-wired.mjs✓ every one of the 166 script(s) CI runs that ship a --self-test has that self-test run by CI.
check-self-test-wired.mjs --self-test4 live ledger row(s) verified ... 7 declared batteries, 50 cases registered, every battery at or above its pinned floor.
check-whole-set-label-write.mjs✓ 0 violations — 258 file(s) over 3 root(s) · 160 uses: pin(s) over 18 distinct action(s) judged · 0 allowlist entr(ies)
check-whole-set-label-write.mjs --self-test✓ all cases pass (24 fixture trees + 5 refusals + 1 allowlist hatch)
pnpm check:agent-test-spelling✓ 0 violations — 438 file(s) · 5916 bare -- token(s) · 1408 launcher-rooted run(s) · 9 separator(s) JUDGED
pnpm check:bash32-floor✓ self-test: 153 cases pass. / ✓ 26 tracked shell file(s) ... name no bash 4+ construct
pnpm check:cli-command-ids✓ 324 command-id literal(s) across 114 file(s) outside packages/cli all resolve to a real command path
pnpm check:cross-package-test-inputsOK: 26 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob.
pnpm check:declared-population-live✓ 198 of 249 famil(ies) declare a path population, and every one of them reaches this tree's 8245 tracked file(s).
pnpm check:entry-guard✓ 207 scripts/ file(s) — every entry guard goes through invoked-as.mjs; 153 export bindings, 153 of them inert on import
pnpm check:nul-bytescheck-nul-bytes: OK (scanned 8238 text file(s) ... no raw ASCII control bytes).
pnpm check:parse-guard✓ 206 scripts/ file(s) — every TypeScript parse goes through ts-parse.mjs.
pnpm check:pm-dispatch-gates✓ dispatch-gates self-test: 1353 cases pass.
pnpm check:pnpm-filter-targets✓ 142/181 --filter occurrence(s) across 33 file(s) resolve against 79 workspace package(s)
pnpm check:refd-timer-probeOK 5853 source file(s) swept; the process-global timer probe is read in packages/qa/refd-timer-testkit/src/index.ts and nowhere else.
pnpm check:watch-hint-literal✓ 48 declaration(s) across 4 rostered name(s) ... no unrostered spelling of the idiom in the tree.
bare-root-worklist.mjs --self-test (not in the derivation; asserted green because a parallel flight edits that file)OK self-test: 54 live row(s), 46 unreachable as spelled, 46 recorded verdict(s) — none stale, none missing, none contradicted.

Repo-wide ESLint, not narrowed: pnpm lint (eslint . --no-inline-config) through
the shared verify lock — os-verify-lock: VERDICT command-exit 0 · held the lock 66s.
The dispatch-gates --self-test also ran through the lock: VERDICT command-exit 0 · held the lock 354s. Both wall-clock figures are shared-box seconds, as the lock's
own verdict line states.

NOT MEASURED, in its own words: check-test-completeness is itself one of this
diff's value-bearing families — its argv takes $RUNNER_TEMP from the workflow,
so it is outside --commands and has no local run. Same for
check-shard-attestation.mjs --emit ... and the --union-into spelling of
check-cross-package-test-inputs (the bare pnpm check:cross-package-test-inputs
spelling of that family IS in the table above and ran green).

Nothing is published from any package (scripts/pm/**, no .md), so
skip-changeset is applied.


Generated by Claude Code

…lue-bearing class
`runReconciliation` classified a recorded command that is not in the derived
runnable set into three outcomes, two of which are "explained, not a mistake":
a CI-measured-only family and a pending-changeset family. The value-bearing
class added for #15083 — an invocation whose argv takes a value from the
workflow, which `commandsFor` subtracts from the union for the same reason it
subtracts the CI-measured one — had no such bucket, so a dev who recorded one
landed in `extra` under a caption that reads "named by nothing this run
derived": the one sentence that is false about it, because this run derived it
and then classified it out.
Third bucket, built from `notRunnableCommandSet` off the same rows the
CI-measured set is built from, checked beside it (the two subtractions
`commandsFor` makes, in that order) and before the `extra` fallback, and
rendered on its own labelled line naming the reason. Diagnostic only:
`recon.ok` still reads `unrun.length`, `--commands` is untouched, and no
derivation key moves.
Measured at the merge base 4283b72: a record of the 15 derived commands for a
changeset path plus the live `node scripts/check-empty-changeset.mjs --base
"$MERGE_BASE"` invocation reported
Outside this card's derivation (1) — recorded, and named by nothing this run
derived.
- node scripts/check-empty-changeset.mjs --base "$MERGE_BASE"
and the same record on this tree reports the labelled bucket instead, with the
remainder heading gone and the verdict unmoved (15 derived, 15 run, exit 0).
Self-test: seven cases, three of them a real CLI run whose fixture is the
tool's own `--json` answer on this tree — a parameter defaulting to empty is
exactly the shape that keeps unit cases green while the live mode still dumps
the class in the remainder.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019RfFHiRCSs3JXLK4cwcfox
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/mskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants

@os-steve@claude
, '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

tooling(pm): give dispatch-gates --ran an explained bucket for the value-bearing class - #15165

Merged
os-steve merged 1 commit into
mainfrom
claude/issue-15115-ran-value-bearing-bucket
Sep 4, 2026
Merged

tooling(pm): give dispatch-gates --ran an explained bucket for the value-bearing class#15165
os-steve merged 1 commit into
mainfrom
claude/issue-15115-ran-value-bearing-bucket

Conversation

@claude

@claudeclaudeBot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Fixes#15115

runReconciliation in scripts/pm/dispatch-gates.mjs classifies a command the
runner RECORDED that is not in the derived runnable set. It had three outcomes,
two of which are "explained, not a mistake": a CI-measured-only family, and a
pending-changeset family. The value-bearing class — an invocation whose argv
takes a value from the workflow, added by PR #15114 and subtracted from
--commands by the second subtraction in commandsFor — had no such bucket, so
a recorded one landed in extra.

Reproduced at the merge base, before any edit

Merge base 4283b72a. Record = the 15 commands --commands derives for a
changeset path, plus the live pr-automation.yml invocation
node scripts/check-empty-changeset.mjs --base "$MERGE_BASE":

$ node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack \
--ran repro-ran.list .changeset/foo.md
Run reconciliation — 15 derived, 15 run, 0 NOT-MEASURED, 0 UNRUN.
Outside this card's derivation (1) — recorded, and named by nothing this run derived. Not an error: a run beyond the union costs nothing.
- node scripts/check-empty-changeset.mjs --base "$MERGE_BASE"
✓ dispatch-gates --ran: 15 derived famil(ies) accounted for — 15 run, 0 NOT-MEASURED.

That caption is the defect in one sentence: this run DID derive that family and
then classified it out of the union on purpose, so "named by nothing this run
derived" is the one thing that is false about it. No reason is attached, and the
runner is left to guess whether they typed something wrong.

The bucket

  • runReconciliation takes notRunnableCommands, defaulting to an empty set so
    every existing caller's verdict is byte-identical.
  • It is checked BESIDE ciOnlyCommands and before pendingCommands, because
    the file already groups those two: they are the two subtractions commandsFor
    makes from the runnable union, in that order, while the pending families are a
    different fact (a path that did not exist at derivation time). ciOnly keeps
    first precedence, matching commandsFor, so one command can never produce two
    counts for one omission.
  • The set is built at the --ran call site by notRunnableCommandSet(matchedRows, alwaysRunsRows) — the SAME expression commandsFor subtracts with, off the
    same rows — so the union and the reconciliation cannot drift about which
    invocations are withheld.
  • recon.ok still reads unrun.length only. --commands output, the
    classifier, the derivation keys and every live key are untouched.

Rendered on its own labelled line, in the shape the two beside it use. Same
record, same card, on this branch:

Run reconciliation — 15 derived, 15 run, 0 NOT-MEASURED, 0 UNRUN.
Classified by this tool, no explanation owed (1) — VALUE-BEARING famil(ies): its argv takes a value from the workflow, so it is recorded, not derived as runnable:
- node scripts/check-empty-changeset.mjs --base "$MERGE_BASE"
✓ dispatch-gates --ran: 15 derived famil(ies) accounted for — 15 run, 0 NOT-MEASURED.

The remainder heading is gone entirely and the verdict is unmoved.

Self-test — 7 new cases, 1353 total

Unit, beside the existing explained case (whose fixture now carries a fourth
recorded entry, the live value-bearing spelling):

  • a recorded value-bearing command classifies into explainedNotRunnable and is
    NOT in extra;
  • CONTROL: an unknown command recorded beside it still reads extra — the
    bucket explains its class and nothing else, which is the half that keeps this
    from deleting the remainder rather than shrinking it;
  • the two existing buckets keep their verdicts in the same run
    (explainedCiOnly 1, explainedPending 1, extra 0);
  • the bucket cannot move the verdict: ok true, unrun 0;
  • the rendering carries its own labelled line, the reason, and the command, with
    the two neighbouring lines still present and the remainder heading absent.

End to end, a real CLI run (three cases), because every unit case above stays
green if the --ran call site never PASSES the set — a parameter defaulting to
empty is exactly the shape that keeps its own tests green while the live mode
still dumps the class in the remainder. Its fixture is the tool's own --json
answer on this tree rather than a spelling typed into the test, so it cannot
outlive a change to the renderer that produces the real invocation.

Gates — all green, run at head 5fb6e3a7

Re-derived from the real change set with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands (no paths) after the commit; the
20 families match the dispatch list exactly, nothing added. Each exit code was
captured by redirecting first, never after a pipe; every line below is the
gate's own verdict line.

gateverdict line
check-ci-filter-parity.mjsOK: all 143 declared cross-package glob(s) (99 unique) are covered by core or crosspkg, every crosspkg entry still covers one, and the test job's if: still names both filters.
check-closing-keyword-parity.mjscheck-closing-keyword-parity: OK (3 parsers agree on all 9 keywords and both measured separators; sweep found 5 file(s) carrying the grammar across 8245 tracked file(s), all registered).
check-closing-keyword-parity.mjs --self-test✓ 24 assertions, 5 mutations of the shipped parsers each driven to red.
check-comment-mask-corpus.mjs✓ comment-mask corpus sweep: 5858 files, 0 disagree, 0 unparseable, 49.0s (comparator self-test: 12 cases pass).
check-self-test-wired.mjs✓ every one of the 166 script(s) CI runs that ship a --self-test has that self-test run by CI.
check-self-test-wired.mjs --self-test4 live ledger row(s) verified ... 7 declared batteries, 50 cases registered, every battery at or above its pinned floor.
check-whole-set-label-write.mjs✓ 0 violations — 258 file(s) over 3 root(s) · 160 uses: pin(s) over 18 distinct action(s) judged · 0 allowlist entr(ies)
check-whole-set-label-write.mjs --self-test✓ all cases pass (24 fixture trees + 5 refusals + 1 allowlist hatch)
pnpm check:agent-test-spelling✓ 0 violations — 438 file(s) · 5916 bare -- token(s) · 1408 launcher-rooted run(s) · 9 separator(s) JUDGED
pnpm check:bash32-floor✓ self-test: 153 cases pass. / ✓ 26 tracked shell file(s) ... name no bash 4+ construct
pnpm check:cli-command-ids✓ 324 command-id literal(s) across 114 file(s) outside packages/cli all resolve to a real command path
pnpm check:cross-package-test-inputsOK: 26 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob.
pnpm check:declared-population-live✓ 198 of 249 famil(ies) declare a path population, and every one of them reaches this tree's 8245 tracked file(s).
pnpm check:entry-guard✓ 207 scripts/ file(s) — every entry guard goes through invoked-as.mjs; 153 export bindings, 153 of them inert on import
pnpm check:nul-bytescheck-nul-bytes: OK (scanned 8238 text file(s) ... no raw ASCII control bytes).
pnpm check:parse-guard✓ 206 scripts/ file(s) — every TypeScript parse goes through ts-parse.mjs.
pnpm check:pm-dispatch-gates✓ dispatch-gates self-test: 1353 cases pass.
pnpm check:pnpm-filter-targets✓ 142/181 --filter occurrence(s) across 33 file(s) resolve against 79 workspace package(s)
pnpm check:refd-timer-probeOK 5853 source file(s) swept; the process-global timer probe is read in packages/qa/refd-timer-testkit/src/index.ts and nowhere else.
pnpm check:watch-hint-literal✓ 48 declaration(s) across 4 rostered name(s) ... no unrostered spelling of the idiom in the tree.
bare-root-worklist.mjs --self-test (not in the derivation; asserted green because a parallel flight edits that file)OK self-test: 54 live row(s), 46 unreachable as spelled, 46 recorded verdict(s) — none stale, none missing, none contradicted.

Repo-wide ESLint, not narrowed: pnpm lint (eslint . --no-inline-config) through
the shared verify lock — os-verify-lock: VERDICT command-exit 0 · held the lock 66s.
The dispatch-gates --self-test also ran through the lock: VERDICT command-exit 0 · held the lock 354s. Both wall-clock figures are shared-box seconds, as the lock's
own verdict line states.

NOT MEASURED, in its own words: check-test-completeness is itself one of this
diff's value-bearing families — its argv takes $RUNNER_TEMP from the workflow,
so it is outside --commands and has no local run. Same for
check-shard-attestation.mjs --emit ... and the --union-into spelling of
check-cross-package-test-inputs (the bare pnpm check:cross-package-test-inputs
spelling of that family IS in the table above and ran green).

Nothing is published from any package (scripts/pm/**, no .md), so
skip-changeset is applied.


Generated by Claude Code

…lue-bearing class
`runReconciliation` classified a recorded command that is not in the derived
runnable set into three outcomes, two of which are "explained, not a mistake":
a CI-measured-only family and a pending-changeset family. The value-bearing
class added for #15083 — an invocation whose argv takes a value from the
workflow, which `commandsFor` subtracts from the union for the same reason it
subtracts the CI-measured one — had no such bucket, so a dev who recorded one
landed in `extra` under a caption that reads "named by nothing this run
derived": the one sentence that is false about it, because this run derived it
and then classified it out.
Third bucket, built from `notRunnableCommandSet` off the same rows the
CI-measured set is built from, checked beside it (the two subtractions
`commandsFor` makes, in that order) and before the `extra` fallback, and
rendered on its own labelled line naming the reason. Diagnostic only:
`recon.ok` still reads `unrun.length`, `--commands` is untouched, and no
derivation key moves.
Measured at the merge base 4283b72: a record of the 15 derived commands for a
changeset path plus the live `node scripts/check-empty-changeset.mjs --base
"$MERGE_BASE"` invocation reported
Outside this card's derivation (1) — recorded, and named by nothing this run
derived.
- node scripts/check-empty-changeset.mjs --base "$MERGE_BASE"
and the same record on this tree reports the labelled bucket instead, with the
remainder heading gone and the verdict unmoved (15 derived, 15 run, exit 0).
Self-test: seven cases, three of them a real CLI run whose fixture is the
tool's own `--json` answer on this tree — a parameter defaulting to empty is
exactly the shape that keeps unit cases green while the live mode still dumps
the class in the remainder.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019RfFHiRCSs3JXLK4cwcfox
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/mskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants

@os-steve@claude
, '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

tooling(pm): give dispatch-gates --ran an explained bucket for the value-bearing class - #15165

Merged
os-steve merged 1 commit into
mainfrom
claude/issue-15115-ran-value-bearing-bucket
Sep 4, 2026
Merged

tooling(pm): give dispatch-gates --ran an explained bucket for the value-bearing class#15165
os-steve merged 1 commit into
mainfrom
claude/issue-15115-ran-value-bearing-bucket

Conversation

@claude

@claudeclaudeBot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Fixes#15115

runReconciliation in scripts/pm/dispatch-gates.mjs classifies a command the
runner RECORDED that is not in the derived runnable set. It had three outcomes,
two of which are "explained, not a mistake": a CI-measured-only family, and a
pending-changeset family. The value-bearing class — an invocation whose argv
takes a value from the workflow, added by PR #15114 and subtracted from
--commands by the second subtraction in commandsFor — had no such bucket, so
a recorded one landed in extra.

Reproduced at the merge base, before any edit

Merge base 4283b72a. Record = the 15 commands --commands derives for a
changeset path, plus the live pr-automation.yml invocation
node scripts/check-empty-changeset.mjs --base "$MERGE_BASE":

$ node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack \
--ran repro-ran.list .changeset/foo.md
Run reconciliation — 15 derived, 15 run, 0 NOT-MEASURED, 0 UNRUN.
Outside this card's derivation (1) — recorded, and named by nothing this run derived. Not an error: a run beyond the union costs nothing.
- node scripts/check-empty-changeset.mjs --base "$MERGE_BASE"
✓ dispatch-gates --ran: 15 derived famil(ies) accounted for — 15 run, 0 NOT-MEASURED.

That caption is the defect in one sentence: this run DID derive that family and
then classified it out of the union on purpose, so "named by nothing this run
derived" is the one thing that is false about it. No reason is attached, and the
runner is left to guess whether they typed something wrong.

The bucket

  • runReconciliation takes notRunnableCommands, defaulting to an empty set so
    every existing caller's verdict is byte-identical.
  • It is checked BESIDE ciOnlyCommands and before pendingCommands, because
    the file already groups those two: they are the two subtractions commandsFor
    makes from the runnable union, in that order, while the pending families are a
    different fact (a path that did not exist at derivation time). ciOnly keeps
    first precedence, matching commandsFor, so one command can never produce two
    counts for one omission.
  • The set is built at the --ran call site by notRunnableCommandSet(matchedRows, alwaysRunsRows) — the SAME expression commandsFor subtracts with, off the
    same rows — so the union and the reconciliation cannot drift about which
    invocations are withheld.
  • recon.ok still reads unrun.length only. --commands output, the
    classifier, the derivation keys and every live key are untouched.

Rendered on its own labelled line, in the shape the two beside it use. Same
record, same card, on this branch:

Run reconciliation — 15 derived, 15 run, 0 NOT-MEASURED, 0 UNRUN.
Classified by this tool, no explanation owed (1) — VALUE-BEARING famil(ies): its argv takes a value from the workflow, so it is recorded, not derived as runnable:
- node scripts/check-empty-changeset.mjs --base "$MERGE_BASE"
✓ dispatch-gates --ran: 15 derived famil(ies) accounted for — 15 run, 0 NOT-MEASURED.

The remainder heading is gone entirely and the verdict is unmoved.

Self-test — 7 new cases, 1353 total

Unit, beside the existing explained case (whose fixture now carries a fourth
recorded entry, the live value-bearing spelling):

  • a recorded value-bearing command classifies into explainedNotRunnable and is
    NOT in extra;
  • CONTROL: an unknown command recorded beside it still reads extra — the
    bucket explains its class and nothing else, which is the half that keeps this
    from deleting the remainder rather than shrinking it;
  • the two existing buckets keep their verdicts in the same run
    (explainedCiOnly 1, explainedPending 1, extra 0);
  • the bucket cannot move the verdict: ok true, unrun 0;
  • the rendering carries its own labelled line, the reason, and the command, with
    the two neighbouring lines still present and the remainder heading absent.

End to end, a real CLI run (three cases), because every unit case above stays
green if the --ran call site never PASSES the set — a parameter defaulting to
empty is exactly the shape that keeps its own tests green while the live mode
still dumps the class in the remainder. Its fixture is the tool's own --json
answer on this tree rather than a spelling typed into the test, so it cannot
outlive a change to the renderer that produces the real invocation.

Gates — all green, run at head 5fb6e3a7

Re-derived from the real change set with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands (no paths) after the commit; the
20 families match the dispatch list exactly, nothing added. Each exit code was
captured by redirecting first, never after a pipe; every line below is the
gate's own verdict line.

gateverdict line
check-ci-filter-parity.mjsOK: all 143 declared cross-package glob(s) (99 unique) are covered by core or crosspkg, every crosspkg entry still covers one, and the test job's if: still names both filters.
check-closing-keyword-parity.mjscheck-closing-keyword-parity: OK (3 parsers agree on all 9 keywords and both measured separators; sweep found 5 file(s) carrying the grammar across 8245 tracked file(s), all registered).
check-closing-keyword-parity.mjs --self-test✓ 24 assertions, 5 mutations of the shipped parsers each driven to red.
check-comment-mask-corpus.mjs✓ comment-mask corpus sweep: 5858 files, 0 disagree, 0 unparseable, 49.0s (comparator self-test: 12 cases pass).
check-self-test-wired.mjs✓ every one of the 166 script(s) CI runs that ship a --self-test has that self-test run by CI.
check-self-test-wired.mjs --self-test4 live ledger row(s) verified ... 7 declared batteries, 50 cases registered, every battery at or above its pinned floor.
check-whole-set-label-write.mjs✓ 0 violations — 258 file(s) over 3 root(s) · 160 uses: pin(s) over 18 distinct action(s) judged · 0 allowlist entr(ies)
check-whole-set-label-write.mjs --self-test✓ all cases pass (24 fixture trees + 5 refusals + 1 allowlist hatch)
pnpm check:agent-test-spelling✓ 0 violations — 438 file(s) · 5916 bare -- token(s) · 1408 launcher-rooted run(s) · 9 separator(s) JUDGED
pnpm check:bash32-floor✓ self-test: 153 cases pass. / ✓ 26 tracked shell file(s) ... name no bash 4+ construct
pnpm check:cli-command-ids✓ 324 command-id literal(s) across 114 file(s) outside packages/cli all resolve to a real command path
pnpm check:cross-package-test-inputsOK: 26 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob.
pnpm check:declared-population-live✓ 198 of 249 famil(ies) declare a path population, and every one of them reaches this tree's 8245 tracked file(s).
pnpm check:entry-guard✓ 207 scripts/ file(s) — every entry guard goes through invoked-as.mjs; 153 export bindings, 153 of them inert on import
pnpm check:nul-bytescheck-nul-bytes: OK (scanned 8238 text file(s) ... no raw ASCII control bytes).
pnpm check:parse-guard✓ 206 scripts/ file(s) — every TypeScript parse goes through ts-parse.mjs.
pnpm check:pm-dispatch-gates✓ dispatch-gates self-test: 1353 cases pass.
pnpm check:pnpm-filter-targets✓ 142/181 --filter occurrence(s) across 33 file(s) resolve against 79 workspace package(s)
pnpm check:refd-timer-probeOK 5853 source file(s) swept; the process-global timer probe is read in packages/qa/refd-timer-testkit/src/index.ts and nowhere else.
pnpm check:watch-hint-literal✓ 48 declaration(s) across 4 rostered name(s) ... no unrostered spelling of the idiom in the tree.
bare-root-worklist.mjs --self-test (not in the derivation; asserted green because a parallel flight edits that file)OK self-test: 54 live row(s), 46 unreachable as spelled, 46 recorded verdict(s) — none stale, none missing, none contradicted.

Repo-wide ESLint, not narrowed: pnpm lint (eslint . --no-inline-config) through
the shared verify lock — os-verify-lock: VERDICT command-exit 0 · held the lock 66s.
The dispatch-gates --self-test also ran through the lock: VERDICT command-exit 0 · held the lock 354s. Both wall-clock figures are shared-box seconds, as the lock's
own verdict line states.

NOT MEASURED, in its own words: check-test-completeness is itself one of this
diff's value-bearing families — its argv takes $RUNNER_TEMP from the workflow,
so it is outside --commands and has no local run. Same for
check-shard-attestation.mjs --emit ... and the --union-into spelling of
check-cross-package-test-inputs (the bare pnpm check:cross-package-test-inputs
spelling of that family IS in the table above and ran green).

Nothing is published from any package (scripts/pm/**, no .md), so
skip-changeset is applied.


Generated by Claude Code

…lue-bearing class
`runReconciliation` classified a recorded command that is not in the derived
runnable set into three outcomes, two of which are "explained, not a mistake":
a CI-measured-only family and a pending-changeset family. The value-bearing
class added for #15083 — an invocation whose argv takes a value from the
workflow, which `commandsFor` subtracts from the union for the same reason it
subtracts the CI-measured one — had no such bucket, so a dev who recorded one
landed in `extra` under a caption that reads "named by nothing this run
derived": the one sentence that is false about it, because this run derived it
and then classified it out.
Third bucket, built from `notRunnableCommandSet` off the same rows the
CI-measured set is built from, checked beside it (the two subtractions
`commandsFor` makes, in that order) and before the `extra` fallback, and
rendered on its own labelled line naming the reason. Diagnostic only:
`recon.ok` still reads `unrun.length`, `--commands` is untouched, and no
derivation key moves.
Measured at the merge base 4283b72: a record of the 15 derived commands for a
changeset path plus the live `node scripts/check-empty-changeset.mjs --base
"$MERGE_BASE"` invocation reported
Outside this card's derivation (1) — recorded, and named by nothing this run
derived.
- node scripts/check-empty-changeset.mjs --base "$MERGE_BASE"
and the same record on this tree reports the labelled bucket instead, with the
remainder heading gone and the verdict unmoved (15 derived, 15 run, exit 0).
Self-test: seven cases, three of them a real CLI run whose fixture is the
tool's own `--json` answer on this tree — a parameter defaulting to empty is
exactly the shape that keeps unit cases green while the live mode still dumps
the class in the remainder.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019RfFHiRCSs3JXLK4cwcfox
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/mskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants

@os-steve@claude
, '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

tooling(pm): give dispatch-gates --ran an explained bucket for the value-bearing class - #15165

Merged
os-steve merged 1 commit into
mainfrom
claude/issue-15115-ran-value-bearing-bucket
Sep 4, 2026
Merged

tooling(pm): give dispatch-gates --ran an explained bucket for the value-bearing class#15165
os-steve merged 1 commit into
mainfrom
claude/issue-15115-ran-value-bearing-bucket

Conversation

@claude

@claudeclaudeBot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Fixes#15115

runReconciliation in scripts/pm/dispatch-gates.mjs classifies a command the
runner RECORDED that is not in the derived runnable set. It had three outcomes,
two of which are "explained, not a mistake": a CI-measured-only family, and a
pending-changeset family. The value-bearing class — an invocation whose argv
takes a value from the workflow, added by PR #15114 and subtracted from
--commands by the second subtraction in commandsFor — had no such bucket, so
a recorded one landed in extra.

Reproduced at the merge base, before any edit

Merge base 4283b72a. Record = the 15 commands --commands derives for a
changeset path, plus the live pr-automation.yml invocation
node scripts/check-empty-changeset.mjs --base "$MERGE_BASE":

$ node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack \
--ran repro-ran.list .changeset/foo.md
Run reconciliation — 15 derived, 15 run, 0 NOT-MEASURED, 0 UNRUN.
Outside this card's derivation (1) — recorded, and named by nothing this run derived. Not an error: a run beyond the union costs nothing.
- node scripts/check-empty-changeset.mjs --base "$MERGE_BASE"
✓ dispatch-gates --ran: 15 derived famil(ies) accounted for — 15 run, 0 NOT-MEASURED.

That caption is the defect in one sentence: this run DID derive that family and
then classified it out of the union on purpose, so "named by nothing this run
derived" is the one thing that is false about it. No reason is attached, and the
runner is left to guess whether they typed something wrong.

The bucket

  • runReconciliation takes notRunnableCommands, defaulting to an empty set so
    every existing caller's verdict is byte-identical.
  • It is checked BESIDE ciOnlyCommands and before pendingCommands, because
    the file already groups those two: they are the two subtractions commandsFor
    makes from the runnable union, in that order, while the pending families are a
    different fact (a path that did not exist at derivation time). ciOnly keeps
    first precedence, matching commandsFor, so one command can never produce two
    counts for one omission.
  • The set is built at the --ran call site by notRunnableCommandSet(matchedRows, alwaysRunsRows) — the SAME expression commandsFor subtracts with, off the
    same rows — so the union and the reconciliation cannot drift about which
    invocations are withheld.
  • recon.ok still reads unrun.length only. --commands output, the
    classifier, the derivation keys and every live key are untouched.

Rendered on its own labelled line, in the shape the two beside it use. Same
record, same card, on this branch:

Run reconciliation — 15 derived, 15 run, 0 NOT-MEASURED, 0 UNRUN.
Classified by this tool, no explanation owed (1) — VALUE-BEARING famil(ies): its argv takes a value from the workflow, so it is recorded, not derived as runnable:
- node scripts/check-empty-changeset.mjs --base "$MERGE_BASE"
✓ dispatch-gates --ran: 15 derived famil(ies) accounted for — 15 run, 0 NOT-MEASURED.

The remainder heading is gone entirely and the verdict is unmoved.

Self-test — 7 new cases, 1353 total

Unit, beside the existing explained case (whose fixture now carries a fourth
recorded entry, the live value-bearing spelling):

  • a recorded value-bearing command classifies into explainedNotRunnable and is
    NOT in extra;
  • CONTROL: an unknown command recorded beside it still reads extra — the
    bucket explains its class and nothing else, which is the half that keeps this
    from deleting the remainder rather than shrinking it;
  • the two existing buckets keep their verdicts in the same run
    (explainedCiOnly 1, explainedPending 1, extra 0);
  • the bucket cannot move the verdict: ok true, unrun 0;
  • the rendering carries its own labelled line, the reason, and the command, with
    the two neighbouring lines still present and the remainder heading absent.

End to end, a real CLI run (three cases), because every unit case above stays
green if the --ran call site never PASSES the set — a parameter defaulting to
empty is exactly the shape that keeps its own tests green while the live mode
still dumps the class in the remainder. Its fixture is the tool's own --json
answer on this tree rather than a spelling typed into the test, so it cannot
outlive a change to the renderer that produces the real invocation.

Gates — all green, run at head 5fb6e3a7

Re-derived from the real change set with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands (no paths) after the commit; the
20 families match the dispatch list exactly, nothing added. Each exit code was
captured by redirecting first, never after a pipe; every line below is the
gate's own verdict line.

gateverdict line
check-ci-filter-parity.mjsOK: all 143 declared cross-package glob(s) (99 unique) are covered by core or crosspkg, every crosspkg entry still covers one, and the test job's if: still names both filters.
check-closing-keyword-parity.mjscheck-closing-keyword-parity: OK (3 parsers agree on all 9 keywords and both measured separators; sweep found 5 file(s) carrying the grammar across 8245 tracked file(s), all registered).
check-closing-keyword-parity.mjs --self-test✓ 24 assertions, 5 mutations of the shipped parsers each driven to red.
check-comment-mask-corpus.mjs✓ comment-mask corpus sweep: 5858 files, 0 disagree, 0 unparseable, 49.0s (comparator self-test: 12 cases pass).
check-self-test-wired.mjs✓ every one of the 166 script(s) CI runs that ship a --self-test has that self-test run by CI.
check-self-test-wired.mjs --self-test4 live ledger row(s) verified ... 7 declared batteries, 50 cases registered, every battery at or above its pinned floor.
check-whole-set-label-write.mjs✓ 0 violations — 258 file(s) over 3 root(s) · 160 uses: pin(s) over 18 distinct action(s) judged · 0 allowlist entr(ies)
check-whole-set-label-write.mjs --self-test✓ all cases pass (24 fixture trees + 5 refusals + 1 allowlist hatch)
pnpm check:agent-test-spelling✓ 0 violations — 438 file(s) · 5916 bare -- token(s) · 1408 launcher-rooted run(s) · 9 separator(s) JUDGED
pnpm check:bash32-floor✓ self-test: 153 cases pass. / ✓ 26 tracked shell file(s) ... name no bash 4+ construct
pnpm check:cli-command-ids✓ 324 command-id literal(s) across 114 file(s) outside packages/cli all resolve to a real command path
pnpm check:cross-package-test-inputsOK: 26 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob.
pnpm check:declared-population-live✓ 198 of 249 famil(ies) declare a path population, and every one of them reaches this tree's 8245 tracked file(s).
pnpm check:entry-guard✓ 207 scripts/ file(s) — every entry guard goes through invoked-as.mjs; 153 export bindings, 153 of them inert on import
pnpm check:nul-bytescheck-nul-bytes: OK (scanned 8238 text file(s) ... no raw ASCII control bytes).
pnpm check:parse-guard✓ 206 scripts/ file(s) — every TypeScript parse goes through ts-parse.mjs.
pnpm check:pm-dispatch-gates✓ dispatch-gates self-test: 1353 cases pass.
pnpm check:pnpm-filter-targets✓ 142/181 --filter occurrence(s) across 33 file(s) resolve against 79 workspace package(s)
pnpm check:refd-timer-probeOK 5853 source file(s) swept; the process-global timer probe is read in packages/qa/refd-timer-testkit/src/index.ts and nowhere else.
pnpm check:watch-hint-literal✓ 48 declaration(s) across 4 rostered name(s) ... no unrostered spelling of the idiom in the tree.
bare-root-worklist.mjs --self-test (not in the derivation; asserted green because a parallel flight edits that file)OK self-test: 54 live row(s), 46 unreachable as spelled, 46 recorded verdict(s) — none stale, none missing, none contradicted.

Repo-wide ESLint, not narrowed: pnpm lint (eslint . --no-inline-config) through
the shared verify lock — os-verify-lock: VERDICT command-exit 0 · held the lock 66s.
The dispatch-gates --self-test also ran through the lock: VERDICT command-exit 0 · held the lock 354s. Both wall-clock figures are shared-box seconds, as the lock's
own verdict line states.

NOT MEASURED, in its own words: check-test-completeness is itself one of this
diff's value-bearing families — its argv takes $RUNNER_TEMP from the workflow,
so it is outside --commands and has no local run. Same for
check-shard-attestation.mjs --emit ... and the --union-into spelling of
check-cross-package-test-inputs (the bare pnpm check:cross-package-test-inputs
spelling of that family IS in the table above and ran green).

Nothing is published from any package (scripts/pm/**, no .md), so
skip-changeset is applied.


Generated by Claude Code

…lue-bearing class
`runReconciliation` classified a recorded command that is not in the derived
runnable set into three outcomes, two of which are "explained, not a mistake":
a CI-measured-only family and a pending-changeset family. The value-bearing
class added for #15083 — an invocation whose argv takes a value from the
workflow, which `commandsFor` subtracts from the union for the same reason it
subtracts the CI-measured one — had no such bucket, so a dev who recorded one
landed in `extra` under a caption that reads "named by nothing this run
derived": the one sentence that is false about it, because this run derived it
and then classified it out.
Third bucket, built from `notRunnableCommandSet` off the same rows the
CI-measured set is built from, checked beside it (the two subtractions
`commandsFor` makes, in that order) and before the `extra` fallback, and
rendered on its own labelled line naming the reason. Diagnostic only:
`recon.ok` still reads `unrun.length`, `--commands` is untouched, and no
derivation key moves.
Measured at the merge base 4283b72: a record of the 15 derived commands for a
changeset path plus the live `node scripts/check-empty-changeset.mjs --base
"$MERGE_BASE"` invocation reported
Outside this card's derivation (1) — recorded, and named by nothing this run
derived.
- node scripts/check-empty-changeset.mjs --base "$MERGE_BASE"
and the same record on this tree reports the labelled bucket instead, with the
remainder heading gone and the verdict unmoved (15 derived, 15 run, exit 0).
Self-test: seven cases, three of them a real CLI run whose fixture is the
tool's own `--json` answer on this tree — a parameter defaulting to empty is
exactly the shape that keeps unit cases green while the live mode still dumps
the class in the remainder.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019RfFHiRCSs3JXLK4cwcfox
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/mskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants

@os-steve@claude