Skip to content

pm(half-states): H66 lists a released pm:queue card whose remainder is not work - #18514

Merged
os-justin merged 5 commits into
mainfrom
claude/issue-18299-released-nondispatchable-remainder-row
Sep 16, 2026
Merged

os-justin merged 5 commits into
mainfrom
claude/issue-18299-released-nondispatchable-remainder-row

Conversation

@os-justin

@os-justin os-justin commented Sep 16, 2026

Copy link
Copy Markdown
Collaborator

Fixes #18299

A card released with a remainder no dev can execute — a maintainer ruling, a reading only the reporter can take, a confirmation owed by another seat — wears pm:queue with no assignee, which the queue view and 「最老优先」 both read as ready to hand to a dev. The label cannot tell that apart from real queue work; only the card's newest seat comment can, and reading that costs a full card read per candidate.

H66 is the detector the card asks for: report-only, a listing, never a relabel and never a gate verdict.

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

The clause it enforces

Two lines of .claude/skills/pm-dispatch/SKILL.md, quoted verbatim in the row's own sentence:

  • pm:queue = 有具名落点或复现的具体缺陷,或范围明确的工具/门禁修复,无可问之事。」 — the label is a definition, not a habit.
  • 「释放是显式动作:让卡离手者同笔清 assignee + Release: 行(会话/因/去向);下一任重新认领。」 — the release act, whose 去向 field is the only thing that can say where the card went.

⛔ The row does not decide the state question. Whether these cards belong in needs-user-decision, in pm:awaiting-maintainer or in a spelling that does not exist yet is left open exactly as the card leaves it; the remedy the row prints is 「judge by hand — this is a listing」.

The measurement — the dispatch's A1 was falsified before anything was built

A1 assumed the machine-readable half is the Release: line's third field (去向). Measured 2026-09-16 over both boards the filing card names, read-only:

corpus threads Release: lines release announced in a heading
objectui#7696 · #8167 · #8826 · #8938 (the filer's four) 4 0 3
objectstack, 25 most recently updated open unassigned pm:queue cards 25 0 0

Zero of 29. The lit control for that zero is the prose leg read by the same fetch over the same threads: it answers three on the same four specimens, so the zero is a property of the board, not of a broken reader.

⇒ Both legs ship, the canonical one first. It is kept, not dropped, for a mechanical reason: H47 exists to report exactly that absence, and as it is answered the canonical line starts appearing — on that day it is the only reading that can tell a return-to-queue (去向 = pm:queue, ⛔ never a finding) from an exit, because both are written in the same announcement heading otherwise. The summary clause publishes the zero on every run.

The prose leg's three anchors are frozen beside the live comment each was measured on — objectui#7696 5664140343, #8167 5663517223, #8938 5668851500 — and the third anchor named by the filing card itself is labelled measured: false, so nobody later reads it as a measurement. Matching is heading-anchored (H58's fence/blockquote/table/code-span refusals, reused): the filing card, this PR and the row's own docblock all quote the spellings, and none of them may fire.

⚠️ pm:queue is not read over the whole comment body, and that is measured rather than stylistic: the two live release comments on objectui#8167 and #8938 both say 「pm:dispatched stripped, pm:queue added」 in their state-write paragraph, so a body-wide read would clear precisely the specimens this row exists to list.

The bounded fetch leg — and why the first landing needed one

The row first shipped buying nothing: one commentCache lookup per card. The first live sweep measured what that costs — 194 candidates, a thread in hand for 1. The corpus is thin by construction, not by luck: the only row that buys a card thread for a pm-tracked card is H2's claim read, and H2 buys one only for an ASSIGNED card, which is this population's exact complement. A listing that cannot see 99.5% of its own population is a row whose silence means nothing, and the filing card's own criterion already asks for the fetch — 「fetch the newest comment authored by a seat」.

So a bounded buy pass now sits at the foot of the sweep:

  • One page per card, the NEWEST one, located from the card's own comments count through H65's exported h65NewestPagePlan / h65CommentPagePath rather than a second copy of the arithmetic. ⛔ Never page 1 on a long thread: GitHub serves issue comments OLDEST-FIRST, so a page-less read returns the archive (the [finding] check-half-states: H44's seat leg is blind on the triage seat post it was written for — its corpus is the thread's FIRST comment page (oldest-first, five weeks stale on #6015) and H44_ROUND_OPEN_MARKER no longer matches the marker spelling; H56 / H64 share the corpus #18312 measurement — five weeks stale on a long thread).
  • Ordered newest-touched first (updated_at descending, number as a total-order tiebreak; an unreadable stamp sorts last, never promoted ahead of a dated card). A release is a write on the card, so that is where fresh evidence lives — deliberately the opposite of the dispatch order 「最老优先」, which says which card to work, not where the evidence is.
  • Capped by a named, frozen H66_THREAD_BUY_CAP = 100. The standing caller is half-state-patrol.yml on PATROL_CRON = 37 1,7,13,19 * * * — four runs a day — under the Actions token's 1000 requests/hour per repository, beside the sweep's existing label pages, listings, PR windows, H50 walks, H43 probes, H45 parent reads and seat pages.
  • A cap is not a coverage claim. Candidates past it are NOT ATTEMPTED and counted separately (H40's posture). A fetch that fails, or a carrier whose comments count is unreadable and whose page 1 came back FULL (so the newest comment cannot be located), leaves that card UNJUDGED — never clean, the H4 three-state contract — and is counted as such.
  • Placement is mechanical: after every row that reads commentCache, because a newest page is not the same object as the first page that cache holds. The write-back is guarded to the one case where the bought page is the whole thread (plan.page === 1); seatPostRowsFor keeps its own window out of that map for exactly this reason, and this applies the same rule to the one case where the two coincide. A card whose thread is already in hand is judged in place, free, and never deferred — so never re-bought.

seatWindowAudit gains seatPurchaseSites. The buy adds a second located-page purchase, so rather than raise the file-wide pin from 1 to 2 and lose the argument behind it, the seat-post bound is split out and stays at 1 — the new purchase is on a disjoint population (an unassigned pm:queue CARD, never a pm:seat post), so it cannot be the 「same page bought twice」 the original bound is about.

Disjointness (the dispatch's A3), and the one overlap stated rather than hidden

  • H8 requires pm:dispatched; this population is pm:queue without it. Disjoint by label.
  • H47 leg (a) requires an assignee; leg (b) is the inverse of this row — it fires when a Claim: is the newest ownership record and no Release: answers it. On the canonical leg releaseAnswersClaim is true and H47 returns null.
  • H49 requires pm:dispatched and an assignee. Disjoint.
  • H58 reads the card's own declaration of what it IS; this reads the release record of where it WENT. A card may wear both and fire both — two readings, never one restated.
  • ⚠️ The overlap: on the prose leg the release was never written as a canonical line, so H47 leg (b) fires too. That is correct and is a different defect (a record missing its canonical line) from this one (a card in the dispatch pool with a remainder). Asserted as a case, not hidden.

Population excludes an assigned card (H24's row) and any card carrying a second member of PM_EXCLUSIVE_STATE_LABELS — that pair is H3's, H25's or H29's contradiction. One vocabulary, ⛔ not a second list.

Battery roster and floor

This file printed one total over a flat case list — the shape AGENTS.md 「Writing a --self-test」 names as evidence rather than proof. The handshake (SELF_TEST_VERDICT) was already here; the floor was not. This PR adds the other half, copied from scripts/check-agent-model-declared.mjs and ⛔ never imported:

battery registered at 301de97 pin
H66 released queue card 183 172

Three failure conditions, each producing a real process.exit(1): a declared battery below its pin; a case registered against an undeclared battery; the roster below SELF_TEST_BATTERY_FLOOR. ⚠️ The roster is partial and says so: the several hundred pre-existing t(...) cases stay unbatteried, and retrofitting them is a diff of its own.

Cases cover every clause the dispatch named — (a) a 去向 naming a non-queue owner ⇒ listed · (b) 去向 = pm:queue ⇒ silent, including where an announcement heading sits beside it · (c) both prose forms ⇒ listed · (d) any later comment ⇒ silent, structurally, because the row reads the newest · (e) an assigned card ⇒ outside the population · (f) no comment at all ⇒ silent — plus the seven quoting shapes that must not fire, the anchor set's cap/freeze/provenance, the three-input-state contract, band, registry coverage, and the summary clause in both media. The buy leg adds: the cap respected when candidates exceed it, newest-touched ordering with the undated card last, a bought page judged by the same predicate as a cached one, a failed buy read as UNJUDGED, and the two source-audited guarantees below.

Measurements — all at 301de97

Gatesnode scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 40 commands at 301de97; all 40 run, each exit code captured by redirect-then-$? and never through a pipe, 40 exit 0. Reconciled with --ran: 「40 derived famil(ies) accounted for — 40 run, 0 NOT-MEASURED (a DERIVED zero — all 40 recorded an exit code and none of them is 3)」.

Lint — the whole-repo run, not a narrowing: pnpm lint = eslint . --no-inline-config over eslint's own population of 6,798 files, exit 0 at 301de97.

Self-testnode scripts/pm/check-half-states.mjs --self-test and pnpm check:pm-half-states at 301de97: exit 0, 「4546 cases pass. Batteries: H66 released queue card 183/172.」

Live sweep — report-only, from an agent container at 301de97, exit 0: 465 half-states over 397 pm-labelled issues. The H66 clause, the new quadruple:

Released queue cards (H66): 197 open unassigned pm:queue card(s) could be spoken about, 1 with a comment thread ALREADY in hand, 100 NEWEST comment page(s) BOUGHT this run (cap 100, 96 candidate(s) NOT ATTEMPTED at that cap), 0 listed. 0 card(s) stayed UNJUDGED.

⇒ the leg works and costs what it declares: 100 pages bought, every one read, nothing unjudged. The board genuinely has no card in this shape right now — the filing card's own four specimens were all dispositioned by hand between filing and this build. Before the leg the same sweep read 194 / 1 / — / 0, i.e. the row could see one card in two hundred.

Request count — the run total is NOT MEASURED: the script exposes no request counter, and the container's /rate_limit proxy reported a full 15000/15000 both before and after with the quota window rolling between the two reads, so the delta is not a measurement either. What is exposed is the leg's own cost, and it is exact: queueReleaseBought = 100, one request each, printed on the summary line every run.

Ablations — three, each mutating the committed tree, proving the mutation on disk by occurrence count, restoring with git checkout HEAD -- path under a trap, and verifying the restore by blob hash against git rev-parse HEAD:path plus an empty git diff HEAD:

  1. the release-announcement anchor rewritten so it can never match ⇒ 29 named cases red, suite runs to completion, exit 1.
  2. the battery pin raised above the registered count ⇒ 「the battery floor failed — battery 「H66 released queue card」 registered 183 case(s), below its pin of 200」, exit 1.
  3. the defer guard removed, so a thread already in hand would be re-bought ⇒ exactly one named case red — 「⭐ …and that place is guarded by the cache test, so a thread ALREADY in hand is never re-bought (got 0, want 1)」 — exit 1.

Control after every restore: exit 0, 4546 cases pass. ⭐ The first ablation also found two real defects in the battery it was measuring — a direct [0].heading and a direct field read on a three-valued helper, each of which aborted the whole suite with a TypeError instead of reporting a case. Both are now read through describing readers, with the measurement recorded beside them.

Renderers — both, offline over a synthetic finding: renderPlain and renderMarkdown each render the H66 row, the clause with its counts, and the markdown family ledger names H66.

Scope

scripts/pm/check-half-states.mjs only. ⛔ objectui's copy is byte-pinned there and is not touched. skip-changeset: scripts/pm/** is the fast lane and nothing in any package's files[] moves.


Generated by Claude Code

… is not work

A card released with a remainder no dev can execute wears `pm:queue` with no
assignee, which the selection order reads as ready to dispatch. H66 reads the
card's NEWEST comment out of the shared cache -- no new fetch class -- and
lists the card when that comment is a release the label contradicts.

The canonical `Release:` line's 去向 was measured first and came back ZERO of
29 threads on two boards, with the prose leg lit on 3 of the same 4 specimens
as the control; both legs ship, the canonical one first, and the summary clause
publishes the zero. Report-only: it never relabels and never returns a verdict.

Adds this file's first battery roster + floor beside the existing handshake.

Claude-Session: https://claude.ai/code/session_01Gqi43smmqjJ5sUrhfoPeKu
Co-authored-by: Claude <noreply@anthropic.com>
…t `[0].heading`

Measured during the anchor ablation: the direct index throws while evaluating
the case's arguments, so the suite ABORTS at that line and every later case
never runs -- the exact shape this file's own row-wrapper note warns about.

Claude-Session: https://claude.ai/code/session_01Gqi43smmqjJ5sUrhfoPeKu
Co-authored-by: Claude <noreply@anthropic.com>
Second abort site measured by the same ablation: `h66ReleaseVerdict` is
three-valued, so a direct field read throws in the case's arguments and takes
every later case with it. Both readers keep a nulled helper reporting under its
own case name.

Claude-Session: https://claude.ai/code/session_01Gqi43smmqjJ5sUrhfoPeKu
Co-authored-by: Claude <noreply@anthropic.com>
The first live sweep measured the cost of buying nothing: 194 candidates, a
thread in hand for ONE, because the only row that buys a card thread buys it
for an ASSIGNED card and this population is that set's complement. The row now
defers cache misses to a bounded pass at the foot of the sweep: candidates
ordered newest-touched first, at most H66_THREAD_BUY_CAP=100 per run, ONE page
each -- the NEWEST, located from the card's own comments count through H65's
helpers, never page 1 on a long thread.

A failed buy, or a carrier whose newest page cannot be located, is UNJUDGED and
counted as such; candidates past the cap are NOT ATTEMPTED and counted
separately. The cache write-back is guarded to the one case where the bought
page IS the whole thread, so no reader is handed a partial window.

seatWindowAudit gains seatPurchaseSites: the second located-page purchase is on
a disjoint population, so the fetch-per-seat-post bound is split out and kept at
one rather than raised.

Claude-Session: https://claude.ai/code/session_01Gqi43smmqjJ5sUrhfoPeKu
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/xl and removed size/l labels Sep 16, 2026
@os-justin
os-justin marked this pull request as ready for review September 16, 2026 18:43
@os-justin
os-justin added this pull request to the merge queue Sep 16, 2026
Merged via the queue into main with commit 9e6c6cf Sep 16, 2026
42 checks passed
@os-justin
os-justin deleted the claude/issue-18299-released-nondispatchable-remainder-row branch September 16, 2026 19:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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

Projects

None yet

2 participants