Repository navigation
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 |
…eview to the skills seat (objectstack-ai#17806) Fixes objectstack-ai#17490 ## What changed One line of `.claude/skills/pm-dispatch/SKILL.md` (the governed ACCEPT fork, rule ①) is replaced in place. Nothing else moves: same line count, the four-axis frame block untouched, no reference twin, the tiering ruling's own text untouched. | | line 610 | bytes (no newline) | |:--|:--|--:| | before (`a77b4e92e`, identical on the dispatch tip `6682c5d8`) | `- ① 复核结论照常写在 issue 上;技能面 PR 的复核席须跑在契约复审档位。` | 96 | | graded candidate (comment 5624178855, re-measured on the base) | `- ① 复核结论照常写在 issue 上;技能面 PR 须由契约复审档的席复核,档外席交 skills 席。` | 114 | | **after (this PR)** | `- ① 复核结论照常写在 issue 上;技能面 hunk 须由契约复审档的席复核,档外席先交 skills 席。` | **119** | Line cap is 120 bytes (`scripts/pm/check-skill-line-ratchet.mjs`, "Why 120 bytes"); 119 is under it. File stays 812 lines, ceiling 812, headroom 0 — no line added, no rule deleted, no re-wrap, no density payment. ### The three ruled elements, mapped to the words The ruled direction is option 3 on the card (5624178855): the seat that cannot run the tier hands the `skills/**` hunk's review to the skills seat, which records it on the card before the four-piece terminal fires; the lane's own review of the code half stands. | element | words | |:--|:--| | (a) the conclusion still goes on the issue | `复核结论照常写在 issue 上` — unchanged from the old line | | (b) the review of the `skills/**` hunk runs at the contract-review tier | `技能面 hunk 须由契约复审档的席复核` — the skills-face hunk must be reviewed by a seat AT the contract-review tier | | (c) a seat outside that tier hands that review to the skills seat | `档外席先交 skills 席` — an out-of-tier seat hands it to the skills seat first | Two wording changes against the graded 114 B candidate, both inside the ruled direction and inside the cap: - `PR` → `hunk` (+2 B). The ruling hands over the **hunk's** review and keeps the lane's review of the code half; with `PR` the sentence read as handing the whole PR review across seats, which contradicts that second clause. `hunk` is already vocabulary in this skill (`references/dispatch-runbook.md` lines 101 and 151). For a PR that is entirely `skills/**`, hunk = PR, so nothing narrows. - `交` → `先交` (+3 B). The ruling says the handed-off review is recorded on the card **before the four-piece fires**; `先` carries that ordering, closing the failure mode where a seat fires the four-piece and hands off afterwards, leaving the PR at the maintainer with no at-tier read. Kept from the candidate: `技能面` (the term the old line and line 600's `触 skills/** 的 PR` pairing already use), `契约复审档`, `skills 席`. ### core-rules twin check `git grep -n '契约复审档位' origin/main -- .claude/skills/pm-dispatch/references/core-rules.md` ⇒ exit 1 (no hit). A wider probe `git grep -n '复核席\|技能面 PR' origin/main -- .claude/skills/pm-dispatch/references/core-rules.md` ⇒ exit 1 (no hit). The only tier sentence in core-rules (line 112, `语义面卡恒契约复审档施工,契约卡达档复核归 spec 席`) is about the build tier and the spec seat's clause-② review, not the governed fork's rule ① — not a twin. `references/core-rules.md` is untouched. The phrase `契约复审档位` also appears in `references/contract-review.md` line 57 (`产出裁决的每轮都须读到契约复审档位`) — that is the contract-review reading's own tier requirement, a different rule; untouched. ### Premise check (all three PM readings held) 1. Line 610 on `6682c5d8` and on the actual base `a77b4e92e` (origin/main moved by one commit between dispatch and branch cut — PR objectstack-ai#17794, which touches only `references/dispatch-runbook.md`; `git diff --stat 6682c5d HEAD -- SKILL.md` is empty) is the 96 B sentence above, 97 B with its newline. Ratchet verdict on the base: `SKILL.md is 812 lines (ceiling 812; headroom 0)`. No open PR touches SKILL.md or core-rules.md: the 30 most recently updated open PRs (newest at 06:19Z, i.e. none after the PM's 06:29Z reading) were listed via REST; the two pm/ci PRs in that list (objectstack-ai#17799, objectstack-ai#17801) were file-listed and touch `scripts/pm/check-clause2-carriers.mjs` and workflow/script files only. 2. The interim was already executed at tier by the previous skills seat on card 17134 (PR objectstack-ai#17462); that card was read only, not touched. 3. `node scripts/pm/dispatch-gates.mjs --tier .claude/skills/pm-dispatch/SKILL.md` prints `Model tier — MANDATORY: claude-fable-5-1`. This PR was authored at that tier (claude-fable-5-1). ### Governed verdict `node scripts/pm/check-governed-merges.mjs --test .claude/skills/pm-dispatch/SKILL.md` ⇒ exit 3: `governed-surface predicate: 1 of 1 path(s) hit the register (5 surfaces, repo-agnostic).` / `⛔ GOVERNED — a human merge is the review record for this PR`. ⇒ draft at the human terminal; no ready flip, no queue, no auto-merge from any seat. `skip-changeset` applied additively (`.claude/**` publishes nothing). ## Verification (final commit `8b7dae6c0`) Gate set derived, not recalled: `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` (change set derived three-dot from the merge base `a77b4e92e`, 1 path) ⇒ 16 commands. All 16 run on `8b7dae6c0` with exit captured before any pipe (`cmd > log 2>&1; ex=$?`), all exit 0; reconciliation `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran FILE` ⇒ `Run reconciliation — 16 derived, 16 run, 0 NOT-MEASURED, 0 UNRUN.` The same 16 ran once before the commit on the identical bytes; the command list was byte-identical pre/post commit. Verdict lines as printed by the gates on `8b7dae6c0`: - `✓ check-skill-line-ratchet: .claude/skills/pm-dispatch/SKILL.md is 812 lines (ceiling 812; headroom 0).` - `✓ check-skill-id-lint: 27 file(s) clean (pattern /#[0-9]{3,}/g).` - `✓ check-skill-frame-sync: the one declared copy of the decision frame is internally coherent (.claude/skills/pm-dispatch/SKILL.md; no second copy to compare — …)` — plus the direct proof the block did not move: `sed -n 734,755p SKILL.md | md5sum` ⇒ `3327d02c56f8a0eca88569dad2270f32` before and after the edit (the dispatch's expected hash). - `✓ check-governed-prose: 2 instruction surface(s) name all 5 registered governed surfaces (…) and claim no others.` - `check-nul-bytes: OK (scanned 8456 text file(s) … no raw ASCII control bytes).` — plus the manual control-byte self-scan on SKILL.md (`grep -naP` over the C0/DEL range) ⇒ no hits. - `pnpm check:pm-governed-merges`, `check-governed-queue-guard --self-test`, `check-closing-keyword-parity` (+ `--self-test`), `check-comment-mask-corpus`, `check:agent-test-spelling`, `check:doc-authoring`, `check:driver-memory-census`, `check:refd-timer-probe`, `check:watch-hint-literal` — each printed its own ✓/OK line, exit 0. - `pnpm --filter @objectstack/lint run check:doc-formula-expressions`: first run exit 3 `PREREQUISITE NOT MET` (`@objectstack/formula` / `@objectstack/lint` not built — NOT MEASURED, not a finding). Built both under the shared verify lock (`os-verify-lock.sh`, `VERDICT command-exit 0`, held 182 s), re-run ⇒ `✓ check:doc-formula-expressions: 22 record-scoped formula example(s) across 438 files / 1374 TS blocks judged clean`, exit 0; included in the post-commit union above. On-disk proof of the edit: `grep -cF` of the old line 1 → 0 and of the new line 0 → 1; `git show HEAD:…SKILL.md | sed -n 610p` prints the new line. No repo-wide `pnpm lint` run locally (CI-owned); no package build/test owed — the diff touches no package (no ① closure, no ②). Not measured locally: CI convergence (owned by the review side). ## Acceptance notes - noted, not filed: `references/contract-review.md` line 57 uses the spelling `契约复审档位` for the contract-review reading's own tier, while SKILL.md line 610 now spells the same tier `契约复审档` (as lines 505–527 do). Both spellings already coexisted on the tip; no gate reads either. Carrier: whoever next edits contract-review.md; none queued. - Out of scope, untouched by design: the tiering ruling's text (card 17285 / PR 17294 — those cards remain as they are), card 17134's labels, the four-axis block (lines 734–755). ## 维护者速读(草稿) **改了什么**:`.claude/skills/pm-dispatch/SKILL.md` 第 610 行(治理面 ACCEPT 分叉的规则①)原地换了一句话,96 字节变 119 字节,行数 812 不变。旧句要求「技能面 PR 的复核席须跑在契约复审档位」;新句改为「技能面 hunk 须由契约复审档的席复核,档外席先交 skills 席」。 **为什么改**:旧规则①与分档裁决(契约复审档只留给 skills 席、spec 席的条款②复核与维护者召集的 director)构成一对谁都满足不了的矛盾:一个 `domain:cli` 席复核一张顺手修了 `skills/**` 里一行的 PR,被要求跑一个它被禁止跑的档位。这不是边角:「已发布的说法本轮变假就地修、不立卡」这条规则天然会让普通车道的 PR 顺带碰到 `skills/**`。一张 p1(PR objectstack-ai#17462)已经撞上。方向由 skills 席自分诊定为选项 3(5624178855,轮次报告否决窗已过):档外席把 `skills/**` 那一块的复核交给 skills 席,skills 席在四件套之前记到卡上;代码那一半仍由本车道自己复核。 **风险与代价(含回滚)**:每次顺带的 skills 修补多一次跨席交接 —— 几行 hunk、一条评论,不是一张卡。文字层面只动这一行,分档裁决原文、四轴框架块、objectstack-ai#17134 的标签都没碰;`references/core-rules.md` 无孪生句(已 grep 核实),未动。回滚 = revert 这一个 commit,不牵连任何生成物。 **席位意见**:(留空,席位定稿时填) **你要做的**:读第 610 行这一句,认可则人工直合;不认可则在此 PR 评论里写下你要的措辞,由 skills 席改。 --- _Generated by [Claude Code](https://claude.ai/code/session_01MCLBsUgfykL74aU716rzVK)_ Co-authored-by: Claude <noreply@anthropic.com>
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