Skip to content

pm(half-states): H19 founds every verdict on the set it judges - #18654

Merged
os-justin merged 1 commit into
mainfrom
claude/issue-18379-h19-blocked-by-body-only
Sep 17, 2026
Merged

os-justin merged 1 commit into
mainfrom
claude/issue-18379-h19-blocked-by-body-only

Conversation

@os-justin

Copy link
Copy Markdown
Collaborator

Fixes #18379

H19 gathered Blocked-by: targets over the body ∪ comment union and judged the expiry against the body's set (#17564), but the UNJUDGED branch never moved with that split: it stayed founded on the resolution set, so a target a seat had superseded in the body could still veto the verdict from the comment archive whenever its number was unreadable. Two conditions had to coincide, which is why it outlived #17564 — the target is superseded (judging correctly ignores it) and the superseded number 404s (resolution cannot dismiss it either) — and the resulting row was unclearable by construction: the card's owner had already done the prescribed thing, and the only remaining lever, editing the archived comments, is the write the protocol forbids because those comments record what an earlier seat measured and why the line was migrated.

Authored by session_01Gqi43smmqjJ5sUrhfoPeKu (Claude Code). Every reading below was taken on this branch at ab30706ed, the head this body describes.

The two sets, before and after

blockerTargetsFor is untouched: DISCOVERY stays a union, because H26 and H28 share those resolutions and a comment-borne target is a perfectly good answer to what they ask. What changed is inside h19BlockOutlivedBlocker.

reading may FOUND a row — before may FOUND a row — after
CLOSED, body-borne (or comment-borne on a body-less card) yes yes (unchanged)
CLOSED, comment-only beside a body set no — hygiene clause no — hygiene clause (unchanged)
UNRESOLVED, body-borne (or on a body-less card) yes — UNJUDGED yes — UNJUDGED (unchanged)
UNRESOLVED, comment-only beside a body set yes — the unclearable row no — named in its own clause
OPEN, either channel no — withholds only no — withholds only (unchanged)

The denominator moved with it: an archive-only unreadable target no longer enters the row's N of M count, which was the other half of the sentence the card quotes (「1 of 2 … the set judged against the BODY's set」 on a card whose body names one target).

What the archive is still consulted for, and must be: evidence that the wait may still be RUNNING. An OPEN target it names still withholds a discharge, and an UNREADABLE one now withholds a FULL discharge in a new branch (「No target this row JUDGED is still open, but N target(s) the comment archive names … could not be read at all」). Narrowing that half to the body too would re-create #11747 — the measured mirror defect, where a re-park parks the live blocker in a comment while the body still names the spent one, and a card was released to pm:queue while its real blocker was open and dispatched. Only the power to FOUND a verdict is carrier-scoped; evidence of a live wait never is.

UNJUDGED is not silenced. Three controls say so live, below.

Pins

Battery H19 judged-set founding — the file's second registered battery (SELF_TEST_BATTERIES), 37 cases registered, pinned at 34; SELF_TEST_BATTERY_FLOOR raised 12, so deleting either battery now fails the roster rather than shrinking a total nobody reads.

It floors a NEGATIVE — a row that must not fire — plus the controls that keep the silence scoped:

Two existing pins were re-judged rather than deleted, and both are called out where they sit:

  1. H19 precedence: an UNRESOLVED comment-borne target is NOT silenced by the body asserted exactly the row this card measures as unclearable. Same fixture, re-judged to the direction the file's own asymmetry table always declared: it now founds no row beside a body set.
  2. H19 superseded: the clause rides the UNJUDGED branch as well used a fixture whose body named a target that appeared in no resolution. Its unreadable target is now the BODY's own, which is what the pin was always about: a hygiene clause can only ride a row something in the judged set founded.

One prose correction rides along: the summary line's H19 shortfall clause promised that every unresolved target is named on its own card's row. That clause was measured false once already (#11218) by promising more than the rows deliver, and a repair that narrows what a row founds owes it a second correction — it now names the archive-only class it no longer founds a row for.

Ablation — from the committed fix, restored by blob hash under a trap

Mutation: the founding filter state === 'unresolved' && carries(r) reverted to state === 'unresolved', on disk, at HEAD = ab30706ed.

HEAD blob: fe92847f1b12633e42f47790b6a47888e0f174ee
BEFORE: fixed-line count = 1     AFTER: fixed-line count = 0
BEFORE: mutant-line count = 1    AFTER: mutant-line count = 2   (one pre-existing identical line in H4's prose clause)
AFTER:  on-disk blob = f764989fbeebeedd2d9920f7dd7678da0abdff1c   (differs from HEAD blob)
ABLATED self-test exit: 1 — 8 of 4583 cases failed
RESTORED: blob fe92847f1b12633e42f47790b6a47888e0f174ee == HEAD blob; git diff HEAD empty

Direction: RED, as predicted, and the reds are the right ones — five negatives plus three counts:

✗ H19 precedence: an UNRESOLVED comment-only target founds NO row beside a body set
✗ H19 #18379: …and the unclearable row is GONE
✗ H19 #18379: …⛔ with no quieter row invented in its place
✗ H19 #18379: a superseded target the archive alone names founds nothing
✗ H19 #18379: …and that holds for a LOCAL superseded number too
✗ H19 #18379 withhold: …and the count stays the judged set's
✗ H19 #18379 clauses: …and neither enters the count
✗ H19 #18379: the UNJUDGED denominator never counts an archive-only target

The got value on the card's own fixture is the measured sentence, reproduced verbatim by the ablated code:

pm:blocked and 1 of 2 Blocked-by: target(s) could NOT be resolved this sweep (#13515 (HTTP 404)) — the set judged against the BODY's Blocked-by: set …

Self-test

node scripts/pm/check-half-states.mjs --self-test   ::  exit 0
✓ check-half-states self-test: 4583 cases pass. Batteries: H66 released queue card 183/172, H19 judged-set founding 37/34.

Run under scripts/pm/os-verify-lock.sh (VERDICT command-exit 0), as were the ablated run and the live sweep.

Live sweep — report-only, from this worktree, after the fix

node scripts/pm/check-half-states.mjs at 2026-09-17T12:25Z, head ab30706ed, under the verify lock (VERDICT command-exit 0, held 366s): swept 409 open pm-/p0-labeled issues, 462 half-states, 12 H19 rows.

Before — anchor #9857 as the scheduled patrol rewrote it at 2026-09-17T07:50:06Z (four H19 rows, all UNJUDGED, which sort above the body's size trim): 11973, 11975, 15140, 15214.

H19 #11975pm:blocked and 1 of 2 Blocked-by: target(s) could NOT be resolved this sweep (#13515 (HTTP 404)) — the set judged against the BODY's Blocked-by: set …

After — this run:

Gates

Derived from this worktree with node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack scripts/pm/check-half-states.mjs (list re-derived after the commit: identical), every command run, each exit code captured by redirect-then-$?, reconciled with --ran:

✓ dispatch-gates --ran: 38 derived familie(s) accounted for — 38 run, 0 NOT-MEASURED
  (a DERIVED zero — all 38 recorded an exit code and none of them is 3)

All 38 exited 0. In full:

node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0
node scripts/check-changeset-no-major.mjs --self-test :: exit 0
node scripts/check-ci-filter-parity.mjs :: exit 0
node scripts/check-closing-keyword-parity.mjs :: exit 0
node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0
node scripts/check-comment-mask-corpus.mjs :: exit 0
node scripts/check-declaration-mirrors.mjs :: exit 0
node scripts/check-declaration-mirrors.mjs --self-test :: exit 0
node scripts/check-scripts-symbol-anchors.mjs :: exit 0
node scripts/check-scripts-symbol-anchors.mjs --self-test :: exit 0
node scripts/check-self-test-wired.mjs :: exit 0
node scripts/check-self-test-wired.mjs --self-test :: exit 0
node scripts/check-self-test-workflow-commands.mjs :: exit 0
node scripts/check-self-test-workflow-commands.mjs --self-test :: exit 0
node scripts/check-whole-set-label-write.mjs :: exit 0
node scripts/check-whole-set-label-write.mjs --self-test :: exit 0
node scripts/pm/bare-root-worklist.mjs --self-test :: exit 0
node scripts/pm/board-snapshot.mjs --self-test :: exit 0
node scripts/pm/check-governed-queue-guard.mjs --self-test :: exit 0
node scripts/pm/sweep-closed-cards.mjs --self-test :: exit 0
pnpm check:agent-test-spelling :: exit 0
pnpm check:bash32-floor :: exit 0
pnpm check:changeset-gate-self-tests :: exit 0
pnpm check:cli-command-ids :: exit 0
pnpm check:closing-target-claim :: exit 0
pnpm check:commit-card-trailers :: exit 0
pnpm check:cross-package-test-inputs :: exit 0
pnpm check:driver-memory-census :: exit 0
pnpm check:entry-guard :: exit 0
pnpm check:nul-bytes :: exit 0
pnpm check:parse-guard :: exit 0
pnpm check:partof-closing-keyword :: exit 0
pnpm check:pm-dispatch-gates :: exit 0
pnpm check:pm-half-states :: exit 0
pnpm check:pnpm-filter-targets :: exit 0
pnpm check:ratchet-remedy-authority :: exit 0
pnpm check:refd-timer-probe :: exit 0
pnpm check:watch-hint-literal :: exit 0
pnpm lint  (repo-wide, as PR #18514 did) :: exit 0

skip-changeset: scripts/pm/** is the fast lane — nothing in any package's files[] moves, and the patrol script is not published.

Acceptance notes


Generated by Claude Code

H19 resolved `Blocked-by:` targets over the body∪comment union and judged
against the body's set, but the UNJUDGED branch sat on the RESOLUTION step:
a target superseded in the body and unreadable in the archive could still
veto the verdict, producing a row no correct action by the card's owner
could ever clear.

Both founding states are now taken over the judged set. What the archive
still does — and must, per #11747 — is withhold: an OPEN target it names
still refuses a discharge, and an UNREADABLE one refuses a FULL discharge
and is named in its own clause.

Claude-Session: https://claude.ai/code/session_01Gqi43smmqjJ5sUrhfoPeKu
Co-authored-by: Claude <noreply@anthropic.com>
@os-justin os-justin added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 17, 2026 — with Claude
@os-justin
os-justin marked this pull request as ready for review September 17, 2026 12:51
@os-justin
os-justin added this pull request to the merge queue Sep 17, 2026
Merged via the queue into main with commit 14d2544 Sep 17, 2026
39 checks passed
@os-justin
os-justin deleted the claude/issue-18379-h19-blocked-by-body-only branch September 17, 2026 13:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants