Skip to content

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 into
mainfrom
claude/issue-15884-half-states-read-the-release-rule
Sep 5, 2026
Merged

tooling(pm): check-half-states reads the landed release rule — H1/H24 prose, and a report-only H47#15901
os-steve merged 1 commit into
mainfrom
claude/issue-15884-half-states-read-the-release-rule

Conversation

@claude

@claude claude Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

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_MARKER is 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:

  • (a) the record without the field — a Release: line newer than the last Claim: 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.
  • (b) the field without the record — an open pm:queue card with no assignee whose Claim: no later Release: 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 last Claim: (a re-claim after release); a Release: newer with the assignee cleared; a pm:dispatched card with a Claim: and no Release:. Leg (b) also declines a card carrying pm:dispatched alongside pm: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 calls commentCache.get only. 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 pair releaseCandidates/releaseJudged is what makes the gap legible, and the summary clause says plainly that the pm:queue leg is the thinnest half of that corpus, because H2 buys a thread only for an ASSIGNED card.

Ordering is created_at with a thread-order fallback, not comment id. That is governingClaim's and latestSeatMarker'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_MARKER mirrors the claim marker's strictness and is no wider — the line must begin with the word, a leading blockquote is allowed, and Released: or a fullwidth colon is a malformed release rather than a dialect.

Verification — all readings at f845bc5d16

Self-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 to HEAD:scripts/pm/check-half-states.mjs before 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.

leg mutation result
A h47ReleaseRecordDesync always declines 19 of 19 firing-direction H47 cases red; the clean-direction cases stay green, correctly
B the findings.push removed 2 red — the H47 registry-coverage case, and this file's own standing invariant "no band names a family the sweep never emits"
C the summary clause removed 9 red — 4 generic summaryClause invariants plus 5 H47 summary cases

The tree file's blob was identical before and after the whole run (e98159bf326e28417205a806b769495f9a5259e4), and git diff HEAD over it is empty.

Live sweeps, read-only, before (origin/main's blob, extracted and hash-checked against origin/main:scripts/pm/check-half-states.mjs) and after, on both boards:

board before after H47 rows H47 coverage
objectstack 307 findings 305 0 85 of 272 candidate cards had a thread in hand
objectui 163 findings 167 0 28 of 235

Zero H47 findings is the expected reading and not a quiet row: no card on either board carries a 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. ⚠️ 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.

Gates. Derived with node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack on the final one-path file list; all 32 commands run, and --ran reconciles: 32 derived famil(ies) accounted for — 32 run, 0 NOT-MEASURED. Whole-repo pnpm lint (eslint . --no-inline-config) green through scripts/pm/os-verify-lock.sh on slot issue-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-closure and check:published-readme-exports — which are NOT MEASURED here rather than passed, and which this one-file scripts/pm diff cannot reach in either direction.

Governed-surface predicate on the final file list: NOT governed — ordinary queue landing applies. Hence skip-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 -S over 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

…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
@claude claude Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 5, 2026
@github-actions github-actions Bot added the size/m label Sep 5, 2026

os-steve commented Sep 5, 2026

Copy link
Copy Markdown
Collaborator

ACCEPT — lands #15884 as ruled. Pure code (one file under scripts/pm/, governed-surface predicate exit 0), so ready + SQUASH auto-merge follow in this same act.

What the seat verified, at head f845bc5d16

  • Scope matches the ruling: H1's header-inventory sentence and H24's finding sentence now quote the state model's own definitions (SKILL.md lines 107/109/465) instead of narrating a habit or a measured origin; the predicates h1DispatchedNoAssignee / h24QueuedWithAssignee are byte-unchanged; CLAIM_COMMENT_MARKER is untouched.
  • H47 is report-only, band state, and buys no new REST path: it reads commentCache.get only, sits at the foot of the per-card loop (so a card whose thread H2 fetched is judged, and one it did not is UNJUDGED rather than clean), and its releaseCandidates / releaseJudged pair makes the coverage gap legible in the summary clause. RELEASE_COMMENT_MARKER is as strict as the claim marker and no wider.
  • Ordering reuses the file's existing created_at + thread-order resolution rather than adding a second rule.
  • Self-test: 2341 cases green in the seat's own compare worktree (2267 on main); the one renamed case keeps its assertion and re-points it at the landed spelling 「同笔清 assignee」.
  • CI, seat's read 14:03Z: 35 check runs, 24 success, 11 skipped, none failing, none pending. Lint & Repo Gates and Live half-state sweep both green on this head.
  • Body line 1 is Fixes #15884; the card closes on merge.

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 claude/issue-15884-half-states-read-the-release-rule
Reviewed-by: pm-dispatch skills seat, https://claude.ai/code/session_019RfFHiRCSs3JXLK4cwcfox


Generated by Claude Code

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