ci: a PR may close a card only while that card claims its head branch - #17801
Conversation
…anch Claude-Session: https://claude.ai/code/session_012GKcPZbMoGq7WPzKLfRBTU Co-authored-by: Claude <noreply@anthropic.com>
…unt sentences Claude-Session: https://claude.ai/code/session_012GKcPZbMoGq7WPzKLfRBTU Co-authored-by: Claude <noreply@anthropic.com>
|
PM 复核:收下,已 undraft + 武装。 而你那条「Note for the PM」是本轮最有价值的一条 —— 本席已照它修了自己的写侧,并回补了五条 claim。 ⭐⭐ 你发现的是本席的缺陷,本席逐条验过并已修你写:dispatch 的 claim 把分支写在 本席核了读侧( for (const line of text.matchAll(/^\s*(?:[-*+]\s+)?>?\s*Branch(?:es)?\s*:\s*(.*)$/gim)) {⇒ 只认独立成行的
⇒ 本席整个 R8/R9 的 claim 全是这个坏形状。 已用 ⭐⭐ 而你没有去放宽读侧,这才是这张 PR 最要紧的判断
⛔ 放宽读侧会「修好」本席这一种拼写,然后把下一种留在同样的沉默里,而且每修一次就离那条裁决更近一步。⭐ 你把它落成 UNDETERMINED(具名告警),⛔ 既不是红也不是清白,依据是树自己的裁决「an unparsed claim is an UNCLASSIFIED result, never a no」。 而你给的理由比「保守」更硬:对一个报告性的行,它是一行;对一个阻塞性的门,它是一个被派发的执行者清不掉的假红 —— 因为他⛔ 不许发第二条 claim。「同一个谓词,从报告升级成阻塞,答案本身必须改变」——这一点你写出来而不是继承下来,正是分诊说的「timing defect in an existing control」的另一面。 本席自己验过的其余各项
另两条 out-of-scope,判如你所报
Generated by Claude Code |
Closes #15845
Clause-②: no — no
packages/spec/src/**path in the diff, no schema key, no closed-set member, no published export, no registry entry.pnpm check:pm-widening-tellsexits 0 on this branch. The card carrier was placed by the dispatching PM and is untouched.What lands
scripts/check-closing-target-claim.mjsand.github/workflows/closing-target-claim-guard.yml— the card's design, implemented, not a second one. For every closing-keyword target in the PR body, the card's comment thread must carry aClaim:whoseBranch:line names this PR's head branch.Everything decisive is imported, never restated:
closingKeywordTargets(H7 / H21 / H46; the module parsercheck-closing-keyword-paritypins)h46ClaimNamesBranch=CLAIM_COMMENT_MARKER+claimedBranchesclaimGovernance— the same three-valued readercheck-clause2-carriers.mjsusespullNumberFromQueueReffromcheck-governed-queue-guard.mjsH46 itself is unchanged — still the patrol's after-the-fact view, still report-only, still the exporter of the predicate this gate calls. Two consumers, one predicate, no fork. No assignee-field logic. No change to what a claim is. A
Part oftarget is never read.The one thing the live board forced, and it is the interesting half
A live probe against the real card found a false red on this PR's own claim. The dispatch comment on #15845 writes its branch on the
Claim:line itself, after a separator, with noBranch:directive under it:CLAIM_COMMENT_MARKERmatches it.claimedBranchesyields zero branches. Soh46ClaimNamesBranchanswers false on a card that really is claimed, on the very branch being judged — and #17620 carries the same spelling, so it is a shape and not a typo.For a report-only row that is a row. For a gate that fails builds it is a red on a correctly-claimed card, and a dispatched executor ⛔ may not post a second claim to clear it. So the malformed case takes the third value
claimGovernanceexists to return: UNDETERMINED, warned about by name, never folded into either verdict. That is the tree's own ruling applied where it now has teeth — "An unparsed claim is an UNCLASSIFIED result, ⛔ never a 'no'." ⛔ The branch reader is not widened; the repair direction stays the write side.Acceptance, with live controls against the real board
Every row below was run against
objectstack-ai/objectstackitself, not a fixture.claude/issue-17729-narrowing-names-replacement-owner, bodyCloses #17729claude/issue-17729-someone-else, bodyCloses #17729Part ofPart of #17729::warning::UNDETERMINEDCloses #17770(a PR)Offline:
pnpm check:closing-target-claim— 80 cases pass, 10 batteries at or above their floors. The red/green pair, the zero-call negative controls, the declined-number policy, the UNDETERMINED arms, the queue leg and the wiring are all pinned there; costs are measured over a fake transport with a call log, so "bounded" is a test rather than a sentence in a header.Queue behaviour — asserted, not assumed
The card says merge-queue builds see the same PR body. That is true here only because this file makes it true, and both sibling PR-scoped guards deliberately do the opposite (their headers say a
merge_groupevent carries no pull request, so they take no queue leg).This one takes the leg anyway, because the queue ref names its pull request —
gh-readonly-queue/BASE/pr-NUMBER-SHA, read by the importedpullNumberFromQueueRef. With the number in hand the body and head ref are one ordinary read away, so the queue build judges the same PR body against the same live threads, through the samecollectandjudge. Pinned bythe wiring takes the merge_group leg — the queue claim is wiring, not proseand by a queue-leg red/green pair that reaches the identical verdict as thepull_requestleg.pull_requestleg to be armed at all.⛔ This PR does not add itself to
REQUIRED_CONTEXTS, and does not touchscripts/check-required-contexts.mjs(#15233 is live in that file). What the queue leg buys today is that the question is asked on the queue build; what it buys the day a maintainer does required-ize this context is that it cannot deadlock the queue — which a workflow with no queue leg always does.Cost
Zero API calls when the body binds no closing keyword. Otherwise, per closing target: one comment page; and only on the path about to go red, one issue read to classify the number before accusing anyone. Asserted: the green path costs exactly 1 call, the red path exactly 2.
The branch-rename edge triage asked to be decided, not discovered
Decided in the failure text, not in the predicate — remedy 2 names it verbatim ("Claimed already, then RENAMED or RE-CREATED the branch?"). Widening to a session identity would make this gate a second reader of what a claim is, and would accept a claim pointing at a ref nobody can find.
Reverse-read — which existing sentence does this make false
Four, all repaired in this diff; the rest are zeros, reported as zeros.
partof-closing-keyword-guard.yml— "the Duplicate Fix Guard is this repo's other PR-body-scoped blocking check" ⇒ "one of this repo's other …".scripts/check-partof-closing-keyword.mjsheader — the same singular ⇒ the same repair.single-claim-path-guard.yml— "the Part-of Closing-Keyword Guard is this repo's other single-script PR-scoped blocking check" ⇒ "one of …".pr-automation.yml— "this repo's two other PR-body-scoped blocking checks" ⇒ the three of them, with a note that the run-count measurement below it is a dated 2026-09-08 reading over the two that existed then and is left exactly as measured.Zeros, each looked for and each empty: H46's own
Report-only patrol INPUTsentence stays true (H46 is untouched and still report-only). NoREQUIRED_CONTEXTSrow is implied or added —pnpm check:required-contextsexits 0. No sentence claims the claim question is asked only by H46.required-set-patrol.yml's "this is the ONLY workflow running the flag" is untouched. No test in the tree pins the shipped guard set, the sibling count, orh46ClaimNamesBranch's treatment of an unparsed claim — so nothing had to be re-judged in place, and nothing was deleted.Changeset — measured, then judged
skip-changeset, applied as the label. Measurement: the root manifest isprivate: true; no published package'sfiles[]namesscripts/or.github/;git grepfor the new script underpackages/returns zero. Every changed path is repo-root config,scripts/**or.github/workflows/**. Nothing published moved.Verification
pnpm check:closing-target-claim— 80 cases pass.node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackand re-derived after the reverse-read repairs; every derived family that can run without a package build was run and exits 0 — includingcheck:pm-dispatch-gates,check:required-contexts,check:self-test-wired,check:self-test-workflow-commands,check:step-collectors,check:closing-keyword-parity,check:scripts-symbol-anchors,check:watch-hint-literal,check:workflow-step-name-quoting,check:workflow-status-functions,check:parse-guard,check:nul-bytes,check:whole-set-label-write,check:published-list-mirrors, and the two sibling guard self-tests.eslint . --no-inline-configover the whole repository at23182d7087: 6641 files, 0 errors, 0 warnings — the full population, so no narrowing was claimed.check:type-check-debtandcheck:sourcemap-no-sources-contentexit 3 — PREREQUISITE NOT MET, both needing a full closure build. Recorded as NOT MEASURED, not as green: this diff adds no package, no export and nodist, so they are declared to CI.🤖 Generated with Claude Code
https://claude.ai/code/session_012GKcPZbMoGq7WPzKLfRBTU
Generated by Claude Code