Skip to content

fix(pm): the claim HANDOVER protocol — one comment, four items, provenance instead of a liveness test; the reader accepts it and C9 keeps one red - #19502

Merged
os-steve merged 5 commits into
mainfrom
claude/issue-19240-claim-handover-protocol
Sep 21, 2026
Merged

os-steve merged 5 commits into
mainfrom
claude/issue-19240-claim-handover-protocol

Conversation

@os-steve

Copy link
Copy Markdown
Collaborator

Fixes #19240
Clause-②: yes

Clause-②: yes — the claim reader's accept set widens (a cross-login Release: carrying provenance now retracts) and C9's judged set narrows to a bare cross-login Claim:; a .claude/** surface ⇒ Tier S, the seat lands it on its ## Contract review PASS + --pair 0. This PR stays draft.

What lands — ruling 5754797404, shape A, executed as ruled

The claim HANDOVER protocol. A card whose claimant is unreachable (token exhausted, session ended, identity retired) is taken over by a new session in ONE comment, and the claim reader accepts that comment — no liveness heuristic anywhere: the human's word, copied with provenance, is the permission.

# Surface Change Net lines
1 scripts/pm/check-clause2-carriers.mjs claimRetractions gains the HANDOVER arm; CLAIM_RETRACTION_RULE, CLAIM_HANDOVER_RULE, CLAIM_HANDOVER_REMEDY rewritten; C9 keeps one red and lists refused handover attempts; self-tests both sides (1075 → 1091 cases) +174 / −54 = +120 (the claim's budget, exactly)
2 .claude/skills/pm-dispatch/SKILL.md :177 · :472 · :492 aligned in place; the nine liveness-heuristic bullets (:493–:501 at base) replaced by five handover bullets 813 → 809 (net −4; ceiling 813, headroom 4)
3 .claude/skills/pm-dispatch/references/core-rules.md the two twins (:110, :111) rewritten in place 151 → 151 (net 0)
4 .claude/agents/os-dev.md :94 in place: every compilable step is pushed; a handover reads the remote branch's last sha 403 → 403 (net 0)
5 scripts/pm/check-half-states.mjs untouched — measured: grep -n 'author !== ' → 0 hits; its Release: readers (H47 latestMarkedComment / releaseAnswersClaim) compare comment ORDER, never authors, so it carries no copy of the retraction rule and imports nothing from the clause-② reader 0

Base 32b5831, origin/main merged once at d00692f (PR #19462 had not landed at 2026-09-21T04:1xZ — SKILL.md ceiling stays 813, no region overlap). Every line ≤ 120 bytes; check:pm-skill-ratchet, check:pm-skill-id-lint, check:pm-governed-prose, check:agent-model-declared, check:nul-bytes all exit 0 on the edited files.

1. The reader

The HANDOVER arm of claimRetractions (the one accept-set widening)

A Release: comment by a different login retracts an earlier claim when, and only when:

  • (a) its Release: line (the first line of the body that markerMatches(RELEASE_COMMENT_MARKER, line) reads — the sibling's ONE reading, applied per line, so **Release:** and `Release:` read and - Release: does not) names the retracted claim's comment id (digit-bounded) and its session id (token-bounded; the claim's Session: line first, else the first session_… token in the claim body — a claim with none cannot be named, fail closed);
  • (b) the comment carries the three provenance fields of SKILL.md's 出处三件 line 「代执行他人指令的关闭、摘标、回收认领,评论带出处三件:谁的指令、原话、在哪说。」, each with a non-empty value.

Missing any one piece ⇒ NOT a retraction, state unchanged. ⛔ No liveness test: the earlier claimant's later comments are irrelevant (pinned). Same-login retractions: byte-for-byte the old behaviour (no id, no session, no provenance needed).

Pinned key spellings (HANDOVER_PROVENANCE_KEYS = ['谁的指令', '原话', '在哪说'], exactly the :149 vocabulary — ⛔ no fourth key, ⛔ no synonym). A field is: the key · optional decoration (*, _, backticks) · an optional parenthetical (…) / (…) · a colon (ASCII : or fullwidth : — indistinguishable on the page, pinned equal) · the value = the rest of that line up to the next key, or, when that is blank, the blockquote (> lines) under the key. Whitespace, >, decoration and separator punctuation alone are an EMPTY value (pinned per key).

The two live specimens, both replayed verbatim in the self-test:

  • 5754797404 (inline paragraph): **出处三件** — **谁的指令**:维护者,在本席(…)会话内的三个真实用户轮次。**在哪说**:本席会话聊天,在评论 5754717208(2026-09-21T02:44Z)之后、本条之前的连续三轮。**原话**(逐字,⛔ 未翻译、未润色): followed by the blockquoted turns.
  • 5754717208 (line per field): **出处三件**—— / **谁的指令**:维护者(本仓 maintainer,…)。 / **在哪说**:本会话聊天内,…。 / **原话**(逐字,⛔ 未翻译、未润色): followed by the blockquoted turns.

C9 keeps exactly one red

claimHandovers is unchanged in its walk: it reads the LIVE claims through the same claimRetractions map, so a handover comment (① provenance Release: naming the holder's claim + ③ new Claim: with Branch:/Clause-②: in the SAME comment) leaves one author holding ⇒ no row, no note, and the new Claim: is the governing claim on the --pair path (a comment is not later than itself, so it cannot retract its own claim — pinned). The one red left: a cross-login Claim: with NO Release: at all for the earlier claim — a real claim-jump. CROSS_AUTHOR_CLAIM_ROW_EFFECTIVE_AT stays; its gating now applies to that narrowed red only (it is read at the same place as before).

Loud refusal, not silent red: a cross-login Release: that TRIED to hand over a live claim (names its id or session id, or carries a provenance field) and did not is listed in the C9 sentence with its reason — missing 在哪说, the comment id is not on its Release: line, the session id is not on its Release: line, the claim carries no session id to name. A bare Release: by another login (a seat releasing its own claim) is not an attempt and is not listed — the first draft listed those and the --pair 19373 row named os-steve's own two releases as "refused handovers" of os-bill's claim, which was noise; narrowed.

The remedy sentence (CLAIM_HANDOVER_REMEDY) prescribes the four-item handover comment and prints SKILL.md's handover sentence verbatim (CLAIM_HANDOVER_SENTENCE_LINES = the five 认领 bullets, byte for byte), citing the 出处三件 line as its source. The old remedy words 「the HOLDER posts Release: … the TAKER posts nothing until then … ⛔ never a Release: on the holder's behalf」 are gone; ② (assignee swap) and ④ (the sha record) are stated as the seat's acts, unread by the reader.

Self-tests (beside the existing retraction and C9 cases, ⛔ not at selfTest()'s tail; floor unchanged)

Retraction battery: ⭐ a cross-login provenance Release: naming id + session is accepted — state declared, the handover's own Claim: governs, the record says "a DIFFERENT login … HANDOVER" · ⛔ missing any one field, or a key with an empty value ⇒ refused, one case per key each way, the missing key named · ⛔ id without session / session without id / both in prose under a bare Release: line / the three fields with no Release: line at all ⇒ refused · ⭐ NO liveness test: the earlier claimant commenting after the handover changes nothing · ⭐ both live specimens' spellings read, and a fullwidth colon reads as the ASCII one · ⛔ same-login Release: still needs nothing (arm untouched); a claim with no session id cannot be handed over · ⛔ item ④ absent still retracts (the seat's act, not the reader's gate) · the printed rule names both arms, the three keys, the source line and the absent liveness test.

C9 battery: ⭐ the handover comment clears C9 (no row, no note) · the handover's Claim: is the governing claim on --pair (branch, declaration) and is not self-retracted · ⛔ the same comment missing any one field ⇒ still C9 JUDGED, the row names the refused release and the missing key · ⛔ the ONE red kept: a cross-login Claim: with no Release: at all · ⛔ a handover naming only one of two live claims leaves the other standing · the remedy is SKILL.md's handover sentence verbatim, with the 出处三件 source line and 让先到者 for a yield · each sentence line is one SKILL.md bullet by shape (≤ 120 bytes, no bullet, no issue id).

2. Before / after — every changed instruction line

.claude/skills/pm-dispatch/SKILL.md

line (base → now) before after
:177 → :177 - dev 自己死了不等于维护者中止:子代理消失是正常死法,走死认领回收。 - dev 自己死了不等于维护者中止:子代理消失是正常死法,走接管(见认领节)。
:472 → :472 - 共享身份下 assignee 只答有无认领;身份只认正文 session ID,⛔ 不认作者字段。 - 共享身份下 assignee 只答有无认领;身份只认正文 session ID,⛔ 不认作者字段,接管同此。
:474 - 释放是显式动作:让卡离手者同笔清 assignee + Release: 行(会话/因/去向);下一任重新认领。 unchanged, deliberately — this line is the greppable source of RELEASE_ACT_RULE in check-half-states.mjs (outside this claim's surface); the handover reuses the act's two halves (② assignee + ① Release: line, by the taker), stated in the new bullets
:492 → :492 - dev 侧早推分支,远程分支是在飞工作最硬的证据。 - dev 每个可编译小步即 push:容器随会话回收,未 push 的树救不回,可交接的只有远程分支。
:493–:501 → :493–:497 the nine liveness bullets (listed in §3) - 认领人不可达(token 耗尽/会话结束/身份退役)⇒ 接管:一条评论四件齐,⛔ 不判死活。 / - ① 跨账号 Release: 点名被撤认领的 id 与 session ID,带出处三件(谁的指令/原话/在哪说)。 / - ② assignee 同笔换人(--unassign 旧 --assign 新);③ 新 Claim::新 session、续用分支与远程 sha。 / - ④ 交接记录:旧分支最后已 push 的 sha + 一句状态;读者只验①③形状,缺一件即非撤销。 / - C9 只剩一种红:无任何 Release:的跨账号Claim:(真抢卡);线程上每条活认领都要点名。
:502 → :498 - 误伤活席位 ⇒ 令其追加式更正,落 PR 正文不落分支历史。 unchanged (a mis-handed live seat still appends its correction)

.claude/skills/pm-dispatch/references/core-rules.md

line before after
:110 - 更早的他会话认领即让行并交出已诊断的一切;认领逾一天且无合并证据即疑死。 - 更早的他会话认领即让行并交出已诊断的一切;认领人不可达即接管,⛔ 不判死活。
:111 - dev 自死不等于维护者中止,需显式信号;回收前先救工作树,有提交的活分支 ⛔ 永不回收。 - dev 自死不等于维护者中止,需显式信号;接管一条评论四件齐,只救已 push 的分支。

.claude/agents/os-dev.md

line before after
:94 - 有可展示内容即 commit、push 并开 draft PR,不等验证结束;验证结果到达即写进报告。 - 每个可编译小步即 commit + push;有可展示内容即开 draft PR;接管只认远程分支最后 sha。

The dropped tail 「验证结果到达即写进报告」 survives at os-dev.md :95 (「未读到的判决写 NOT MEASURED」) and :311 (「报告在本地验证走完时交付」).

3. SKILL.md deletion list — each retired line's surviving home

retired line (base :493–:501) surviving home
死认领回收:认领 >~24h ⇒ 疑死;判死主腿 = 搜引用本卡的 PR、读其 merged/merged_at。 retired outright — the ruling replaces liveness judgement with the human's word (:493 「⛔ 不判死活」)
⛔ 判死不读 closes-list;承诺分支缺席与提交扫描失效只能支持判死、永不单独确立。 retired outright (no liveness judgement exists to bound)
零引用 PR ⇒ 停下发问,⛔ 不判什么都没落地。 retired outright; the "ask first" half is the protocol itself — the handover IS the human's answer copied with provenance (:494)
回收前先救工作树:向任何派发 worktree 提交前先过存活/所有权检查。 retired outright — the hard fact at :492: a remote container's worktree is reclaimed with the session; there is nothing to rescue
或对树最新 mtime 过明确年龄阈值;⛔ 不凭 GitHub 侧静默动手。 retired outright (same reason); 「⛔ 不凭 GitHub 侧静默动手」 survives as the provenance requirement (:494)
过栏后,派发 worktree 的未提交改动先 WIP commit 到派发分支并 push,sha 记进回收评论。 :492 (every compilable step is pushed by the dev — the WIP-rescue is moved to the writer side, before the cut) + :496 ④ (the last pushed sha in the handover record)
WIP commit 标 INCOMPLETE AND UNREVIEWED;续派者 diff 它,⛔ 不无审续建。 :496 ④ 「一句状态」 — the taker records the branch's state and continues from the remote sha; "diff before continuing" is the taker's ordinary care under 「读者只验①③形状」
WIP 信息只写观察到的(脏路径/行数/sha),⛔ 不写席位行为的现在时断言。 :496 ④ (sha + one status sentence) — no WIP commit is written by anyone but the dev itself
再评论询问,静默一窗后释放回队(Release: 行载因);有带提交活分支的认领永不回收。 :494 ① (the Release: line, now with provenance instead of a silence window) + :497 (every live claim named) — 「有带提交活分支的认领永不回收」 is retired: a pushed branch is precisely what the handover continues (:495 ③)

4. PM mechanism assumptions — verified, one refuted

  1. ✓ At 5e7d83c = 32b5831 (no diff on the surface between them): CLAIM_RETRACTION_RULE :1731 stated "⛔ never a DIFFERENT author's line", claimRetractions skipped every candidate whose author differs (:1778 candidate.author === null || candidate.author !== claim.author), and claimHandovers judged cross-login claims after CROSS_AUTHOR_CLAIM_ROW_EFFECTIVE_AT (:2073, 2026-09-19T03:45Z).
  2. ✓ Reproduced before the change (2026-09-21T03:5xZ, PM_SWEEP_REPO=objectstack-ai/objectstack): --pair 19373 → exit 4, ✗ C9 — card #17518 (delivering open PR #19373) — 2 authors hold LIVE claim comments … os-bill's 5646971772 at 2026-09-12T15:54:12Z is the claim that stood; os-litant's 5749581295 at 2026-09-20T11:43:41Z took the card from os-bill (dated AFTER the effective instant 2026-09-19T03:45Z — JUDGED); --pair 19335 → exit 4, ✗ C9 — card #18670 (delivering open PR #19335) — 3 authors … os-litant's 5717305863 … stood; os-steve's 5736537462 … (listed, informational); os-bill's 5749165780 at 2026-09-20T10:14:08Z took the card from os-steve (… JUDGED). After the change both STILL exit 4 (same rows, the remedy now printing the four-item comment) — as predicted, until the seats post the handover comments below.
  3. ✓ SKILL.md :149 reads exactly 「代执行他人指令的关闭、摘标、回收认领,评论带出处三件:谁的指令、原话、在哪说。」 (ASCII punctuation); the reader now reads exactly those three fields and quotes the line unbroken (HANDOVER_PROVENANCE_SOURCE).
  4. Tier S — node scripts/pm/check-governed-merges.mjs --pr N is run once the PR number exists; the result is in the report. The PR stays draft.
  5. REFUTED — ruling item ② spelling. label-write --clear-assignees --assign NEW is refused by the tool: --clear-assignees cannot be combined with --assign/--unassign (scripts/pm/label-write.mjs :417–:419). The one-write assignee swap is node scripts/pm/label-write.mjs --repo objectstack-ai/objectstack --issue N --unassign OLD_LOGIN --assign NEW_LOGIN (computeAssigneeTarget: target = current − unassign + assign, one write, read back). SKILL.md :495 and the handover comments below use that spelling.

5. The handover comments the seats post (verbatim — ⛔ not posted by this PR, ⛔ nothing written on #17518 / #18670 / PR #19373 / PR #19335 here)

Both were simulated offline against the live threads (the REST rows of each card plus the drafted comment appended): C9 state null (clear), pool = the handover comment, governing branch = the continued branch, declaration declared / yes, C8 = 0; controls — the same comment without 在哪说 ⇒ C9 judged true; the same comment with the session id blanked on the Release: line ⇒ C9 judged true. Placeholders in CAPITALS are the poster's to fill (its own session id / login, the UTC stamp). The 原话 / 在哪说 values copy the maintainer's words that adopted this protocol for exactly these two PRs (5754717208 § the maintainer's turns; 5754797404 「同意」, which names PR #19373 and PR #19335 as the two the ruling unblocks); a fresher instruction naming the card directly is a better value, if the seat has one.

#17518 (PR #19373) — posted by the domain:spec#1 seat (os-litant, the taker already holding claim 5749581295)

Release: handover of claim 5646971772 (`session_01MkQhmuuJAVDjmeWNixwDDH`, `os-bill`, branch `claude/issue-17518-assembled-body-json-schema`) and of this seat's own claim 5749581295 (`session_01LvwGppdonww4zGLWZo5rho`) · 因: the earlier claimant is a dev subagent session that ended on 2026-09-12 and cannot post its own `Release:`; the taker has delivered the whole diff on PR #19373 · 去向: the `Claim:` below — same seat, same branch
谁的指令: the maintainer (objectstack-ai/objectstack#19240 — ruling 5754797404, recorded by the `domain:skills` seat 2 at 2026-09-21T02:57Z; the maintainer's words carried in 5754717208 by the `domain:spec` seat 2)
原话: 「某个 agent 开发了一半没有token了,就是需要新的 agent 重新认领,而且重新认领的时候 是不是不issue 的人员也要跟着改。」「把它从「补一种 Release: 拼写」升级成 「接管协议」」「同意」
在哪说: objectstack-ai/objectstack#19240 comments 5754717208 (2026-09-21T02:44Z, the maintainer's verbatim turns in the `domain:spec` seat 2's session) and 5754797404 (2026-09-21T02:57Z, 「同意」 on shape A in the `domain:skills` seat 2's session)
Assignee: `os-project-manager` → `os-litant`, in the same label write as this comment: `node scripts/pm/label-write.mjs --repo objectstack-ai/objectstack --issue 17518 --unassign os-project-manager --assign os-litant`
Claim: `domain:spec` seat 1 takes over #17518 under SKILL.md's handover rule (认领 section), at DATE_TIME_UTC
Session: `session_01LvwGppdonww4zGLWZo5rho`
Branch: `claude/issue-17518-assembled-package-body-inert-json` (continued at remote sha `aac764cc36113b4e52820c1695715f000ccbe1b4`, the head of PR #19373)
Clause-②: yes
Handover: the released claim's branch `claude/issue-17518-assembled-body-json-schema` — last pushed sha `ed8dea17bd510100320ab42dbac6ec2a78e99deb` (read from origin at 2026-09-21); status: superseded — the whole diff was re-delivered on PR #19373 at `aac764c` (checks green, `## Contract review` pending), nothing from the old branch is carried.

Why the seat's own 5749581295 is named too: the reader would otherwise carry TWO live Claim: comments by os-litant (C8). Named on the same Release: line it is retracted by the same-login arm, and the fresh Claim: in this comment is the only one standing. If the posting session differs from session_01LvwGppdonww4zGLWZo5rho, the Session: line carries the new one.

#18670 (PR #19335) — posted by the live domain:spec seat (POSTER_LOGIN / POSTER_SESSION_ID; the taker of record, session_01JbZnqu8bt6YqfJsr9vaFb3, was retired at 2026-09-20T23:34Z)

Release: handover of claims 5717305863 (`session_01LvwGppdonww4zGLWZo5rho`, `os-litant`, branch `claude/issue-18670-refinement-projection-census`), 5736537462 (`session_01AmH9bKvGoLjiY86Q4Z3og2`, `os-steve`, branch `claude/issue-18670-banned-keys-projection`) and 5749165780 (`session_01JbZnqu8bt6YqfJsr9vaFb3`, `os-bill`, branch `claude/issue-18670-propertynames-not-pattern-arm`) · 因: the first two claims' work is merged (PR #18729, PR #19137; both branches absent on origin), and the third claim's session was retired at 2026-09-20T23:34Z with its PR #19335 reviewed and green — none of the three can post its own `Release:` · 去向: the `Claim:` below
谁的指令: the maintainer (objectstack-ai/objectstack#19240 — ruling 5754797404, recorded by the `domain:skills` seat 2 at 2026-09-21T02:57Z; the maintainer's words carried in 5754717208 by the `domain:spec` seat 2)
原话: 「某个 agent 开发了一半没有token了,就是需要新的 agent 重新认领,而且重新认领的时候 是不是不issue 的人员也要跟着改。」「把它从「补一种 Release: 拼写」升级成 「接管协议」」「同意」
在哪说: objectstack-ai/objectstack#19240 comments 5754717208 (2026-09-21T02:44Z, the maintainer's verbatim turns in the `domain:spec` seat 2's session) and 5754797404 (2026-09-21T02:57Z, 「同意」 on shape A in the `domain:skills` seat 2's session)
Assignee: `os-bill` → POSTER_LOGIN, in the same label write as this comment: `node scripts/pm/label-write.mjs --repo objectstack-ai/objectstack --issue 18670 --unassign os-bill --assign POSTER_LOGIN` (a no-op when the poster IS `os-bill`; `pm:blocked` → `pm:dispatched` in that same write once the reader has landed)
Claim: `domain:spec` seat takes over #18670 under SKILL.md's handover rule (认领 section), at DATE_TIME_UTC
Session: `POSTER_SESSION_ID`
Branch: `claude/issue-18670-propertynames-not-pattern-arm` (continued at remote sha `1dfe2f40bce77270758d9b31b01dd8d46875a290`, the head of PR #19335)
Clause-②: yes
Handover: branch `claude/issue-18670-propertynames-not-pattern-arm` — last pushed sha `1dfe2f40bce77270758d9b31b01dd8d46875a290` (read from origin at 2026-09-21); status: `## Contract review` PASS recorded at 5749728565 on this head, both carriers stripped, checks green — nothing left to build, the landing is the only step. The two older branches are absent on origin (their work merged as PR #18729 / PR #19137).

Why all three claims are named: C9 walks every LIVE claim; naming only 5749165780 would leave os-litantos-steve → NEW as two hand-overs, the last dated after the instant — still red. The row prints exactly the ids to name (:497 「线程上每条活认领都要点名」).

6. Four-axis analysis

「No liveness test」 (ruling; 5754717208 §4's 「点名的是活认领 ⇒ 拒」 not kept)

「C9 keeps one red」 (a bare cross-login Claim: with no Release: at all)

7. Tests and gates (head 7a66ffe; every exit captured before any pipe)

  • node scripts/pm/check-clause2-carriers.mjs --self-test → exit 0, 1091 cases pass (1075 at origin/main, run from a temp copy in the same tree; the roster floor unchanged; both new case groups sit inside their existing batteries).
  • --pair 19373 / --pair 19335 → exit 4 before AND after (rows quoted in §4.2); after the change each row ends with the four-item remedy (grep -c 认领人不可达 = 1 per log).
  • Offline simulation of the two handover comments (§5): C9 clear, governing claim = the handover, declaration declared/yes; controls red.
  • Derived union (node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, 48 commands, derived from the tree at 693afd7 after the origin/main merge and re-run at 7a66ffe): all 48 of 48 commands exit 0 (run 2026-09-21T03:56Z–04:15Z, sequential, each exit captured before any pipe; the list reconciled with --ran); the slowest, pnpm check:pm-dispatch-gates, ran its full 1883-case battery green at this head.
  • pnpm --filter @objectstack/lint run check:doc-formula-expressions first answered exit 3 (PREREQUISITE NOT MET: @objectstack/formula / @objectstack/lint not built — NOT a finding); after pnpm exec turbo run build --filter=@objectstack/formula --filter=@objectstack/lint under os-verify-lock.sh (VERDICT command-exit 0, 203 s) it answers exit 0.
  • pnpm check:pm-dispatch-gates (845 s on this box) red once on an EARLIER draft: its governed-read census found a readFileSync of SKILL.md in this reader's self-test (my "same words" pin). Removed — see Deviations — and re-run green at the final head.

Deviations (declared)

  1. The "ONE sentence" property is not a governed read. A self-test pin that reads SKILL.md makes check:pm-clause2-carriers a derived family of SKILL.md and needs a GOVERNED_READ_FLOOR row in scripts/pm/dispatch-gates.mjs (outside this claim's surface; a gate-derivation change). Kept instead: CLAIM_HANDOVER_SENTENCE_LINES (the remedy prints the five lines verbatim) + the twin rule at review + a shape pin (each line ≤ 120 bytes, no bullet, no issue id). Open question for the seat: register the read so a SKILL.md edit that breaks the sentence reds the reader (recommended; a two-line floor row).
  2. SKILL.md ceiling not lowered (813 → could be 809): check-skill-line-ratchet.mjs is outside the surface; headroom 4 is reported, the seat lowers it if wanted.
  3. :474 left byte-identical (see §2) — the alignment the dispatch asked for is carried by the new bullets rather than by editing the line that a sibling file quotes.
  4. Ruling ② spelling corrected (--unassign OLD --assign NEW), see §4.5.

Acceptance notes (off-path; noted, not filed — ⛔ no card filed by this dev)

维护者速读(草稿)

改了什么:把「死认领回收」换成「接管协议」。一个 agent 做到一半没 token 了,新会话在一条评论里接管:① 跨账号 Release: 点名旧认领的评论 id 与 session ID,并带出处三件(谁的指令 / 原话 / 在哪说);② assignee 同笔换人;③ 新 Claim:(续用远程分支与 sha);④ 一句交接状态。认领读者(check-clause2-carriers.mjs)按形状接受①③,不再判死活;C9 只剩「没有任何 Release: 的跨账号抢卡」一种红。SKILL.md 删掉九行判死启发式,换成五行接管规则;os-dev.md 把「早推分支」提为硬要求(每个可编译小步即 push)。

为什么改:两张已复核完毕的成品 PR(#19373#19335)今天落不了地,只因为旧认领人已经不在、没人能替它写 Release:;而「代执行他人指令要带出处三件」这条规矩早就在 SKILL.md 里,只是读者不读。您的原话:「这种情况通常都是人类口头交代的……我们系统开发了太多无用的门禁」。

风险与代价(含回滚):风险是一条编造出处的接管评论会被读者接受——但出处三件留在卡上可审,误伤活席位按既有规则追加更正。代价是读者多一条判形状的分支(+120 行,含自测)。回滚 = revert 本 PR,一次 revert 即回到判死启发式与旧 C9。

席位意见:(席位填写)

你要做的:本 PR 是受管面(.claude/**),由席位达档复核后落地,不需要您动手;落地后 spec 席按正文第 5 节的两条评论接管 #17518#18670,两张 PR 即可入队。若您希望读者对「SKILL.md 与读者同句」做机械钉死(而非复核时人工核对),点一下头,席位在 dispatch-gates.mjs 登记一条 governed read 即可。


Generated by Claude Code

…ems, provenance instead of a liveness test

SKILL.md: the dead-claim reclaim heuristics (>24h suspicion, PR search,
closes-list, mtime threshold, WIP commit + push before reclaim) are
replaced by the handover-in-one-comment rule; identity and early-push
lines aligned with the reader. core-rules.md twins follow. os-dev.md:
every compilable step is pushed — the remote branch is the only thing a
handover can pick up.

Claude-Session: https://claude.ai/code/session_017ETYWqMQD4qMtZzAGovWNi
Co-authored-by: Claude <noreply@anthropic.com>
… keeps one red

`claimRetractions` gains the HANDOVER arm: a `Release:` by a DIFFERENT
login retracts an earlier claim when, and only when, its line names that
claim's comment id AND session id and the comment carries the three
provenance fields of SKILL.md's 出处三件 line (谁的指令 / 原话 / 在哪说),
each non-empty — fail closed on any missing piece, and no liveness test.
Same-login retractions unchanged. C9 keeps exactly one red (a cross-login
`Claim:` with no `Release:` at all for the earlier claim); its remedy is
SKILL.md's handover sentence verbatim, and a refused cross-login release
is listed with its reason. Self-tests pin both sides (1075 → 1097 cases).

Claude-Session: https://claude.ai/code/session_017ETYWqMQD4qMtZzAGovWNi
Co-authored-by: Claude <noreply@anthropic.com>
…f SKILL.md

A self-test that reads SKILL.md makes check:pm-clause2-carriers a derived
family of that file (check:pm-dispatch-gates' governed-read census reds on
the unclassified read). The byte identity with SKILL.md is the twin rule's,
checked at review; the self-test keeps the shape pin.

Claude-Session: https://claude.ai/code/session_017ETYWqMQD4qMtZzAGovWNi
Co-authored-by: Claude <noreply@anthropic.com>
…im's +120 line budget

No behaviour change: the same accept shape, the same refusals, the same
printed rule and remedy; docblocks and fixtures tightened (1091 self-test
cases, 1075 before).

Claude-Session: https://claude.ai/code/session_017ETYWqMQD4qMtZzAGovWNi
Co-authored-by: Claude <noreply@anthropic.com>
@os-steve os-steve added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 21, 2026 — with Claude
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation labels Sep 21, 2026

Copy link
Copy Markdown
Collaborator Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 7a66ffeef059ff20becdb82f22e61f218cd9a8a5

① Derived judgments

  • Published accept set / public surface: none moves — scripts/pm/check-clause2-carriers.mjs (+174 / −54 = +120, the claim's budget exactly) and three .claude/** charter files (SKILL.md 813 → 809, references/core-rules.md 151 / 151 net 0, agents/os-dev.md 403 / 403 net 0), every line ≤ 120 B; no package, no export. Clause-②: yes is the PM protocol's own accept set widening (a cross-login Release: with provenance now retracts) — this record is the review it declares.
  • The fix, read line by line against the ruling 5754797404 (shape A): handoverProvenance reads exactly the three 出处三件 keys (谁的指令 / 原话 / 在哪说 — ASCII or fullwidth colon, decorated keys, the value inline or in the blockquote below; a blank value is missing); handoverRelease accepts only when the Release: line names the retracted claim's comment id AND its session id and no key is missing; claimRetractions sends cross-login candidates through it and leaves same-login retractions unchanged; ⛔ no liveness test anywhere (verified: no author-later-comment probe in the arm); C9's rule and remedy narrow to 「no Release: at all for the earlier claim」, refused handover attempts are listed with their reason, and the remedy prints SKILL.md's five handover bullets verbatim from CLAIM_HANDOVER_SENTENCE_LINES with the 出处三件 source line quoted. SKILL.md :493–:501 (nine liveness heuristics) → five handover bullets at :492–:497, :177 and :472 aligned in place; core-rules :110–:111 twins; os-dev.md :94 = every compilable step pushed. The ruling's ② spelling is corrected by the tool's own refusal (label-write cannot combine --clear-assignees with --assign) to --unassign 旧 --assign 新 — accepted: the act is the same one write.
  • Evidence, seat-run on this head in a detached worktree: git merge-tree --write-tree origin/main HEAD clean; check-skill-line-ratchet.mjs exit 0 (809 / 813 · 403 / 403 · 151 / 151); check-skill-id-lint.mjs exit 0; check-governed-prose.mjs exit 0; check-clause2-carriers.mjs --self-test exit 0 (1091 cases, from 1075 on origin/main); check-governed-merges.mjs --pr 19502 exit 3 GOVERNED, Tier S (3 of 4 paths on the register). The dev's union: 48 / 48 derived commands exit 0 at 7a66ffe, reconciled with --ran; --pair 19373 / --pair 19335 exit 4 before and after, as predicted — the two seats owe the handover comments the PR body prints verbatim for finding(spec): AssembledPackageBodySchema declares callable and custom branches an inert-JSON artifact cannot hold, and every schema embedding it loses its JSON Schema #17518 and [finding] the published JSON Schema is WIDER than the zod schema it is generated from wherever a .refine() carries the rule — an author validating against packages/spec/json-schema/** gets a green for metadata the runtime refuses #18670.
  • --pair 19502 before this record read C3: the needs:contract-review gate had never been hung on either carrier — the seat's own omission at claim (5754805042 declared Clause-②: yes), corrected in this act: gate hung on the PR immediately before this record, cleared by it as the completed review; the re-run after clearing is the ACCEPT's --pair 0.

② Semver level

None — nothing published; skip-changeset on the PR is the correct declaration. Judged correct.

③ Boundary flags

Implemented-by: claude/issue-19240-claim-handover-protocol
Reviewed-by: session_017ETYWqMQD4qMtZzAGovWNi

VERDICT: PASS


Generated by Claude Code

@os-steve
os-steve marked this pull request as ready for review September 21, 2026 04:23
@os-steve
os-steve enabled auto-merge September 21, 2026 04:23
@os-steve
os-steve added this pull request to the merge queue Sep 21, 2026
Merged via the queue into main with commit 9dd9319 Sep 21, 2026
51 checks passed
@os-steve
os-steve deleted the claude/issue-19240-claim-handover-protocol branch September 21, 2026 04:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants