tooling(pm): check-half-states reads the landed release rule — H1/H24 prose, and a report-only H47 - #15901
Merged
os-steve merged 1 commit intoSep 5, 2026
Conversation
…24 prose, and a report-only H47
The dispatch protocol made release an explicit act on 2026-09-05: the seat that
lets a card go clears the assignee and posts a `Release:` line in one write.
Three places in the sweeper still read the board the way it read before.
- H1's header-inventory sentence narrated a habit ("dispatch marks a claim");
it now reads the definition the state model gives the label.
- H24's finding sentence narrated the measured origin (a state rollback) and
prescribed a bare assignee drop; it now quotes the rule and names both lawful
exits — finish the release, or re-claim.
- H47 (new, report-only, band `state`) reads the record itself, over the comment
threads the sweep already holds and with no fetch of its own: (a) a `Release:`
newer than the last `Claim:` while the assignee is still set — the record was
written, the field was not cleared; (b) an open `pm:queue` card with no
assignee whose `Claim:` no later `Release:` answers — an exit that left no
record, stated as the lower bound it is.
Predicates of H1 and H24 are unchanged. `CLAIM_COMMENT_MARKER` is untouched;
`RELEASE_COMMENT_MARKER` mirrors its strictness and is no wider.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019RfFHiRCSs3JXLK4cwcfox
Collaborator
|
ACCEPT — lands #15884 as ruled. Pure code (one file under What the seat verified, at head
Out of scope, correctly filed: #15899 (the 「同笔摘 assignee」 coinage in H8/H19/H24 inventory). The seat grades it separately and holds it behind this landing, same file. Implemented-by: os-dev executor, flight #15884, branch Generated by Claude Code |
os-steve
marked this pull request as ready for review
September 5, 2026 14:04
os-steve
deleted the
claude/issue-15884-half-states-read-the-release-rule
branch
September 5, 2026 14:29
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 #15884
One file:
scripts/pm/check-half-states.mjs. Two prose alignments, one new report-only row, its tests. The predicates of H1 (h1DispatchedNoAssignee) and H24 (h24QueuedWithAssignee) are unchanged,CLAIM_COMMENT_MARKERis untouched, and the diff adds no REST path.What the sweeper was still reading
The dispatch protocol made release an explicit act at 2026-09-05T11:52Z (PR #15848): the seat that lets a card go clears the assignee and posts a
Release:line in the same write, and the next seat re-claims. Three readings in this file predate it.H1's header-inventory sentence narrated a habit — "dispatch marks a claim; a claim is assign + claim comment". It now reads the definition the state model gives the label, 「
pm:dispatched… 恒带 assignee」 (SKILL.md line 109), and names the one lawful way for that field to empty (line 465). An empty field under this label is the label contradicting its own definition, not a step somebody forgot.H24's finding sentence narrated the measured origin ("a state ROLLBACK that swapped the label and left the field ... only dead-claim reclamation ever said so") and prescribed a bare assignee drop. It now opens on the definition — 「
pm:queue卡恒无 assignee,有即半态」 — keeps both contradicting readers and the asymmetric human/agent rule, and states the remedy as the release act itself, quoting line 465 unbroken and naming both lawful exits: finish the release, or re-claim.H47 (new, report-only, band
state) reads the record. Two shapes, both over the comment threads the sweep already holds:Release:line newer than the lastClaim:while the assignee is still set. The record was written, the field was not cleared, so the board still reads TAKEN on a card whose own thread says its owner has left.pm:queuecard with no assignee whoseClaim:no laterRelease:answers. An exit that left no record, and the row states it as the lower bound it is.Clean shapes pinned as cases: a
Release:older than the lastClaim:(a re-claim after release); aRelease:newer with the assignee cleared; apm:dispatchedcard with aClaim:and noRelease:. Leg (b) also declines a card carryingpm:dispatchedalongsidepm:queue— that pair is H3's contradiction, and a card the board still reads as in flight has not exited anything; the case pins that H3 does fire on it, so nothing goes unreported.Two decisions worth reviewing
No fetch, by construction. The row reads
commentCache— the object H2's claim fetch lands its rows in — and callscommentCache.getonly. A card whose thread no other row bought is UNJUDGED, never clean. The push therefore sits at the FOOT of the per-card loop rather than beside H1/H24 at its head: judged at the head, the cache would be empty for that card on every iteration and the row would report UNJUDGED forever while looking healthy. The coverage pairreleaseCandidates/releaseJudgedis what makes the gap legible, and the summary clause says plainly that thepm:queueleg is the thinnest half of that corpus, because H2 buys a thread only for an ASSIGNED card.Ordering is
created_atwith a thread-order fallback, not comment id. That isgoverningClaim's andlatestSeatMarker's existing resolution, reused rather than re-derived: ids are monotonic in practice but nothing in the protocol or the API documents them as an ordering, and a second ordering rule in a file that already has one is a drift waiting to happen. Equal stamps (two writes in one second, which is exactly what a hurried release makes) fall back to thread order.RELEASE_COMMENT_MARKERmirrors the claim marker's strictness and is no wider — the line must begin with the word, a leading blockquote is allowed, andReleased:or a fullwidth colon is a malformed release rather than a dialect.Verification — all readings at
f845bc5d16Self-test. 2341 cases pass, up from 2267 on
main. No case dropped: the name lists of the two runs differ by exactly one entry,H24: …and names the paired write it is owed, whose assertion is preserved and re-pointed at the spelling that landed (同笔清 assignee), with three companions added beside it.Ablation, on a throwaway copy placed in
scripts/pm/so every sibling resolves; the copy's blob was proved byte-identical toHEAD:scripts/pm/check-half-states.mjsbefore each leg, each mutation was confirmed on disk by grepping the injected and the deleted text, and each restore was proved by blob hash. Control: 2341/2341 green.h47ReleaseRecordDesyncalways declinesfindings.pushremovedsummaryClauseinvariants plus 5 H47 summary casesThe tree file's blob was identical before and after the whole run (
e98159bf326e28417205a806b769495f9a5259e4), andgit diff HEADover it is empty.Live sweeps, read-only, before (
origin/main's blob, extracted and hash-checked againstorigin/main:scripts/pm/check-half-states.mjs) and after, on both boards:Zero H47 findings is the expected reading and not a quiet row: no card on either board carries a⚠️ The before/after totals are eight minutes apart on live boards worked by parallel seats, so the deltas are drift, not this change: the per-family counts show H24 identical in both pairs (1 on objectstack, 4 on objectui), H1/H8/H20/H30/H38/H44/H46/H16 moving by one or two, and H47 contributing nothing to either side.
Release:line yet — the rule landed at 11:52Z — so leg (a) has no carrier by construction, and leg (b) fires only where a thread was already fetched. The self-test carries the shapes instead.Gates. Derived with
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackon the final one-path file list; all 32 commands run, and--ranreconciles:32 derived famil(ies) accounted for — 32 run, 0 NOT-MEASURED. Whole-repopnpm lint(eslint . --no-inline-config) green throughscripts/pm/os-verify-lock.shon slotissue-15884(79s hold, shared box). Every exit code was captured before any pipe.The eight artifact-roster gates whose roster sits under
scripts/were run rather than read as silent: all green except two that answer PREREQUISITE NOT MET without a closure build —check:dts-closureandcheck:published-readme-exports— which are NOT MEASURED here rather than passed, and which this one-filescripts/pmdiff cannot reach in either direction.Governed-surface predicate on the final file list:
NOT governed — ordinary queue landing applies. Henceskip-changeset: this diff publishes nothing from any package.Out of scope, filed not fixed
#15899 — H8's remedy sentence, H19's release text and the H24 header-inventory paragraph present 「同笔摘 assignee」 in quote brackets, and that phrasing is in no SKILL.md and never has been (
git log -Sover that file is empty on a non-shallow clone). It prescribes half of the landed act, which is the shape H47 exists to report. Not addressed here — this card's ruling scoped the diff to the two sentences above and the new row.🤖 Generated with Claude Code
https://claude.ai/code/session_019RfFHiRCSs3JXLK4cwcfox
Generated by Claude Code
Generated by Claude Code