Repository navigation
feat(pm): fleet-write relay carries the base sync as pr_update_branch, confirmed by measure - #22457
Merged
objectstack-fleet[bot] merged 2 commits intoOct 9, 2026
Conversation
…, confirmed by measure
The sanctioned merge of main into a PR head had no relay op, so the seat's
only route was bare REST under its session token and the merge commit was
authored by the seat's personal account. One row in the PR-ops cluster of the
closed op table now issues PUT /repos/{repo}/pulls/{n}/update-branch as the
fleet App (pull_requests: write in GitHub's ledger, so the mint step widens
nothing). expected_head_sha is required and must be a full 40-hex sha, refused
by the validator before the dispatch. The executor confirms a 202 by polling
the pull's head off the expected sha for up to 60 s and prints old -> new head
with the merge commit's author; a 422 is a no-op only when the compare
measures the head not behind its base, otherwise a FAILED action with the
platform's sentence, so the enqueued pull's "cannot update" reading survives
as a row. rest-channel.md, landing-operations.md and platform-readings.md
spell the op; bare REST for this verb is now marked refused.
Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JmWtcHfGbC4ncw4GFKWuRA
…lay-pr-update-branch-op
Contributor
Author
Contract reviewServed-tier: ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS |
This was referenced Oct 9, 2026
objectstack-fleet
Bot
deleted the
claude/issue-22052-relay-pr-update-branch-op
branch
October 9, 2026 11:04
This was referenced Oct 9, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #22052
Clause-②: no
What this lands
The sanctioned base sync — merging
origin/maininto a PR head — had no relay op, so a seat could only issuePUT /repos/{owner}/{repo}/pulls/{n}/update-branchas bare REST under its own session token, and the platform authored the merge commit under the seat's personal account (the card measured PR 22002's headfab444b4). The relay's closed op table now carries ONE new row,pr_update_branch, in the PR-ops cluster besidepr_request_reviewers/pr_ready: the same PUT, issued on the runner with the App installation token, so the merge commit leaves asobjectstack-fleet[bot].pullis required;expected_head_shais required and must be a full 40-hex sha (FIELDSkindsha,SHA_SHAPEinops.mjs): a short, uppercase, non-hex or 41-character spelling is refused byvalidate.mjsBEFORE the dispatch, naming the shape — never left to the platform's 422. A missing sha is refused as a missing required key, never defaulted to the current head.pull-requests: GitHub's server-to-server permission ledger (github/docs,src/github-apps/data/fpt-2022-11-28/server-to-server-permissions.json, read 2026-10-09) listsPUT /repos/{owner}/{repo}/pulls/{pull_number}/update-branchunderpull_requestswithaccess: write,server-to-server: true,additional-permissions: false. The mint step infleet-write.ymlalready grants it, so the workflow is untouched.execute.mjs,confirmUpdateBranch) confirms the write by MEASURE, never by the answer's sentence:GET /pulls/{n}) every 3 s for up to 60 s until its head is no longerexpected_head_sha; thenGET /repos/{repo}/commits/{sha}is read and the row printshead OLD → NEW · merge commit author LOGIN (NAME)— the fleet-identity reading this card exists for, in the log and the step summary of every real sync. A window that expires is a FAILED action whose sentence says UNCONFIRMED (the merge may still land; read the pull; ⛔ never re-sent), and the stroke stops there, so apr_readyqueued behind the sync never runs on an unconfirmed head.GET /repos/{repo}/compare/{base}...{head}is read andbehind_by0 is a NO-OP landing (the row says "nothing to merge" and quotes the platform's sentence), while a head still behind its base, or a measure that cannot be read, is FAILED with the platform's sentence — never a no-op by default.write-pace.mjslike every other write verb (fleet-write PUTin the pace log; pinned in the wiring battery).dispatch.mjsreads nothing back for the op (it carries no body); asuccessrun IS the confirmation, and the seat reads the new head with oneGET /repos/{repo}/pulls/{n}after. Pinned in the read-back-targets battery; no new sender, so no newrelayMissProbe.rest-channel.mdlines 49–50 moved to the relay spelling with bare REST for this verb marked ⛔ (82/82 before and after); line 74 names the op (reading ③ kept);landing-operations.md§C names the op (101/101);platform-readings.mdline 89 names the op (469/469) — that file was not on the claim's surface, but the card's pin (grep -rn 'update-branch' .claude scripts/pmreturns the op, its tests and the ⛔ line only) could not hold with its old spelling, so it was moved too.Pin, measured on the final tree:
grep -rn 'update-branch' .claude scripts/pmreturnsrest-channel.md:50(the ⛔ line),ops.mjs(the row and its header),validate.mjsandexecute.mjs(their tests and the executor's header) — nothing else.The PM's mechanism assumptions, measured
fleet-write.yml:actions/checkout@v7with noref, on arepository_dispatchtrigger, checks out the event'sgithub.sha, the default branch's tip. So the relay runsmain'svalidate.mjs/execute.mjs: apr_update_branchstroke sent before this lands would be refused bymain's validator ("not an op the relay executes") with zero writes. The merge commit's author on a real PR is therefore NOT MEASURED until merge — the seat measures on the first real sync after landing; that sync's run prints the reading in its row (head OLD → NEW · merge commit author LOGIN (NAME)) and its step summary, so the seat quotes it from there. Not measured here by bare REST (that is the defect) and the workflow was not changed to take code from the PR ref.pull_requests: write, no additional permission; the mint step is untouched.pr_request_reviewersandpr_ready, inside the PR cluster; the table's tail (transfer) is untouched, so fleet-write: no relay op starts a workflow_dispatch run, so a seat cannot begin an unblock that only a workflow run produces (Shard Timings Refresh for p1 #22014) and it waits on a maintainer click or the weekly schedule #22369'sworkflow_dispatchrow appends behind it without overlap. The executor's confirmation is its own function, not a branch of the GraphQL path.dispatch.mjs --actions-file;grep -l sendFleetWrite scripts/pmis unchanged and the no-run conformance battery still passes (27 cases).pnpm check:pm-skill-ratchetgreen; operative text carries no issue number (pnpm check:pm-skill-id-lintgreen, 34 files).Verification
Self-tests, before → after on this tree (
node scripts/pm/fleet-write/FILE.mjs --self-test, exit codes captured before any pipe):validate.mjs: 94 → 99 cases across 9 batteries — new rows: the accepted shape; a short sha refused before the dispatch; uppercase / non-hex / 41-char refused; a missingexpected_head_sharefused; the table's one PUT pinned by op and path, never reaching/labelsor/assignees; the row's permission pinned topull-requestswith the sha in its body.execute.mjs: 83 across 12 → 93 across 13 batteries — the new "base sync" battery (10): 202 polled off the expected sha with the author read (3 polls, 6 000 ms of fake clock, the PUT the one write); 422 +behind_by0 as a no-op landing (exit 0, the platform's sentence quoted); 422 + head behind as FAILED naming the count and the platform's sentence (the enqueued "cannot update" reading); 422 + a moved head as FAILED naming both shas with no compare taken; an unreadable pull or compare as FAILED; a window that expires (60 000 ms of fake clock) as FAILED, UNCONFIRMED in its sentence, with thepr_readybehind it never attempted; any other status as FAILED; the wiring battery now pinsfleet-write PUTin the pace log.dispatch.mjs: 200 → 201 cases across 18 batteries —pr_update_branchis not inBODY_OPS, has no locator and yields no read-back target.scripts/pm/write-pace.mjs --self-test: 113 cases across 12 batteries (execute.mjs is on its roster).dispatch.mjs --dry-run, route resolved todispatch): the full-sha stroke packs and prints (exit 0, nothing sent); the 8-character sha and the missing sha are refused at exit 2 naming the reason, nothing sent.eslintover the four relay files: exit 0 (a targeted run, not the repo-widepnpm lint, which is CI's).Gate sweep:
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 47 commands from the 7-path change set, identical before and after the pre-PR merge (derived at2b3e864b, re-derived atc3f353d9against merge base2b61f2d9). All 47 ran serially and detached onc3f353d9(1 593 s in all,check:pm-dispatch-gates1 174 s of it), each exit code captured before any pipe: 46 exit 0 on the first pass;pnpm --filter @objectstack/lint run check:doc-formula-expressionsexit 3 = PREREQUISITE NOT MET (@objectstack/formulaand@objectstack/lintnot built; nothing measured), its own fix line run underscripts/pm/os-verify-lock.sh(turbo run build --filter=@objectstack/formula --filter=@objectstack/lint, VERDICT command-exit 0), then the gate re-run: exit 0. Reconciliation with the exit codes recorded (--ran): ✓ dispatch-gates --ran: 47 derived famil(ies) accounted for — 47 run, 0 NOT-MEASURED (a DERIVED zero — all 47 recorded an exit code and none of them is 3). Among them, every gate the dispatch named:check:pm-skill-ratchet,check:pm-skill-id-lint,check:pm-governed-merges,check:nul-bytes,check:pm-fleet-write-validate/-execute/-dispatch,check:pm-write-pace— all exit 0.Acceptance notes
failure), so the seat'sdispatch.mjsanswers 5 "the run FAILED", not 6: a seat cannot read the executor's exit, only the run's conclusion, and the executor has no UNCONFIRMED exit of its own. The row's sentence says UNCONFIRMED and prescribes a read; the prescription for a failed run (never fall back, never re-send) is the same. The PM's suggested route (an UNCONFIRMED-style exit aligned withexitForResult) is met in the sentence, not in the exit number.behind_byand the re-read head, never by the sentence.fleet-write.yml's mint-step comment ("the op table has NO contents op — no file, ref or branch write") stays true for thecontentsgrant it explains;ops.mjs's header now says where the one branch-moving row's merge comes from (the platform's own update-branch action, underpull-requests). The workflow file is untouched on purpose.objectstack-fleet[bot]on every commit; the commit trailer pair is the model-free pair AGENTS.md prescribes.origin/main(2 commits, none touching these files;pnpm-lock.yamlmoved),pnpm install --frozen-lockfilewas re-run; no package build is owed — the diff touches no published package and every gate here is a script-level self-test or repo scan.rest-channel.mdline 57 still spells a bare direct merge (PUT .../pulls/{n}/merge) as a ✓ for a non-queued repository — the same identity question this card asked about update-branch, for a verb the relay refuses by construction (/merge$is a refused family). carrier: the skills seat (this card's lane); noted, not filed.维护者速读(草稿)
改了什么:舰队写中继多了一个 op
pr_update_branch:把main合进某个 PR 的 head,这件事以前只能由席位用自己的账号裸调 GitHub 接口,合并提交就记在席位的个人账号名下;现在走中继,由objectstack-fleet[bot]发出。同时三份席位参考文件改成新拼写,裸调标为 ⛔。为什么改:仓规定每一笔 GitHub 写都以舰队身份经
scripts/pm/发出;这条动词是唯一还在用个人账号写的口子(实测 PR 22002 的 headfab444b4作者是席位的个人账号)。风险与代价(含回滚):新增一行 op 表、一个确认函数与自测,不改工作流、不扩权限(GitHub 权限表列此接口为
pull_requests: write,已铸)。合并前中继跑的是main上的代码,所以"真实 PR 上合并提交作者是舰队"这条要等合并后首次真实同步才能实测;执行器会把新 head 与合并提交作者打印在 run 的汇总里。回滚 = revert 本 PR,一次提交。席位意见:(留空)
你要做的:本 PR 受管面全在
.claude/**,Tier S:席内达档契约复核 PASS 后由席位入队,不需要你动手;合并后首次真实同步的作者读数由席位贴回卡上。Generated by Claude Code