fix(pm): H26 claims no SCHEDULED or DECLARED release, not "no mechanism" — counted, not asserted - #19426
Conversation
…ted, not asserted H26 asserted that a `pm:blocked` card whose `Blocked-by:` target carries `pm:on-hold` / `needs-user-decision` has "NO MECHANISM THAT WILL EVER RELEASE IT". That half was never counted, and counting refutes it: on this board 2026-09-18, 14 closed cards carry `pm:on-hold` and 5 carry `needs-user-decision`, 9 of the 19 closing as `completed` — with `pm:blocked` (20 closed) as a control that fires, so the zeros would have been visible had the closed enumerator been broken. The row is miscalibrated, not wrong, so it stays. All six sites now say what is actually true — nothing is SCHEDULED to close these targets — and carry the provenance: the counted population with its control, plus this file's own `PM_RESIDUE_LABELS` / H22, which already presuppose these labels land on closed cards. H26's useful final sentence is preserved verbatim. The self-test pins the NEW wording POSITIVELY, not merely the absence of the old: an `absent:`-only check would stay green if a later edit deleted the assertion along with the sentence it named. Claude-Session: https://claude.ai/code/session_017ef78bLdybu3AffehKkhfk Co-authored-by: Claude <noreply@anthropic.com>
One textual conflict, and it is a boundary rather than a rewrite: #19255 inserted the label-transition exit block (`UNLOCK_LABEL_EXIT_RE`, `unlockLabelExits`, `h19DeclaredExitFired`) directly above H26's banner comment, so both sides edited the same banner line. Resolved by keeping #19255's new block whole and taking this branch's banner wording. The rest of #19255's H26 work — the `exitKeys` stand-down on a target with a declared label exit, the widened `h26BlockOnIndefiniteTarget` signature and the `⭐ The exit:` remedy paragraph — merged textually with no conflict. The follow-up commit rewrites this branch's wording AROUND it, because a row that now stands down for a declared exit can no longer say no sweep in this loop can fire anything. Claude-Session: https://claude.ai/code/session_017ETYWqMQD4qMtZzAGovWNi Co-authored-by: Claude <noreply@anthropic.com>
…sm" — counted, not asserted H26 asserted that a `pm:blocked` card whose `Blocked-by:` target carries `pm:on-hold` / `needs-user-decision` has "NO MECHANISM THAT WILL EVER RELEASE IT", because those are "by definition states a card sits in WHILE OPEN". The categorical half was never counted, and counting refutes it. Re-run of the filing card's own instrument on this board, 2026-09-20T19:06Z, REST `/issues?labels=LABEL&state=STATE&per_page=100` walked to exhaustion with pull requests excluded: `pm:on-hold` 101 open / 14 closed (7 completed, 7 not_planned), `needs-user-decision` 8 / 15 (9, 6), and the control that fires, `pm:blocked`, 69 / 21 (17, 4). All three closed legs non-zero, so a silently empty `state=closed` leg would have shown as three zeros and did not. The row is miscalibrated, not wrong, so the POPULATION is untouched: every card it fired on before, it fires on now. What changed is the claim. Since #19255 a card can declare a label-transition exit the sweep fires, and H26 already stands down for a target that has one — so the row now says what it actually checked: nothing schedules these targets' close AND this card declared no exit for them. NO SCHEDULED OR DECLARED RELEASE. H26's useful final sentence is preserved verbatim, and the remedy paragraph #19255 added, which names the declarable exit, is untouched. The self-test pins the NEW wording POSITIVELY rather than only the absence of the old, and each retired phrase is pinned in the case the row really wrote it (`can never CLOSE`): a lower-cased paraphrase was already absent before this change and would have pinned nothing. Claude-Session: https://claude.ai/code/session_017ETYWqMQD4qMtZzAGovWNi Co-authored-by: Claude <noreply@anthropic.com>
…pin the retired opening in the case the row wrote it Two self-test placements, no behaviour change. The #19255 regression case — a card whose `Unlock-action:` declares a label-transition exit for the target still stands the row DOWN after the rewording — belongs with H26's other clean directions rather than in the middle of the `…and` chain that continues the firing case. Net delta against `origin/main` is +94 / −35 (net +59), inside the budget. Claude-Session: https://claude.ai/code/session_017ETYWqMQD4qMtZzAGovWNi Co-authored-by: Claude <noreply@anthropic.com>
…6-counted-not-asserted
Contract reviewServed-tier: ① Derived judgments
② Semver levelNone — nothing published; ③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
Fixes #18017
Clause-②: no
H26 told every reader that a
pm:blockedcard whoseBlocked-by:target carriespm:on-holdorneeds-user-decisionhas "NO MECHANISM THAT WILL EVER RELEASE IT", on the grounds that those are"by definition states a card sits in WHILE OPEN". The grounds were an assertion. Counting refutes it.
The row is miscalibrated, not wrong, so the population is untouched — every card it fired on before
it fires on now — and only what it CLAIMS has changed: it now claims what it actually checked.
The count, and its control
Re-run of the filing card's own instrument, on this board,
objectstack-ai/objectstack, taken2026-09-20T19:06Z. REST
/issues?labels=LABEL&state=STATE&per_page=100walked to exhaustion, thepopulation being every ISSUE carrying the label, pull requests excluded.
state_reasonpm:on-holdneeds-user-decisionpm:blocked(control)The control has a known direction and it hit: all three CLOSED legs came back non-zero, so a
state=closedleg that silently answered nothing would have read as three zeros, and it did not.⇒ cards do close out of both indefinite states, 16 of those 29 closures
completed— the holdlifted and the work finished, which is exactly the release the retired wording said could not exist.
⛔ Excluding pull requests is not tidying: 96 of the 111 rows the closed
needs-user-decisionlegreturns are PRs. A census that skipped that filter would be measuring a different population, so the
filter is stated in the rationale block beside the numbers rather than left implicit.
Two readings agree on direction. The lead commit's 2026-09-18 reading (carried on this branch, see
below) reproduces row for row on
pm:on-hold— 101 open / 14 closed, 7 completed + 7 not_planned —while
needs-user-decisionmoved 2/5 → 8/15 over the two days between the readings.Before and after
BEFORE (
origin/maina0e62e6,h26BlockOnIndefiniteTarget):AFTER:
⭐ H26's final sentence — the half the filing card calls the useful one — survives verbatim, and is
now pinned as such: "the release has to come from the target's own state changing (a ruling answered,
a hold restarted) and someone has to want that."
The same retirement is carried through every H26-owned site in this file, including the one a
line-oriented grep cannot see: the resolution-map rationale in
sweepInto, whose "can never / close"is split across two lines. Sites moved: the header docblock's H26 row, the region banner and rationale,
the
INDEFINITE_TARGET_LABELSjsdoc, the emitted string, thesweepIntonote, and the self-test.The pin this card flips, on purpose
origin/mainscripts/pm/check-half-states.mjs:30799pinned the retired sentence POSITIVELY:That case is replaced, not deleted, and three more that named the same phrase (
bothLegs,bothOnOne, the chain-leg negative) move to the new one. What stands in its place is a POSITIVE pin onthe replacement plus a retirement pin per retired phrase — because an absence-only check would stay
green if a later edit deleted the assertion along with the sentence it named, which is the exact
failure mode this card exists to describe.
retired opening as
.includes('can never close')— lower case. The row never wrote it that way; itwrote
can never CLOSE. Measured againstorigin/main's own module, before any change:A negative pin that was already satisfied before the change is a phantom check: it evaluates, it passes,
and deleting it leaves every gate just as green. This PR pins
can never CLOSE.Ablations — both legs, on disk, restored
Run through
scripts/ablation-replace.mjs, which proves the mutation reached the file (anchor count andgit hash-objectblob) and proves the restore againstHEADrather than against an exit code.Run on head
2e6ee9e(file blobc49d47a7484d), in an isolated worktree so no other measurementshared a tree with the mutation.
has NO SCHEDULED OR DECLARED RELEASE. ' +→has no release anyone has scheduled. ' +c49d47a7484d→d50b37d13bb1; 3 cases RED, self-test exit 1closed 15, 16 of the 29 …closed 21→ the same sentence without the countsc49d47a7484d→7052be9a1f12; 2 cases RED, self-test exit 1Both restored:
blob == HEAD (c49d47a7484d)andgit diff HEADempty,git status --porcelainemptyafter each leg. ⛔ No ablation artefact is left in the tree; neither leg is a permanent test.
What was carried forward from the lead commit, and what was dropped
The branch already existed on
originat9180b90— thedomain:devxseat's aborted dispatch of2026-09-18, one commit, +92 / −30, no PR, base
86b9000. It is merged, never re-derived and neverforce-pushed:
3746268is the merge commit, and its history is intact.Merge conflict, measured rather than assumed. Exactly one textual conflict, and it is a boundary
rather than a rewrite: #19255 inserted the label-transition exit block (
UNLOCK_LABEL_EXIT_RE,unlockLabelExits,h19DeclaredExitFired) directly above H26's banner comment, so both sides editedthat one banner line. Everything else merged textually with no conflict, and the merge reproduces the
lead's delta exactly (+92 / −30 against
origin/main).Kept from the lead: the direction itself (claims no SCHEDULED release, not "no mechanism" — counted,
not asserted); the census-with-control shape in the rationale block; the retirement carried to every
H26-owned site including the two-line-split one; the requirement that the self-test pin the new wording
POSITIVELY; H26's useful final sentence preserved verbatim.
Dropped or rewritten, because #19255 landed in between:
statement — since [finding] a pm:blocked card whose Blocked-by target is a needs-user-decision card can never be released — the unlock predicate is "closed", and Unlock-action recognizes exactly one PR-shaped spelling #19255 the sweep can fire a declared label-transition exit on exactly these
targets. The row now says what it tests: nothing schedules the close and this card declared no
exit.
NO SCHEDULED RELEASEbecameNO SCHEDULED OR DECLARED RELEASE, and the four pins naming theold phrase moved with it.
⭐ The exit:paragraph namingthe declarable exit (
Unlock-action: re-check #N when label needs-user-decision absent). Thatparagraph and the
exitKeysstand-down are left byte-for-byte untouched.now carries them permanently and they should be a reading this branch took.
can never closepin is corrected tocan never CLOSE(see above).origin/mainata0e62e6(the lead'sown delta was +92 / −30, net +62).
Verification
node scripts/pm/check-half-states.mjs --self-test→ exit 0, 5125 cases (5118 atorigin/maina0e62e6, measured on a throwaway worktree at that commit; +7 here).SELF_TEST_BATTERY_FLOORunchanged at 8, roster unchanged, every battery's registered countunchanged (
H66 183/172, H19 37/34, H65 46/42, H67 152/142, H2/H47/H66 213/184, ISSUE_BODY_LIMIT 55/52, H19/H26 11/10, H68 26/24). The new cases are plaint(...)cases beside H26's existing ones,⛔ not at
selfTest()'s tail and ⛔ not a new battery.#19255regression case added: a card whose body carriesUnlock-action: re-check #987 when label pm:on-hold absentstill stands the row down for thattarget after the rewrite.
origin/main,NO MECHANISMhas 5occurrences and
can never CLOSE2, and every one of them is inside this single file. Nothingunder
.claude/,scripts/elsewhere, or.github/pins either phrase, so no other file has to movein this PR.
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack,41 commands, change set derived by the tool itself) is run with each exit captured before any pipe;
results are in the
os-dev-reportcomment on finding(pm-gate): H26's "can never CLOSE" premise is asserted, not counted — counting refutes it, and objectui's ported copy cannot be fixed without this one moving first #18017.skip-changeset: the diff publishes nothing.scripts/pm/**is on the no-publish fast path and appearsin no released package's
files[].Acceptance notes
target.
ISSUE_BODY_LIMITis 262144 bytes and the renderer trims low-band rows first, so this is areal if small charge against the anchor body — paid because the sentence is quoted outward and the
count has to travel with the quote. The full census lives in the rationale block, not in the row.
「无机制可唤醒的卡 ⛔ 不 hold」. That is the maintainer's wording about when not to hold a card, not this
row's structural verdict about a target, and AGENTS.md forbids rewriting a quoted ruling — so it stays
verbatim.
scripts/upstream-port-pin.jsonon the consumer card (objectui#9317 / objectui PR The datasource-admin HTTP family is reachable unauthenticated, read and write — mounts outside the enforceAuth seam #9391); ⛔ nodivergence is declared here and the pin is not touched.
.includes(...)that wasalready false before the change) is a self-test hazard with no mechanical guard — a case that cannot
fail reads identically to one that passes. Noted rather than filed because no such pin exists on
origin/main; the one instance lived only on an unmerged branch and is fixed here. Successor: the nextPR touching this suite's negative pins.
Generated by Claude Code