Skip to content

pm-dispatch protocol hardening: subagent claim identity, explicit draft bit, cross-lane same-file holds - #12638

Merged
os-zhuang merged 6 commits into
mainfrom
claude/issue-12220-protocol-hardening
Aug 27, 2026
Merged

os-zhuang merged 6 commits into
mainfrom
claude/issue-12220-protocol-hardening

Conversation

@os-litant

Copy link
Copy Markdown
Contributor

Fixes #12220
Fixes #12200
Fixes #12286

Family dispatch, chain head #12220 with members #12200 and #12286 — RESUMPTION of the W4 fold that died mid-edit on a model-tier quota limit. The rescue commit a734b227 was inherited on the branch, origin/main was merged first (SKILL.md had moved after the rescue base), and the rescue was then reviewed hunk by hunk and corrected FORWARD with per-member commits. History was not rewritten; the rescue commit stays auditable.

Governed surface: this diff contains .md files under .claude/** — PR stays draft, human merge only.

Per-member deliveries, each with a named checkable criterion

#12220 — claim identity under mode:subagent (commit b523e7cf9)

The dispatch protocol said the claim comment's session ID distinguishes the dev's claim from the PM's; under mode:subagent (the default for S/M cards) the two IDs are identical by construction. All three dispositions, as promoted:

  1. Claim rule 2 now names the branch as the distinguishing identity under mode:subagent and keeps the session ID as mandatory but mode-qualified.
  2. The same sentence states what the claim-comment pair buys per mode — subagent: a dev ran, and on which branch; cloud: additionally which session.
  3. The round-open mutex clause states the same-session reading as a deliberately kept safe direction (a subagent dev's claim reads as this session in flight and never triggers self-retreat — a same-session dev is not a mutex object; who it is, the branch answers), instead of safe-by-accident.

Criterion: claim rule 2 in .claude/skills/pm-dispatch/SKILL.md names the branch as the claim's distinguishing identity and the per-mode purchases; the mutex clause carries the safe-direction sentence; pnpm check:pm-skill-ratchet green at 1005/1005 with zero over-120-byte lines (the rescue state had 14).

#12200 — update_pull_request always sends the draft bit (commit bb49f525e)

Rule stated over the WHOLE tool, per the promotion: any MCP call against a draft PR must pass draft: true explicitly — a reviewers-only review request published a governed-surface draft into a live merge queue in the sibling repo (2026-08-25).

  • SKILL.md ACCEPT fork carries the requirement inline at the point of use, on metered lines; the endpoint-level detail lives in platform-readings. The already-enqueued remedy line prescribes draft-conversion AND disable, then verifying the queue ref.
  • platform-readings: the update_pull_request fact row (rescue) is kept; the dedicated REST review-request path is re-attributed as documented-not-measured (this seat's repo-scoped REST probe answers 403, and no measurement record exists for the rescue's claimed date — the payload schema fact stands on the API docs).
  • The measured contradiction is reconciled, both readings kept with boundaries: this repo measured draft-conversion dropping auto-merge and queue membership; the sibling repo measured an already-enqueued PR NOT dequeued by draft conversion (entry kept its position, merged about 40 minutes later) with disable_pr_auto_merge refusing at that point. Repo/timing difference undiagnosed; the surviving row prescribes the worst-case remedy (do both, verify queue ref and non-landing, never report remediated on a single reading) and states the two disable readings precisely (this repo: measured not to dequeue when called alone; sibling repo: the call refused outright).
  • .claude/agents/os-dev.md rule 2: the rationale against reverting another actor's ready flip no longer asserts only the direction the incident refuted — both measured directions carry it now.

Criterion: the ACCEPT fork's request-review passage requires explicit draft: true for MCP calls on a draft PR; the platform-readings row titled with draft-conversion reliability carries BOTH dated readings with repo boundaries and no picked side.

#12286 — cross-lane same-file holds made legible (commits a62f572d7, cb9ea5623)

The two promoted halves only; the card's larger questions (cross-lane fence visibility in general, re-laning) stay on the card, not addressed here — #12286 scope beyond the two halves remains recorded on the card thread.

  1. platform-readings: mergeable_state row now dated, states unknown is NOT a reading (measured: a dirty-headed PR answered unknown for the whole window and auto-merge was armed on it), and names the decisive zero-quota local probe — fetch the PR ref, git merge-tree --write-tree against current main.
  2. scripts/pm/check-half-states.mjs H36: report-only patrol row — open-PR pairs sharing a changed file where at least one side is accepted/armed. One changed-file page per open PR (bounded by the open population), H16's transport posture (partial reads are a stated gap; all-failed is the transport), a CLOSED two-spelling noise floor (pnpm lockfile, .changeset/ prefix) pinned against per-incident growth, changeset-release/* excluded as candidates with the argument in the banner, and a read-X-of-Y coverage pair on the summary line. Patrol input, never a verdict — the row text says so.

Criterion: node scripts/pm/check-half-states.mjs --self-test green (1456 cases; 27 H36-labelled pins covering the firing shape, both silent directions, the noise floor, truncation, transport judgement, summary pair, and markdown rendering). Ablation below proves the pins can fail.

Rescue reconciliation — kept, corrected, dropped

Kept on their merits: the reconciled draft-dequeue row shape, the update_pull_request row, the mergeable_state/merge-tree row, the mutex/claim/dispatch-order content, and the rescue's same-file byte-compressions in platform-readings (ledger below). Corrected: all 14 over-budget lines the rescue left in SKILL.md (its line count fit the ceiling only by illegal line widths — exactly the restructuring debt the dead dev's last note named); the request-review tool discipline moved OFF the quotation-exempt mega-line onto metered lines (the pre-existing mega-line rides the quotation exemption and was deliberately NOT extended); the disable-alone claim un-conflated into the two per-repo readings; the unverifiable measurement attribution downgraded to documented-not-measured. Dropped: the rescue's deletion of the conditional-request/304 fact (restored — it had no surviving home, and the lane charter reads a home-less cut as a lost rule).

Cut ledger (platform-readings.md held at its 314-line ceiling; SKILL.md at 1005)

cut (rescue or this PR) surviving home
queue-ruleset row: "由 PR 上下文的 check run 满足" mechanism detail core fact ("缺席不构成反证", 405 probe, rulesets API) survives in-row compressed
mergeable_state row: "重读一次拿真值" superseded by the member-3 measurement (unknown can hold the whole window) — a refuted reading, removed rather than rehomed
mergeable_state row: "「draft+clean」组合不存在" same fact survives as "draft PR 恒回 draft"
mergeable_state row: "该字段不老化" contrast operative rule survives as "随 fetch 老化,重跑先 fetch" on the probe
undraft row standalone bullet folded in substance into the update_pull_request row (all three path readings + dates kept)
enable_pr_auto_merge row: duplicated echo-reading narrative consolidated in-row; both 2026-08-24 readings and the dead-hypothesis line survive
thin-params row: "3258-char first item" evidence detail + "tool-description reverse hint is false" rule and its direct-observation evidence bar survive in-row
"文档载明、未实测: 304 不计 core 池" (rescue deleted) restored onto the surviving quota line — no home existed
search-zeroing row: the two counter-evidence numbers and two candidate mechanisms the refutation verdict and the raw-response requirement survive in-row
minutes-old-card row: specimen narrative rule, boundary and date survive in-row
SKILL.md dispatch-order bullet: per-mode purchase restatement single home is claim rule 2, pointed to

Verification (tree cb9ea5623 = this PR's head, union run at that head under the shared verify lock)

  • pnpm check:pm-skill-ratchet — green: SKILL.md 1005/1005 (widest table row 765/765 untouched), platform-readings 314/314 (table-row pin 0 untouched), os-dev.md 470/470, zero over-budget lines anywhere.
  • pnpm check:pm-skill-id-lint — green, 22 files clean.
  • pnpm check:skill-frame-sync and check:skill-frame-freshness — green (4 copies isomorphic; current with origin/main).
  • node scripts/pm/check-half-states.mjs --self-test — green, 1456 cases.
  • Ablation (from committed state, trap-restored): mutated h36SharedFileHolds to drop every pair; mutation proven on disk by anchored grep counts (orig 1 to 0, mutant 0 to 1); self-test then FAILED exit 1 with exactly the 10 H36 firing pins red and the suite running to completion (the null-guard wrappers held); restore verified by empty git diff HEAD plus worktree blob hash equal to HEAD blob hash. No build step exists on this path (the script runs from source), so no dist leg applies.
  • Full derived union (26 families from dispatch-gates.mjs, no paths passed — derivation stderr named this repo at commit cb9ea562): all green, including the two convention-triggered obligations for editing a gate script (bare-root-worklist --self-test, check:pm-dispatch-gates), check:nul-bytes, check:pm-governed-merges, check:pm-governed-prose, check:pm-half-states, governed-queue-guard self-test, and check:doc-formula-expressions (green after building @objectstack/lint; the first run was its PREREQUISITE NOT MET refusal, not a measurement). check:partof-closing-keyword was run locally against THIS body via PR_BODY before opening the PR.
  • Changeset: none — .claude/** and scripts/pm/** only, nothing releases; skip-changeset label applied and read back.

Generated by Claude Code

os-steve and others added 6 commits August 27, 2026 01:41
…died dispatch

The dispatched dev terminated mid-edit when its model tier hit an account
quota limit, leaving these edits uncommitted in the worktree — the exact
loss shape the WIP-commit rule that landed this morning exists to prevent.

INCOMPLETE AND UNREVIEWED. Preserved so the resuming dispatch can read it
with git diff and decide what to keep; the dev's own last note said the
ACCEPT-region approach still needed restructuring for the quote-exemption
mechanics. Not a claim that any of it is correct.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JANH3y7qe3MD8aLaLXci8N
…tity under mode:subagent

Reconciles the rescue commit's claim-identity edits against the line
ratchet: the mutex four-readings clause now states the same-session
safe direction as deliberate (a subagent dev's claim carries the PM
session's own ID, reads as this session in flight, and never triggers
self-retreat — the branch is the distinguishing identity); claim rule 2
states what the claim-comment pair buys per mode (subagent: a dev ran,
on which branch; cloud: additionally which session); the dispatch-order
bullet now points at rule 2 instead of restating it. All touched lines
re-wrapped to the 120-byte budget the rescue exceeded; file at
1004/1005.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MnijPVVDakqK2J335JoJtq
…equire explicit draft:true, reconcile the draft-dequeue readings

Completes and corrects the rescue commit's half of this member:

- SKILL.md ACCEPT fork: the inline requirement now stands on metered
  lines instead of riding the quotation-exempt mega-line, and drops the
  endpoint spelling (detail lives in platform-readings). The
  already-enqueued remedy line keeps draft+disable+verify.
- platform-readings: the reconciled row now states the two measured
  directions precisely — this repo measured disable-alone not
  dequeuing, objectui measured the call refusing outright (the rescue
  conflated them as one reading); the dedicated REST review-request
  path is re-attributed as documented-not-measured (this seat's REST
  gate is 403, and no measurement record exists for the rescue's
  claimed date); the conditional-request/304 fact the rescue deleted
  with no surviving home is restored onto the surviving line.
- os-dev.md rule 2: the rationale against reverting another actor's
  ready flip no longer asserts the single direction the incident
  refuted — both measured directions now carry it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MnijPVVDakqK2J335JoJtq
…report-only patrol input

The measured incident: two open PRs edited one runtime source file
across lanes for ~4h; the later-opened one merged first and left the
earlier — reviewed, ACCEPTED, all checks green — in a silent merge
conflict that mergeable_state never volunteered (unknown throughout, so
the seat armed auto-merge on a dirty head). The single-claim gate was
right not to object; what was missing is legibility of the hold outside
the lane that declared it.

- check-half-states.mjs H36: open-PR pairs sharing a changed file where
  at least one side is accepted/armed, one changed-file page per open
  PR (bounded by the open population), H16's transport posture, a
  closed two-spelling noise floor pinned against per-incident growth,
  and a read-X-of-Y coverage pair on the summary line. Self-test pins
  the firing shape, both silent directions, the noise floor, the
  truncation direction and the markdown medium.
- platform-readings: the mergeable_state row (already reconciled by the
  rescue commit) gets its measurement date; unknown is stated as not a
  reading, with git merge-tree --write-tree as the decisive local probe.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MnijPVVDakqK2J335JoJtq
The suite's own wrapper discipline: a nulled first row must report as
red cases under their own names, never abort the suite with a TypeError
raised in argument evaluation — which is exactly the state an ablation
of the predicate produces.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MnijPVVDakqK2J335JoJtq
@os-zhuang
os-zhuang marked this pull request as ready for review August 27, 2026 02:39
@os-zhuang
os-zhuang enabled auto-merge August 27, 2026 02:39
@os-zhuang
os-zhuang added this pull request to the merge queue Aug 27, 2026
Merged via the queue into main with commit 2e9bca2 Aug 27, 2026
35 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-12220-protocol-hardening branch August 27, 2026 03:06
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

4 participants