Repository navigation
feat(pm): the relay deletes a closed mailbox's claude/mailbox-* head, and mailbox-open --close sends it in the close stroke - #30782
Conversation
… mailbox-open --close sends it The relay gains mailbox_branch_delete, its one ref op: a DELETE of one refs/heads/claude/mailbox-* ref, refused unless the pull it names is closed, carries pm:mailbox, has that branch as its head on this repository, and no open pull has that head. The fences are judged by the executor on the pull's own answers immediately before the DELETE, and the delete lands only when the ref reads back absent. REFUSED_PATH_FAMILIES keeps refusing every other /git/ path; contents: write is already a literal grant on every stroke, so the workflow is untouched. mailbox-open --close appends the delete to its close stroke when the head it reads is claude/mailbox-*, so a cloud seat's mailbox leaves no branch. Claude-Session: https://claude.ai/code/session_016WV4cwSv11c8FvyR8ZuWhv Co-authored-by: Claude <noreply@anthropic.com>
|
domain:skills#1 · os-elon-musk · session_016WV4d1mkgEris4MHHLjtnk Contract-review-claim: session_016WV4d1mkgEris4MHHLjtnk · head 1465a85 · 2026-11-06T02:49Z |
|
domain:skills#1 · os-elon-musk · session_016WV4d1mkgEris4MHHLjtnk Contract reviewServed-tier: ① Derived judgments
② Semver levelNone: ③ Boundary flagsPR author
④ Design judgmentMust-checks (a)-(i), each against the head:
Per hits file:
should-fix 1: no sha fence (execute.mjs L911-922). A closed Implemented-by: VERDICT: PASS Generated by Claude Code |
… close-stroke delete names its bare re-send Contract review should-fixes on #30782: - execute.mjs: mailboxBranchPreflight reads GET /git/ref/heads/{branch} as the last read before the DELETE and refuses unless the ref points at the closed pull's head.sha, so the op deletes that PR's head and never a commit pushed after the close. A ref already absent fences nothing (the DELETE's 422 lands idempotent). Self-test: a moved ref and an unread ref each refused with nothing sent; the good case reads the ref before the DELETE. - mailbox-open.mjs: when a --close stroke that carried the delete fails, closeRemedy prints the one bare mailbox_branch_delete action to send through fleet-write/dispatch.mjs, since --close refuses a closed pull. Self-test pins the action and that the validator accepts it. - The fence list now lives only in ops.mjs's header; execute.mjs and mailbox-open.mjs point at it. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016WV4cwSv11c8FvyR8ZuWhv
|
domain:skills#1 · os-elon-musk · session_016WV4d1mkgEris4MHHLjtnk Contract reviewServed-tier: ① Derived judgments
② Semver levelNone, unchanged: ③ Boundary flagsPR author ④ Design judgment
should-fix 1: the PR body is stale on this head: its "Acceptance notes" still say "No sha fence" (body L56), "the last two reads before the DELETE" (L28), the execute battery's call order without the ref read (L50), Implemented-by: VERDICT: PASS Generated by Claude Code |
|
domain:skills#1 · os-elon-musk · session_016WV4d1mkgEris4MHHLjtnk 维护者速读
席位核对 — ACCEPT · head 83464d0
Generated by Claude Code |
Requested by Elon · project thread
Fixes #30350
Clause-②: no
Metadata-protocol: none
维护者速读
objectstack-fleet[bot])多了一个只删不建的操作mailbox_branch_delete,只删「已关闭、带pm:mailbox的 PR 自己的claude/mailbox-*head 分支」;mailbox-open --close关信箱时在同一笔里顺带删掉它。claude/分支,预检还会把它读成认领。contents: write(给 auto-merge 用),所以 workflow 和权限都没改;路径拒绝表只对refs/heads/claude/mailbox-*这一种路径开口,建引用、改引用、写文件仍一律拒;删前在 runner 上现读 PR 和分支,未关闭、无标签、head 不符、fork、还有开着的 PR 用同一分支、或分支已不在 PR 关闭时的 head 提交上,任一条就不发 DELETE;--close那一笔里删除被拒时,失败行打印可单独重发的那一条动作。回滚即 revert;中继只跑 main 上的代码,合并前不生效。scripts/pm/fleet-write/ops.mjs的新行mailbox_branch_delete与REFUSED_PATH_FAMILIES里/git/那一条(唯一放行的路径形状);②execute.mjs的mailboxBranchFenceFailures/mailboxBranchPreflight/confirmMailboxBranch;③mailbox-open.mjs的planClose与closeRemedy。node scripts/pm/fleet-write/validate.mjs --self-test && node scripts/pm/fleet-write/execute.mjs --self-test && node scripts/pm/mailbox-open.mjs --self-test,预期三条全绿(337 / 276 / 87 例),其中 ⭐「after close, no branch」那一条钉住关信箱后无分支。What changes
The relay gains its one ref op, delete-only, by ruling 6674408912 (letter A). Nothing else in the op table moves.
scripts/pm/fleet-write/ops.mjsmailbox_branch_delete: keyspull+branch, nothing else. ONEDELETE /repos/{repo}/git/refs/heads/{branch}, no body. Permissioncontents, noalsoSpends, notalone, so it can ride the close stroke.MAILBOX_BRANCH_SHAPE(claude/mailbox-and a lower-case slug, no slash) andMAILBOX_LABEL(pm:mailbox, now spelled once;mailbox-open.mjsre-exports it).branch: astrheld to that shape before the dispatch. A refusal names the shape and never echoes the value.REFUSED_PATH_FAMILIES: the/git/entry becomes "every/git/path but ONErefs/heads/ref ofMAILBOX_BRANCH_SHAPE", built from the shape's own source./contents/,/branches/and every other family are byte-identical.execute.mjsandmailbox-open.mjspoint at it), and the three sentences that said no row reaches a ref now name this one DELETE.scripts/pm/fleet-write/validate.mjs:validateFieldhonoursshapeon astr(no echo). Self-test pins below.scripts/pm/fleet-write/execute.mjsmailboxBranchPreflightreads the pull's ownGET /pulls/{n},GET /pulls?state=open&head={owner}:{branch}and the refGET /git/ref/heads/{branch}, the last three reads before the DELETE.mailboxBranchFenceFailures(pure) refuses, the DELETE not sent, unless: the pull is closed; it carriespm:mailbox; its head ref isbranch; its head repository is this repository; no OPEN pull has that head; and the ref'sobject.shais the pull'shead.sha(a commit pushed after the close is not the mailbox's). A ref already absent (404) fences nothing; an unreadable ref is refused.confirmMailboxBranch: the 204 is not the landing. The ref is read back (GET /git/ref/heads/{branch}) and lands only when absent; a ref already gone (the DELETE's 422) is an idempotent landing; a ref still there is a FAILED action.preslot and its not-sent block; the confirm joins the landed chain. No other row's path changes.scripts/pm/mailbox-open.mjsplanClose({ repo, pull, head }): when the head read isclaude/mailbox-*,mailbox_branch_deleteof it is appended to the close stroke, last, afterissue_patch state: closed. Any other head (a thread's ownclaude/project-thread-*, a local seat'smailbox/LOGIN/SLUG) is left and the success line says so.closeLivere-plans with the head it already reads; the dry run, which reads nothing, prints the same two actions as before plus a line saying the delete rides when the head isclaude/mailbox-*.closeRemedy(plan)(pure): when a close stroke that carried the delete fails,closeLiveprints it — the pull is closed by then, so--closerefuses a re-run, and the line gives the one baremailbox_branch_deleteaction to send throughfleet-write/dispatch.mjs.--head claude/mailbox-SLUG, the one name--closedeletes.Why
Every cloud seat that opens a mailbox has to push its own
--headbranch, and--closeclosed the pull and deleted nothing, so each cloud seating left a branch that the seat cannot remove (the card body measured four cut-offgit push --deleteattempts) and that pre-flight branch checks read as a claim.The first stroke's measurement (the ruling's confidence gap). The App token does not need any new grant, and the carve-out is one path shape:
github/docs,src/github-apps/data/fpt-2022-11-28/server-to-server-permissions.json, read 2026-11-06):delete /repos/{owner}/{repo}/git/refs/{ref}iscontents: write,additional-permissions: false. The create (post …/git/refs) and the move (patch …/git/refs/{ref}) are the rows that also needworkflows; neither is spelled by any row..github/workflows/fleet-write.ymlalready mintspermission-contents: writeas a literal grant on every stroke (for the two auto-merge mutations), so the workflow is unchanged andPERMISSIONSis unchanged.REFUSED_PATH_FAMILIESis what keeps a row off/git/**. It now lets exactly one shape through, and the self-test pins that this row's DELETE is the only request any row sends under/git/.GET /rules/branches/…answers[],GET /branches/…answersprotected: false, both read 2026-11-06).Tests
validate.mjs --self-test: 337 cases, 17 batteries (floor 16 → 17, one battery for the row). The new battery: the shape and the label; a dotted seat slug accepted; twelve names outside the shape refused with the exact sentence and no echo (another thread's branch, a local seat'smailbox/branch, an empty slug, upper case, a slash, a dot pair, a trailing slash or dot, a space,main, a qualified ref, a path escape); closed keys; the one bodiless DELETE; the close stroke accepted with the five literal grants and nothing stroke-scoped. In "refused by construction" (22 → 24): the ONE request under/git/is this row's DELETE, and a positive control that a ref create, another ref, a nested ref, a tag, a tree, a blob, a commit, the singularrefread path and/contents/are all still caught while the one mailbox ref is caught by none. The twocontentspins now name the third spender.execute.mjs --self-test: 276 cases, 23 batteries (floor 22 → 23). The new battery: the good case's call order (pull, open pulls, the ref at the pull's head, DELETE under the App token, ref read-back) and row; the open-pulls query filtered toobjectstack-ai:claude/mailbox-ui-3; each of nine fences refusing with the DELETE not sent and naming itself (an open pull, nopm:mailbox, another head, a forked head, an open pull on the head, a ref moved off the pull's head, an unread ref, an unread pull, an unread open-pulls list); a 204 whose ref still reads back is FAILED; a ref absent before and after, the DELETE answering 422, is idempotent; ⭐ after close, no branch —mailbox-open's ownplanClosestroke run through the executor sends the comment, the close, then the DELETE, reads the pull only after closing it, and ends on the ref read back absent; and a head outsideclaude/mailbox-*is closed with nothing sent under/git/.mailbox-open.mjs --self-test: 87 cases (the close-plan battery 3 → 7): the delete rides last for aclaude/mailbox-*head and the validator accepts the stroke; a thread branch, amailbox/branch, two look-alikes and no head read send no delete; a failed close stroke that carried the delete names the bare re-send throughdispatch.mjs, whose one action the validator accepts, and a stroke with no delete names none.Acceptance notes
pm:mailbox+claude/mailbox-*. The executor also requires the head to be this pull's head on THIS repository (the card's "a ref that is not that PR's head", and a fork's same-named branch is not ours), refuses when any OPEN pull has that head, because deleting a branch closes every open pull on it (the dispatch's "never one whose mailbox PR is still open"), and refuses unless the ref still points at the closed pull'shead.sha, so a commit pushed to the name after the close is never deleted (contract review should-fix 1, comment 6675668758).--close(should-fix 3). If the relay's pull read lags the stroke's own close, the fence refuses and the pull is already closed, which--closethen refuses;closeRemedyprints the one bare action to re-send instead of leaving the branch behind.main's code, so it cannot be probed before). Two of the three leftovers qualify, read 2026-11-06:claude/mailbox-ui-3is the head of mailbox pm:mailbox — domain:ui#3 · os-justin #30141 andclaude/mailbox-ui-2of mailbox pm:mailbox — domain:ui#2 · os-justin #30142, both pulls closed and labelledpm:mailbox. The third,mailbox/os-zhuang/triage-1, is the head of OPEN mailbox pm:mailbox — triage#1 · os-zhuang #28646 and outside the shape: ⛔ never touched. The actions file is one stroke, both deletions:[{"op":"mailbox_branch_delete","pull":30141,"branch":"claude/mailbox-ui-3"},{"op":"mailbox_branch_delete","pull":30142,"branch":"claude/mailbox-ui-2"}]sent with
node scripts/pm/fleet-write/dispatch.mjs --repo objectstack-ai/objectstack --actions-file FILE --json, then re-checked withgit ls-remote origin 'refs/heads/claude/mailbox-*'(expected: empty)..github/workflows/fleet-write.ymlL134–135 still says the op table "has NO contents op — no file, ref or branch write — so this grant is spent only by those two mutations". That is false once this lands. The file is Tier H and outside this card's surface (⛔.github/workflows/**), so it is not edited here; reported to the seat to card.scripts/pm/**net-negative ask). +230 / −44 over four files: code and header prose +141 / −29, self-tests +89 / −15.ops.mjs2367 → 2407,validate.mjs2036 → 2061,execute.mjs2994 → 3089,mailbox-open.mjs689 → 715. Collapsing the fence prose to one place (should-fix 2) paid back the executor's header section.execute.mjs'srequestLanded(near L593, card pm-dispatch skill: on a private repository (objectstack-ai/cloud), a security finding is an ordinary issue labelledsecurity, not a draft advisory #30114's site) is untouched; the new functions sit beside the hierarchy link's, and the loop edit is two lines.scripts/pm/**only, not governed, publishes nothing:skip-changeset.Verification
Derived gate families (40 of 40) and pnpm lint, head 83464d0
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackon the tree of head83464d0b(the review fixes on1465a855; run on594a4e4c, whose tree is byte-identical, the commit message alone reworded), each run with its exit code captured before any pipe:dispatch-gates --ran: 40 derived, 40 run, 0 NOT-MEASURED, 0 UNRUN;pnpm lintrecorded outside the derivation. The same 40 ran green on32b7ca47and on1465a855before the review fixes, and the derived list is identical on all three heads.The relay self-tests, head 83464d0
No test file outside the scripts names any of the four (
git grepover*.test.*/*.spec.*: 0 hits), so the self-tests are their whole suites. No ablation: the diff is scripts only.