Skip to content

Commit 14d2544

Browse files
os-justinclaude
andauthored
pm(half-states): H19 founds every verdict on the set it judges (#18654)
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 `1` → `2`, 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: - the instance from the card, in its recorded shape (#11975: body refreshed to `Blocked-by: #18336`, the archive naming `#13515`, resolved 404) ⇒ discovery still yields both targets, and **no row of any kind**; - the same shape generically, cross-repo and local ⇒ no row; - CONTROL ①: a body-borne unreadable target ⇒ `UNJUDGED` fires, counted `1 of 1`, still refusing to be read as a quiet row; - CONTROL ②: a body-less card ⇒ still judged from comments, `UNJUDGED` fires; - CONTROL ③: a closed body target ⇒ H19's ordinary row, `1 of 1`, full discharge; - CONTROL ④: #11747's mirror ⇒ an OPEN archive target still withholds, still `1 of 2`, still `PARTIAL`; - the withholding path: an archive-only unreadable target beside a closed body target ⇒ the row fires on the body target, is ⛔ not published as a full discharge, and names the archive target in a clause saying it founds nothing; - ⛔ H26/H28 unaffected: the union still discovers both channels. **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`. ```text HEAD blob: fe92847 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 fe92847 == HEAD blob; git diff HEAD empty ``` Direction: RED, as predicted, and the reds are the right ones — five negatives plus three counts: ```text ✗ 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 ```text 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** #11975 — `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 … **After** — this run: - **#11975 carries no H19 row at all.** Its rows are H11 (important-parked) and H26 (transitive wait on `#18336`) — the two that were always true of it. - **#11973 still fires `UNJUDGED` on `#13689` (HTTP 404)** — the same unreadable epic, reached through that card's **body**. This is the scope note on the card, and it is the control that proves nothing was silenced. - **#15140 and #15214 still fire `UNJUDGED`** on `objectstack-ai/cloud` targets (HTTP 403, repo not readable to this credential) — the standing accepted cross-repo class, untouched. - Ordinary expiry rows still fire (#15642, #15768, #16354): `is CLOSED … the block has outlived its blocker`. - The summary clause now publishes the new limit on every run: 「a target reaching a card ONLY through the comment archive beside a body that states its own `Blocked-by:` set is outside what H19 judges, so it rides a row rather than founding one」. ## 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`: ```text ✓ 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: ```text 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 - Diff is one file and confined to H19, its clauses, its summary sentence and its pins — #18372 (a new reading in the same file) queues behind this PR untouched. - objectui's byte-pinned copy is **not** touched; it re-syncs through objectui#9395. - Noted, not filed: H26 (`h26BlockOnIndefiniteTarget`) reads the same union with no carrier split at all, so a target superseded in the body can still found an indefinite-wait row there. It is a different row with a different founding question and no measured instance was taken in this act, so it is an observation, not a card. Carrier: whoever next opens H26. --- _Generated by [Claude Code](https://claude.ai/code/session_01Gqi43smmqjJ5sUrhfoPeKu)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent 84ad2e1 commit 14d2544

1 file changed

Lines changed: 257 additions & 31 deletions

File tree

0 commit comments

Comments
 (0)