pm(half-states): H67 reads the queue for cards a PR already landed on - #18678
Merged
os-justin merged 5 commits intoSep 17, 2026
Merged
Conversation
Claude-Session: https://claude.ai/code/session_01Gqi43smmqjJ5sUrhfoPeKu Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gqi43smmqjJ5sUrhfoPeKu Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gqi43smmqjJ5sUrhfoPeKu Co-authored-by: Claude <noreply@anthropic.com>
…eue-close-condition-reading
This was referenced Sep 17, 2026
os-justin
marked this pull request as ready for review
September 17, 2026 14:33
os-justin
enabled auto-merge
September 17, 2026 14:33
Base merge for the H65 tier-declaration landing (#18672). One conflict hunk, resolved by keeping BOTH batteries: the roster now declares four, so SELF_TEST_BATTERY_FLOOR rises to 4 rather than staying at the 3 each side wrote for its own third battery. Claude-Session: https://claude.ai/code/session_01Gqi43smmqjJ5sUrhfoPeKu Co-authored-by: Claude <noreply@anthropic.com>
os-justin
deleted the
claude/issue-18372-queue-close-condition-reading
branch
September 17, 2026 15:36
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 #18372
Clause-②: no
skip-changeset— the diff is one file underscripts/pm/**, which no released package ships (files[]on every published manifest excludes it), so nothing publishes.The defect
On 2026-09-09 a batch state transition moved a bucket of cards back onto
pm:queue. The transition set the label correctly and nobody re-read each card's own closing condition, and part of that bucket had already been delivered by another PR during the wait — so the queue length every seat reads (取卡全序, the priority sort, a batch decision) is an upper bound rather than a to-do count, and a seat can be handed work that is already done. The filer measured one lane: 63 open unassignedpm:queuecards, 38 of them with at least one merged cross-referenced PR and zero open ones, 25 with no linked PR at all. It hit the shape three times in a row in one shift and caught all three only by a rule of its own invention — 「派发前先重读关闭条件」 — which is in no repo text. This PR is acceptance ④ and nothing else: that rule made a repeatable, report-only reading. It judges none of the 38, and it closes nothing anywhere.The row — H67
Population. Open, unassigned
pm:queuecards.h67SpeaksAboutCALLSh66SpeaksAboutrather than restating it (refsOnlyLinksFor's discipline): the two rows read one population from two channels, and a population that drifted apart would let a card be "in the pool, unowned" for one row and not the other with nothing saying so.Tell. The card's own timeline carries at least one MERGED cross-referenced PR (
source.issue.pull_request.merged_atparses) and no OPEN one — the filer's own instrument, taken as-is rather than re-derived, because the card's readings are stated against it. A closed-never-merged PR is a third class: it delivered nothing and it is not an open PR, so it neither fires nor clears, and the row names it.Discriminators, per row, at ZERO extra requests. A
cross-referencedevent carries the whole source issue INCLUDING ITS BODY, andcommentedevents ride the same page, so all three legs read off the page that was already bought:Part of,Refs, a bare mention, or nothing at all. All four grammars are this file's own (closingKeywordTargets/partOfTargets/refsTargets/referencedNumbers), CALLED and never restated, and bound PER CARD NUMBER as H7 binds.H67_CLOSING_CONDITION_ANCHORS), each entry beside the live instance it was measured on, read over the body first and then the thread newest-first. A card that states none gets that said on its row: the hand read then has nothing to check the landing against, which is itself the reading.Claim:or aRelease:newer than the newest merge is a seat that has already looked at this card SINCE the delivery landed, and either stands the row down structurally.Remedy, on every row. Judge by hand, card by card. 「有已合 PR」 is not a closing criterion. And before closing anything, carry the thread's residual readings out into a NEW card first — they are other seats' after-the-fact measurements and closing buries them, which is why the filer's two closures filed #18343 and #18364 first.
⛔
Part ofcards are never listed as candidates for closing (acceptance ③). They are listed, in a bucket whose sentence opens 「⛔ NOT A CANDIDATE FOR CLOSING」, names H49 as the row that owns the shape, quotes the partial-landing rule verbatim, and says what is owed instead: a re-read of the remainder, never a close. The reason the bucket has to speak rather than the row stay silent is measured and is the load-bearing fact of this card: #15815 (work done, closed) and #16529 (a true remainder, correctly kept) carry identical machine evidence — both reached only by a merged PR declaringPart of #N. No bucket may read as "close this", and the pair is pinned as a case.Disjointness, argued on each neighbouring row's own criterion
pm:dispatched; this population ispm:queuewithout it (PM_EXCLUSIVE_STATE_LABELSexcludes the pair, which is H3's row). Disjoint by label, in every input.pm:dispatchedAND a non-empty assignee. Disjoint by both fields. It is the nearest neighbour in substance, which is why thePart of/Refsbucket NAMES H49's rule instead of inventing a second disposition for the same shape.MERGED_WINDOW_DAYS, 8 days) over every open card, so a delivery older than the window is invisible to it — which is exactly the tail this card is about. This row starts from the CARD and reads its whole timeline with no time bound, over the unassigned queue only. In the intersection both fire, with different sentences and different remedies. Neither is narrowed to serve the other.Budget
One timeline page per card, page 1,
per_page=100. The page is ONE for H59's reason: the timeline is served oldest-first and no listing payload carries an event count, so a newest page cannot be located the way H65/H66 locate a newest comment page — and a page that came back FULL may hide a later merge, a later open PR or a laterClaim:, so that card is UNJUDGED and counted, never clean.H67_TIMELINE_READ_CAPis 120 — a little over a tenth of one hour's Actions budget added to a run that happens at most once every six hours. Nothing in this file caches a timeline, so there is no free half of the kind H66 has: every judged card costs its page, and the cap is therefore the row ceiling too.Order: OLDEST-FIRST, the opposite of H66's, deliberately. H66 chases a fresh write because a release is one. Here the evidence is a merge that may be weeks old, so recency says nothing about where to look; what decides is who gets hurt. The order in force is 「队列每轮清空时最新优先;清不空的那一刻起改最老优先」 and the queue has not emptied, so the cards a seat reaches NEXT are exactly the cards this row must have read. The plan sorts by the two legs of 取卡全序 the label page gives for free — priority rank, then card age — and declares what it does not model (
target:board membership, the same-priority 「先Bug」 tiebreak). An unreadablecreated_atsorts LAST, never first.Coverage quadruple on the summary line, unconditionally: candidates / read / listed / not attempted, plus the unjudged count as its own number. A partial read is never published as a zero.
Pins
Battery
H67 queued merged-delivery reading, 151 registered, pinned at 142 (the neighbours' headroom: H66 183/172, H19 37/34).SELF_TEST_BATTERY_FLOORrises 2 to 3 with it. The battery floors a LISTING whose whole value is its discriminators, plus the two negatives the card rules on twice: aPart ofcard must never read as a candidate for closing, and an OPEN PR must stand the row down. Cases include the three instances from the card (#15815-shaped listed with its condition quoted; #16529-shapedPart oflisted under 「⛔ NOT A CANDIDATE FOR CLOSING」 and cleared outright once a release answers the merge; #16565-shaped bare-mention listed under the weakest bucket), the open-PR and no-PR silences, the abandoned-PR class, the three page states, the seven population gates driven whole, the foreign-repo refusal, the relation ladder, the closing-condition reader and the four shapes it structurally refuses, the read plan and its cap, the pass audit, the disjointness pairs, and the quadruple.Ablation — from the committed fix, on-disk proof both legs
The guard removed is the OPEN-PR stand-down,
if ((timeline.open ?? []).length > 0) return null;. Run underscripts/pm/os-verify-lock.shfrom the committed state, with anEXIT INT TERMtrap restoringgit checkout HEAD --against the ABSOLUTE path. There is no build ordist/leg: this script is executed from source, so the on-disk proof IS the whole preflight.The direction is the expected one (red), and the failure is SCOPED: exactly the two cases that assert the guard, and no neighbour's case moved.
Live sweep — report-only, from this worktree, under the verify lock
node scripts/pm/check-half-states.mjsoverobjectstack-ai/objectstack, one run, exit 0, lock held 529s (a shared-box wall clock, not an idle-box figure).Lane breakdown of the 55, joined against a
pm:queuelabel page re-read right after the sweep:domain:spec25, nodomain:label 14,domain:devx12,domain:skills2,domain:cli1, and one card (#12511) that had already left the queue page by the time of the join.The devx reading beside the card's 38 — a reading, not a judgement of those cards. The devx lane's open unassigned
pm:queuepopulation is 53 at the time of this sweep (the card measured 63 on 2026-09-16), and 12 of them are LISTED by this row. ⛔ That 12 is NOT the card's 38 re-measured and must not be read as a correction of it. Three declared reasons it cannot be compared directly:h67ReadOrderover apm:queuepage read after the sweep (222 candidates then) puts 18 of the lane's 53 inside the cap and 35 outside it — so most of the lane is NOT ATTEMPTED, not clean.Claim:orRelease:is newer than the merge, and clears one with an open PR.⛔ Nothing here closes, relabels or proposes a state for any of the 55. Acceptance ①②③⑤ bind whoever closes them, with this reading in hand.
Gates
Derived from this worktree with
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack(no hand-fed path list). The first derivation ran on a tree three commits behindorigin/mainwithscripts/pm/dispatch-gates.mjsitself stale in that range;origin/mainwas merged in and the derivation re-run — the family list is identical (38, none added, none gone) and the tree is no longer stale. Reconciliation:--ranreports 38 derived, 38 run, 0 NOT-MEASURED, 0 UNRUN.Every exit code below was captured with redirect-then-
$?, never across a pipe.The gate union above was run on this branch's final content;
pnpm check:pm-half-statesandpnpm lintwere re-run after theorigin/mainmerge.Two deviations from the dispatch order, both argued rather than taken silently
Part ofcards are LISTED, in a bucket that says 「⛔ NOT A CANDIDATE FOR CLOSING」. The dispatch left the choice open ("state which bucket they land in or that they are named as such"). Listing them is what the filing card asks for — its A bucket is 「必须逐张验的候选集」 and its instance three ([finding] Nothing an app author reads says atype: 'app'package may hold only ONE app — the ui skill andui/audience-based-interfacesboth omit it, andADR-0019is two different records #16565) was a bare-reference card whose work really was done — and the acceptance-③ refusal is met by the bucket's own words rather than by silence.Out of scope, noted and not fixed here
RELEASE_COMMENT_MARKERandCLAIM_COMMENT_MARKERanchor the word at line start and tolerate only a leading blockquote, so a DECORATED ownership line is invisible to them — measured live on objectstack#16529 comment5697022358, which is a real release record H2, H47 and H66 cannot see. That reach is shared with three landed rows and widening it is a different card; this row declares the loss on its own sentence and in its summary clause instead, and a control case pins that the decorated spelling really is refused while the bare one still matches. Filed to the triage seat in this PR's report comment; carrier for whoever picks it up:scripts/pm/check-half-states.mjs, the two marker constants.Generated by Claude Code