fix(pm): the --ran and --commands lanes read the one OUTSIDE enumeration - #17028
Merged
Conversation
… lanes The list of blocks that sit outside a card's runnable total is one claim. #16398 built it once for the human lane, as `outsideBlockNames`. The other two output lanes kept their own short prose copies: `--ran` named three of the five blocks a plain run prints, and the `--commands` / `--json` stderr accounting named exactly one — on the very channel this file's own header tells consumers to use INSTEAD of harvesting the prose. `outsideBlockNames` / `outsideBlocksPhrase` are exported now and all three lanes read them. The third name ("the pending-changeset families") was still spelled unconditionally while `pendingChangesetLines` returns nothing at zero, so it is conditional on its own count too, from the array that renders the block — like the two beside it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HxLw5aKDPR5RJgyUR7Exkd
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HxLw5aKDPR5RJgyUR7Exkd
yinlianghui
marked this pull request as ready for review
September 9, 2026 03:22
This was referenced Sep 9, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #16795
What this changes
The list of blocks that sit OUTSIDE a card's runnable total is one claim. #16398
(PR #16802) built it once for the human lane, as
outsideBlockNamesinfamilyReconciliationLines. The other two output lanes kept short prose copies oftheir own, and both were wrong in the way the enumeration is always wrong:
--ran,runReconciliationLinesoutsideBlockNames--commands/--jsonstderr accounting,machineReadableOutputoutsideBlockNamesfamilyReconciliationLinesoutsideBlockNamesandoutsideBlocksPhraseare exported now and all three lanes callthem. Nothing routes differently and nothing derives differently:
--commandsstdout isbyte-identical before and after (
diffclean), and the only moved bytes on any lane arein the closing disclaimer.
The third name went conditional in the same pass (pointer 5580275213). The
enumeration still spelled "the pending-changeset families" unconditionally while
pendingChangesetLinesreturns nothing at zero — so on a card with no pending family thesentence pointed a reader below at a heading that is not there. It is now sized from
pending.length, the length of the very array that renders the block, like the two namesabove it.
familyReconciliationcarries the third block size (pendingChangeset) besidethe two #16398 added, on the same rule: a count read from the array that RENDERS a block
cannot disagree with it.
The card's acceptance baseline, re-run on this branch
The card's repro needs a change set, so it is run with the card's own file as an explicit
path. Before, at
ee2cb6b4:After, at
47e1d710, the tip of this branch:The line that moved:
--ran, same tree, same three counts, from the same arrays:And the third name really is conditional — a plain human run with a changeset path in the
change set drops it, because
pendingChangesetLinesprints nothing for it:Tests
28 new self-test cases, 1552 to 1580, all green. Pinned NAME BY NAME with a negative
beside every positive on both lanes, because the weak shape is what failed before: the
human lane's case used to ask only for the substring
always-runs tail, so the sentencecould name three of five and stay green for the whole time a harvester following it was
missing two.
--ran: 5 positives (each of the five names), 1 order/phrase pin, 2 negatives (namesnone of the three conditional blocks at zero counts; still names the two that print
unconditionally).
--commands: driven throughmachineReadableOutputitself withconsole.errorcaptured, not through the helper — the defect was in what that function PRINTS, and a
case against the helper would have stayed green through exactly it. 5 positives, the
card's own
grep 'WIDE'reading as a case, 2 negatives, plus a control that theenumeration never reaches stdout (stdout stays commands and nothing else).
name, a new negative that the pending block is dropped at zero, and a control that a run
with ONLY pending families names exactly that one — so the negative cannot be passing
because the name went away for good.
Ablation — the lane-2 enumeration reverted to the old hard-coded sentence
Direction predicted before the run: turn red. Run from the committed state, mutation
proved on disk before the battery, restore proved by blob hash.
One reading worth keeping: the case for
the always-runs tailstayed green under theablation, and so did the
--commandsnegative — the old hard-coded sentence names exactlythat one block and none of the three conditional ones. That is the whole argument for
pinning each name as its own assertion: a negative alone, and a positive for the one name
that never moved, cannot see this defect.
Gates
Derived on this branch from the real change set, not from a list:
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackat47e1d710prints 30 families. All 30 were run; exit code captured to a file before anypipe, and the verdict read from each gate's own line.
Reconciled by the tool's own
--ranlane, against a record built from that same--commandsoutput:Beyond the derived set:
pnpm lint(eslint . --no-inline-config) — the whole repo, run in the foreground,exit 0. No narrowing was needed, so no narrowing is claimed.
--self-test, whichcheck:pm-dispatch-gatesruns.scripts/pm/holds no*.test.ts; a repo-widegit grepfor this script under testfiles returns 9 files, and every hit is prose in a docblock — the one that names the
full path,
packages/cli/test/serve-node-env-production-default.e2e.test.ts, mentions itin a comment and does not execute it.
.changesetentry:scripts/pm/**publishes nothing from any released package.skip-changeset.A PM mechanism assumption this run falsified, and a correction to the card body
Falsified (dispatch mechanism assumption 1). The dispatch expected that
runReconciliationLines(recon)"gets the samerecon". It does not:--ranrenders theobject returned by
runReconciliation(a record-against-derived closure), which is adifferent structure from
familyReconciliation's and carries no block sizes. Handled bythe dispatch's own fallback — the enumeration is a small exported function taking counts,
and each lane hands it what it holds. The counts arrive at
--ranas a second parameter tothe RENDERER rather than as terms of
runReconciliation: a block size is not a term ofthat reconciliation's arithmetic, and adding one to an object whose closure is asserted
would put a non-term inside an invariant.
familyReconciliationdid gain the third blocksize, because that object already carries block sizes for exactly this purpose and the two
beside it were placed there by #16398.
Card body, corrected here. #16795 states of the
--commandslane that the declaredWIDE population "is never mentioned at all — not as a block, not as a count, and not in the
closing disclaimer". The middle third is measured false on
ee2cb6b4: the stderraccounting does print a count line for it,
+ 10 famil(ies) DECLARE a population too wide to place and are NOT above …. What holds isthe load-bearing part and the repro: the token
WIDEappears nowhere in the stream(
grep -creads 0, because that line spells "wide" in prose), and the closing disclaimernames exactly one block. The count line is also sized on the RUNNABLE subset
(
wideRunnable), while the block it points at renders every row including refused ones —so the new name is sized off
widePopulationRows.length, the arraywidePopulationLinesactually renders, and not off the subset this lane happens to summarise.
H17 riders — report-only, nothing built
scripts/pm/dispatch-gates.mjsis aRestart-touchon three devx-lane cards. Per thedispatch these are read and reported, not worked.
#14290 (
pm:on-hold, hold 5556473243) — its re-check re-run on this branch:The family entry is reached by its own gate script and by nothing else: it still
inherits none of the hints
scripts/bump-objectui.shspells, which is the defect #14290reports, unchanged by this diff. Two further readings taken while there, offered to the
devx seat and acted on by nobody here: the hold's first
Restart-when(
closed objectstack-ai/objectstack#16132) is now satisfied — #16132 is closed; itssecond (
git grep -l -E "(execFileSync|spawnSync)\('bash', \[join\(" -- 'scripts/**'reads2 or more) still reads 1 on
origin/main.#12797 (extensionless-target residue) — no. This diff touches neither
unreachableReasonnorhintCoversnor any part of the residue's reason selection; theonly functions edited render the OUTSIDE enumeration.
#12808 (the docblock single-source declaration in
scripts/check-cross-package-test-inputs.mjs) — no. That file is not in this diff, andnothing here changes
extractWatchHintsor the single-segment hint rule its sentence isabout.
Risk
Output text on three lanes; no routing, no derivation, no new data. The
--commandsstdout stream is byte-identical, which is the property the mode exists for. Rollback is
reverting the one commit pair on this branch.
Generated by Claude Code