fix(pm): a review of record on the card thread counts at the queue, and one constant holds both tools to it - #18738
Merged
Conversation
…d too The governed text puts the review of record 「一条评论落 PR 或卡」; the queue guard's references tier read the PR thread alone. Both tools now run one reader over one thread set, derived from a single exported constant. Claude-Session: https://claude.ai/code/session_01Gqi43smmqjJ5sUrhfoPeKu Co-authored-by: Claude <noreply@anthropic.com>
The cross-tool pin drives `check-governed-queue-guard.mjs` once per entry in `REVIEW_OF_RECORD_THREADS`, with a record copied from this file's own `--template` sitting on that thread alone, so editing either side by itself reds. Adds the guard-side card-thread battery and the card derivation cases. Claude-Session: https://claude.ai/code/session_01Gqi43smmqjJ5sUrhfoPeKu Co-authored-by: Claude <noreply@anthropic.com>
This was referenced Sep 17, 2026
Merged
os-justin
marked this pull request as ready for review
September 17, 2026 18:05
This was referenced Sep 17, 2026
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 #18701
Clause-②: no
The defect
check-clause2-carriers.mjs --templateprints, and has always printed, that a review of record isONE comment on the PR or its card; C6/C7 judge it on both threads throughlocateReviewOfRecord; the governed text says the same in four places. The Governed Surface Queue Guard's REFERENCES tier read the pull request's thread alone. The two tools already shared every recogniser — the heading marker, the head-sha span test, the newest-of resolution, theReviewed-by:key line, theServed-tier:reader — and the one thing they did not share was the set of threads to run them over, which is the only fact a spelled-out copy of the filter could not copy. Measured cost, on one pull request in one afternoon: the skills seat posted its## Contract reviewon carrier card #18426 (comment5716694216, 15:10:43Z),--pair 18689read it as C6-RECORD on thecard thread, themerge_groupleg (run35238999808) refused with0 comment(s) read on the PR thread, PR #18689 was dequeuedCI_FAILUREat 15:36:18Z, and only a second copy of the same comment on the PR thread (5717108355) cured it.The canonical sentence — the governed text is not silent
Read on
origin/main(080688b561, the branch base). Quoted verbatim and untranslated, because rewriting a quoted ruling rewrites the ruling:.claude/skills/pm-dispatch/references/contract-review.md:27 — 「复核记录 = 一条评论落 PR 或卡,达档与默认档同形;散文、dev 自评、os-dev-report恒不算。」.claude/skills/pm-dispatch/SKILL.md〈入队与落地〉 :640 — 「交付后复核归派发席:达档席内审,未达档循保险丝起子代理;记录 = 同形评论落 PR 或卡。」(The claim comment's aside that
references/contract-review.mdcarries no such sentence is the one premise this card's dispatch got wrong; :27 is that sentence, and it is onmain. Nothing else in the premise moved — the guard, the template, the docblocks and the #18689 evidence all read as the card describes them.)Which tool disagrees with it:
scripts/pm/check-governed-queue-guard.mjs. So the GUARD moves, all of it. No governed file is touched by this PR.Why the guard and not the template — the four axes
--template. Both carriers are live on every pair — the clause-② declaration is hung on both,--pairjudges both, the carrier label is stripped from both — andcheck-clause2-carriershas read both threads since finding(pm-dispatch): after #17285, default-tier lanes have no named "review of record" — fiveClause-②: yesmerges in one window carry none, and clearing the carrier is indistinguishable from never reviewing #17302. The population that would be served by moving the template instead is empty: nobody is asking for the card to stop counting.No axis conflicts with another. A fifth, decisive fact: the template side cannot move from here at all — its sentences live in
.claude/skills/pm-dispatch/**, off this card's file surface and governed.The shared constant, and one derivation of "the record on this head"
check-clause2-carriers.mjs— the file that owns the reader — now declares the thread set once:Three consumers, all derived from it and none spelling a set of its own:
locateReviewOfRecordbuilds the rows it searches, its gap sentences and its per-thread read counts from the list.--template's and--pair's output is byte-identical tomain's.contractReviewTemplateLinesbuilds its printed sentence fromREVIEW_OF_RECORD_LOCATION, so an instruction can never offer a thread no reader searches.check-governed-queue-guard.mjsfetches one thread per entry in the loaded list — and its whole record reading is nowlocateReviewOfRecord, imported through the existing lazy loader rather than re-spelled. The guard's own two-line filter is gone; what is left there is the tier gate on top of the located record (Served-tier:stands, and the [finding] The mandatedServed-tier:top line puts a model identifier in a GitHub comment, which AGENTS.md:442 forbids in terms — and a p1 guard change would cement it into a gate #18060 model-identifier refusal), which is that leg's own fact.The card the record may live on is derived, never configured:
deliveredCardNumber(pull)enumerates the numbers the body or branch could be naming and asksdeliveryEvidence— the one relationderivePairs, H8 and H31 already pair a PR with its card by — so it can never accept a card that relation rejects. A pull request naming no card, or two cards at equal strength, searches NO card thread and the refusal says so: the refusing direction, the only one a governed reading may be wrong in. The card's number and the head sha come from ONEGET /repos/{owner}/{repo}/pulls/{number}— the request the head read already made — so the card thread costs no extra call for the number, and the guard keeps its zero-write, event-payload-only posture with no checkout.REVIEW_OF_RECORD_LOCATIONis mirrored at the guard's module scope for the renderer only (renderGuardVerdictis pure and synchronous, and a module-scope import of the sibling is the measured exit-13 deadlock its own docblock records). The SET the leg acts on is never a mirror — it is the loaded list — and the mirror's words are pinned to the constant in the guard's battery.The pin
check-clause2-carriers.mjsowns the reader, so the pin lives in its self-test — and it is a measurement, not two lists written side by side:contractReviewTemplateLines()actually prints.check-governed-queue-guard.mjs'srunGuardonce per entry inREVIEW_OF_RECORD_THREADS, with a record built from this file's owncontractReviewRecordLines()sitting on that thread alone, and collecting the threads whose run answersexitCode === EXIT_CLEARwithrecord.state === 'stands'.The case asserts
stated === read === the constant's own set, plus its own controls: the set has two locations (not vacuous); each run located the record on the thread it was posted to; the guard fetched exactly one thread per entry, no more and no fewer; and a run with the record on NEITHER thread is refused, so the pin can fail.selfTest()becameasyncto take that lazy import — the dispatch uses.then(...), never a top-levelawait.Counterfactual — the #18689 dequeue, offline
One fixture, driven through
runGuardwith no network and no git: a references-tier pair whose record sits only on the card thread. The same script runs against either generation (it passes both the oldfetchPullHeadand the newfetchPull).record.staterecord.where080688b561)3REFUSEDrefusedabsent0clearedstandscardBefore, rendered:
⛔ NO review of record on this head either (1 comment(s) read on the PR thread)— the #18689 sentence, reproduced.After, rendered:
✅ review of record on this head: comment 5716694216 (2026-09-16T15:10:43Z) on the card thread is a ## Contract review comment naming ...Ablation
Two legs, each: on-disk proof of the mutation (
grep -cof the removed text and of the injected text, plus thegit hash-objectblob before and after), then the self-tests, then restore, then a byte-identity check against the HEAD blob. Both scripts carrytrap restore EXIT INT TERMwith an absolute path. No build ordist/is involved — these two files are run directly by node and bypackage.json, so the on-disk proof is the whole preflight.Leg A — the loop that unifies the thread set (
for (const thread of recognisers.threads)narrowed to the first entry). Blob9a111f6c→b57227d0; removed-text count 1 → 0, injected-text count 0 → 1. Cross-tool pin: 4 of 802 failed. Guard battery: 25 of 253 failed — the loop is load-bearing for the whole references leg, not only for the card.Leg B — the line that supplies the card's number (
card: card.card→card: null), which reproduces the pre-#18701 thread set exactly. Blob9a111f6c→9d935d92; counts 1 → 0 and 0 → 1. This is the clean reading the card asks for:check-clause2-carriers3 of 802 failed, all three the new battery —stated=[PR,card] read=[PR], the card run locating nothing, and the fetch list[[4101],[4101]]instead of[[4101,4102],[4101,4102]].check-governed-queue-guard5 of 253 failed, all five in the#18701battery (record on the card alone, both threads fetched, the rendering naming the thread, newest-governs across threads, the branch-name fallback).#18020included, passes under the ablation.Restore, both legs: worktree blob
9a111f6c…equals the HEAD blob,git diff HEADempty,git status --porcelainempty, and both self-tests back to exit 0.Self-tests
node scripts/pm/check-governed-queue-guard.mjs --self-test⭐ #18701: the record lives on the PR or its card, and BOTH are read(14, floor 14);SELF_TEST_BATTERY_FLOOR20 → 21pnpm check:pm-clause2-carriers#18701: ONE thread set -- what the template STATES is what the queue guard READS(16, floor 16);SELF_TEST_BATTERY_FLOOR27 → 28Both exit 0.
--template,--helpand the no-event refusal path (exit 1, its own message) all still answer.Gates
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived from the worktree with no hand-fed path list (change set: the two scripts, 2 committed / 0 working tree / 0 untracked, three-dot against merge base080688b56). 36 derived, 36 run, every one exit 0, reconciled with--ranafter a freshgit fetch origin main—✓ dispatch-gates --ran: 36 derived famil(ies) accounted for — 36 run, 0 NOT-MEASURED (a DERIVED zero — all 36 recorded an exit code and none of them is 3).Repo-wide
pnpm lint(eslint . --no-inline-config, the whole tree, as PR #18654 did) :: exit 0, no output. Every exit code above was captured with redirect-then-$?, never across a pipe.origin/mainmoved to62bce5c297while this ran; its three commits touchpackages/**and.changeset/**only — nothing underscripts/**and nothing this diff reads — so no merge was taken and the derivation above is unaffected.Scope
Two files,
scripts/pm/check-governed-queue-guard.mjsandscripts/pm/check-clause2-carriers.mjs. No governed file.skip-changeset: nothing published moves —scripts/pm/**is not in any package'sfiles[].readCardComments's paged sibling, not on the un-paged path #18683 names. The guard's ownmakeCommentReaderis paginated (100 per page, 10 pages, and it THROWS rather than truncating), and it is what serves both threads here —GET /repos/{owner}/{repo}/issues/{number}/commentsis the same route for a pull request and for a card. #18683 remains exactly as it was, oncheck-clause2-carriers' own reader, untouched by this PR.Not in scope, as dispatched:
check-clause2-carriers' un-pagedreadCardComments(#18683 — read, not fixed); #18536's machine-side lane/tier re-keying; the REFERENCES-tier rule itself (which paths qualify), untouched.Acceptance notes
deliveryEvidence's ranking had no exported ordering, soDELIVERY_EVIDENCE_PRECEDENCEis declared incheck-clause2-carriers.mjsas a MIRROR and measured against that function pair by pair in the self-test rather than trusted. Ifcheck-half-states.mjsever wants to own it, that is a one-line move; noted, not filed — no PR or person reaches for a ranking that today has exactly one caller.check-half-states.mjsdirectly: every recogniser it reads now arrives through the file that composes them, and a rename upstream stops THAT module loading, which the leg already turns into a fail-closed refusal with the loader's own reason.RECOGNISER_SOURCESis down to one entry and the self-test pins that. Keeping the old direct load would have been a check on something this file no longer reads.Generated by Claude Code