Skip to content

pm-dispatch(sign-off): a seat going off shift hands every unlanded claimed card back to pm:queue in one stroke; a needs_decision card goes to the decision box; stay-behind narrows to ACCEPTed PRs waiting on the queue or a human - #27371

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-27359-sign-off-hands-cards-back
Oct 29, 2026

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #27359
Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:席位下班(收班)时,手上每张还没落地的认领卡一笔交回 pm:queue(Release: + Resume-from: + 清 assignee);dev 报了 needs_decision 的卡改进决策箱;留守只剩已 ACCEPT、正在等合并队列或等人批的 PR。check-handover.mjs 同步收窄:声明留守的卡必须有已 ACCEPT 且在等的 PR,dev 报 needs_decision 却还挂 pm:dispatched 的卡点名拒收。
  • 为什么改:下班的席位不再把卡锁在自己账号下。之前留守是默认安置,结果两张卡停在 pm:dispatched 没人跑,其中一张的待裁问题根本没到你手里,别的席位只能强制接管。
  • 风险与回滚:只改两页席位协议文字和一个本地只读检查脚本(不进 CI、不写 GitHub)。风险是检查比以前严:以前能过的「随手留守」现在被拒,要么交回、要么先把 PR 送进队列或送审。回滚 = revert 本 PR。
  • 先看:scripts/pm/check-handover.mjs 的 handoverVerdict 与 acceptedPullWait(收窄逻辑);seat-post-protocol.md〈活性、写侧刷新与收班〉新第 35–36 行与〈交接收尾清单〉第 2 步;seat-lifecycle.md 第 40、43 行。
  • 两分钟验证:node scripts/pm/check-handover.mjs --self-test,应看到 ✓ check-handover self-test: 66 cases pass。
  • 需要你做的:无,席位落地(.claude/** 为 Tier S,席位达档复核后落地)。

What changes

The maintainer's rule, verbatim: 「下班时卡片应该交回队列,这个应该立卡更新 skills」. The card's items 1–4 are the spec.

Protocol text (both files stay at their line ceilings, every line at most 120 bytes):

file · line before bytes after bytes
seat-post-protocol.md L35 安置默认离任留守;留不了才热移交:在飞卡落收单注记 + 点名看护者。 94 收班默认交回(〈交接收尾清单〉第 2 步);留守只剩已 ACCEPT 待队列或待人批的 PR。 109
seat-post-protocol.md L36 (new) — — 留守写面 = 简报点名的这些 PR,残余定时器同界;留不了才热移交:收单注记 + 点名看护者。 119
seat-post-protocol.md L38 → L39 收班序列:贴正文与标题刷新在先,简报评论是最终一笔;继任的释放判据 = 简报在。 110 收班序列不看维护者措辞:check-handover.mjs 退出 0 → 刷正文与标题 → 简报最终一笔。 114
seat-post-protocol.md L40 (deleted) 留守写面恰为简报点名的项:已 ACCEPT 待队列 PR、落地中的评审链载体;残余定时器同界。 117 — (content moved to L35/L36; 评审链载体 leaves stay-behind by item 3) —
seat-post-protocol.md L47 (checklist step 2) 在手工件逐件处置:本席认领的卡落地,或 Release: 与清 assignee 同笔释放,转成真实状态。 118 认领卡落地,或同笔 Release:+Resume-from:+清 assignee 交回 pm:queue;needs_decision 进决策箱。 118
seat-lifecycle.md L40 收班 = 状态 flush + 在飞显式安置,不是在飞归零;派发口径失效,简报 ⛔ 不列作继承裁决。 118 收班 = 状态 flush + 在飞默认交回队列、不等落地;派发口径失效,简报 ⛔ 不列作继承裁决。 120
seat-lifecycle.md L43 离任留守只写简报点名的项,⛔ 不读队列、不新派发、不碰未点名卡。 94 离任留守只写点名的已 ACCEPT 待队列或待人批 PR,⛔ 不读队列、不新派发、不碰未点名卡。 120

Bytes are the full source line, list marker included (the gate's measure); the table shows the text after the marker. Line counts: seat-post-protocol.md 88 → 88 (ceiling 88), seat-lifecycle.md 96 → 96 (ceiling 96). The new L36 is paid by deleting old L40, whose content moved into L35/L36. One clause leaves old L38: 「继任的释放判据 = 简报在」. Its home is seat-lifecycle.md 〈开轮互斥〉: 「收班简报是前任不再写的显式声明,是释放标记不是锁:简报即最新事件 ⇒ 立即坐席。」

  • Item 1 (hand-back is the 收班 default): L35 + checklist step 2. The stroke is Release:, Resume-from:, the assignee cleared, and the card back to pm:queue.
  • Item 2 (a needs_decision card goes to the decision box, ⛔ not pm:queue): checklist step 2, and the new check below.
  • Item 3 (stay-behind narrows to ACCEPTed PRs waiting on the queue or a human): L35/L36, seat-lifecycle L43, and the check.
  • Item 4 (the brief and the check-handover.mjs run are the last acts, whatever words end the shift): L39.

scripts/pm/check-handover.mjs (a local refusal tool; no workflow runs it):

  • (a′) [needs-decision-unreleased]. A held card is refused by this name when its newest os-dev-report is newer than the claim and declares "status": "needs_decision", and the card is not needs-user-decision. The remedy names the decision box, ⛔ not pm:queue. It is refused even when the card is declared as a stay-behind.
  • Stay-behind narrowed. A card listed in --stay-behind and declared on the vacant body is reported as [stay-behind] only when an open pull that delivers it meets two conditions. First, the pull's newest ## 席位核对 record is an ACCEPT. Second, the pull waits: it is in the merge queue (newest queue event on its timeline is the add), auto-merge is armed, or it awaits a human approval (needs-user-decision on the pull). Otherwise the card stays [claim-unreleased], and the refusal names the failed condition for each pull.
  • No reader is restated. latestDevReport / devReportPayloadCandidates / deliveryEvidence come from check-half-states.mjs, SEAT_RECORD_MARKER / seatRecordAccepts from seat-record.mjs, and enqueuerFromTimeline from check-governed-queue-guard.mjs. The only new reading is the report's status field.
  • Reads are bought only for a declared tail. That is one paged listing of open pulls, then each delivering pull's thread, and its timeline only when the label and auto_merge did not answer. Every request is a GET.

Tests

node scripts/pm/check-handover.mjs --self-test: 46 → 66 cases, all pass (handshake intact). Changed rows: ARM 1 now requires a waiting ACCEPTed pull, and the decision-not-excused row uses a waiting tail so it still isolates the decision refusal. New rows cover the narrowing (not-waiting, unread), (a′) (measured shape, older-than-claim, newest-decides, already-in-inbox, refused-by-name), acceptedPullWait (queue, approval label, armed, needs-timeline, dequeued, no-accept, REWORK-after-ACCEPT, ACCEPT-after-REWORK), the shortfall text, delivery evidence, and two structural pins.

Ablation, four legs through scripts/ablation-replace.mjs, each restored and hash-proven (HEAD d457c56, blob 2557438f5d42)
  • leg 1: else if (twice && h.wait) changed to else if (twice). 2 of 66 failed: SIGN-OFF NARROWING, absence never reads as waiting.
  • leg 2: the needs_decision status test changed to return null. 1 of 66 failed: (a′) THE MEASURED SHAPE.
  • leg 3: the ACCEPT requirement changed to record existence. 1 of 66 failed: a REWORK newer than the ACCEPT, is no ACCEPT.
  • leg 4: the queue reading changed to if (true). 1 of 66 failed: the newest queue event is a removal, or there is none.
  • After each leg: blob after restore == blob at HEAD (2557438f5d42), and git diff HEAD is empty. The unmutated control run: 66 cases pass.
Live reads (GET only, this container)

Acceptance notes

  • 落地中的评审链载体 leaves the stay-behind write surface. Card item 3 narrows stay-behind to ACCEPTed PRs only.
  • Checklist step 2 loses 「转成真实状态」. The ruled targets replace it: pm:queue, or the decision box for needs_decision. Step 4 still carries pm:blocked + Blocked-by: / Restart-when: for ledger obligations.
  • The stay-behind reading asks whether the pull's newest seat record is an ACCEPT. It does not ask whether that record was made for the current head; H69 patrols that half on Tier H.
  • (a′) reads held claims only. A card released to pm:queue while its dev's question is open leaves that population, and H52 (open questions unrouted) still reports it.
  • Queue membership goes through enqueuerFromTimeline. An add event whose actor has no login reads as not queued, which is the refusing direction.
  • The coverage line prints (c) between (a) and (b), because the stay-behind reads follow the held-card reads. Cosmetic only.
  • The file's self-test keeps its pre-existing shape: a handshake and no battery floor. Adding a floor is outside this card.
  • This PR carries no assignee. That follows the seat's ruling for seat-driven drafts, while os-dev.md on main still says to send one.

Verification

Final head d457c56d. Derivation: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack gives 36 commands. All 36 exited 0 locally. check:doc-formula-expressions first exited 3 (PREREQUISITE NOT MET: @objectstack/lint was not built). It exited 0 after turbo run build --filter=@objectstack/formula --filter=@objectstack/lint under the verify lock. --ran reconciliation: 36 derived famil(ies) accounted for — 36 run, 0 NOT-MEASURED (a DERIVED zero — all 36 recorded an exit code and none of them is 3).

Gate readings
  • pnpm check:pm-skill-ratchet: seat-post-protocol.md is 88 lines (ceiling 88; headroom 0). seat-lifecycle.md is 96 lines (ceiling 96; headroom 0). Self-test: 164 cases pass.
  • pnpm check:pm-skill-id-lint: 105 file(s) clean.
  • pnpm check:pm-post-stamped: 735 cases pass across 26 batteries. Its self-test reads check-handover.mjs's carriesFacetMarker, which is unchanged.
  • pnpm check:doc-authoring, pnpm check:nul-bytes, node scripts/check-self-test-wired.mjs (+ --self-test), pnpm check:pm-governed-merges, pnpm check:skill-frame-sync, and the remaining 26 derived commands: exit 0.
  • pnpm exec eslint --no-inline-config scripts/pm/check-handover.mjs: exit 0.
  • check:pm-governed-prose and check:pm-half-states are not derived for this diff, so they did not run locally; CI runs the lint farm.

Generated by Claude Code

… back to pm:queue

Stay-behind stops being the default at shift end. The default is the
hand-back stroke of the handover checklist's step 2 (Release: with
Resume-from:, the assignee cleared, back to pm:queue); a needs_decision
card goes to the decision box instead. Stay-behind narrows to ACCEPTed
PRs waiting on the merge queue or a human approval, and the sign-off
sequence runs whatever words the maintainer used to end the shift.

Claude-Session: https://claude.ai/code/session_01QSeKYmhU5KEj1nukYhp1Tg
Co-authored-by: Claude <noreply@anthropic.com>
…hat waits; a held needs_decision card is refused by name

The sign-off hand-back rule narrows the declared stay-behind: (a) now
reports a declared tail only when an open pull delivering the card
carries an ACCEPT as its newest seat record and waits on the merge
queue (timeline), armed auto-merge, or a human approval (the PR's
needs-user-decision label). A card whose dev's newest os-dev-report,
newer than the claim, says needs_decision while the card is not in the
decision box is refused as [needs-decision-unreleased], with the
decision box as the remedy rather than pm:queue. Readers are imported
from their owners (check-half-states, seat-record, the queue guard).

Claude-Session: https://claude.ai/code/session_01QSeKYmhU5KEj1nukYhp1Tg
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Oct 29, 2026
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation labels Oct 29, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:skills#2 · os-justin · session_01QSeKYmhU5KEj1nukYhp1Tg

Contract-review-claim: session_01QSeKYmhU5KEj1nukYhp1Tg · head d457c56 · 2026-10-29T04:05Z

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:skills#2 · os-justin · session_01QSeKYmhU5KEj1nukYhp1Tg

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: d457c56d296843d4d894d45c2b15ace0689e9753
Local-runs: probe — the seat ran, in a detached read-only worktree of the head (no edit, no network write): node scripts/pm/check-handover.mjs --self-test → ✓ 66 cases (46 on main), node scripts/pm/post-stamped.mjs --self-test ✓ (it imports check-handover's marker reader), check-skill-line-ratchet.mjs → seat-post-protocol.md 88 / 88, seat-lifecycle.md 96 / 96, check-skill-id-lint.mjs ✓ 105; the six edited lines measured 109 / 119 / 114 / 118 / 120 / 120 B; the live --probe and the departed-seat run were the dev's (read-only GETs)
Scope: .claude/skills/pm-dispatch/references/seat-post-protocol.md (L35 rewritten, new L36 paid by old L40, L39 rewritten, checklist step 2 L47 rewritten) + .claude/skills/pm-dispatch/references/seat-lifecycle.md (L40, L43) + scripts/pm/check-handover.mjs (reportedStatus, decisionReportHeld, acceptedPullWait, waitShortfall, the narrowed stay-behind arm, the new refusal [needs-decision-unreleased], the (c) coverage line, battery 46 → 66) + cited homes read on the head: seat-lifecycle.md L14 (「简报即最新事件 ⇒ 立即坐席」, the dropped 继任 clause), seat-record.mjs (SEAT_RECORD_MARKER, seatRecordAccepts), check-governed-queue-guard.mjs (enqueuerFromTimeline), SKILL.md L189 (② a hanging Tier H PR carries needs-user-decision)

Claim line 6487607811 on this head (2026-10-29T04:07Z); no earlier record or claim on it. Read at 2026-10-29T04:07Z by the domain:skills#2 seat on the fetched head (two commits on merge-base 28872e68): three files, +236/−33 (269 ≤ 3000; check-governed-merges --pr 27371: two .claude paths ⇒ Tier S; scripts/pm/** off the register). The claim 6486925160 served TIER_DEFAULT; this review is served at CONTRACT_REVIEW_TIER (governed rules text).

① Derived judgments

  • Card item 1 (hand-back in one stroke): seat-post-protocol L35 「收班默认交回(〈交接收尾清单〉第 2 步);留守只剩已 ACCEPT 待队列或待人批的 PR。」 + step 2 L47 「认领卡落地,或同笔 Release:+Resume-from:+清 assignee 交回 pm:queue;needs_decision 进决策箱。」 — the stroke's three parts named, pm:queue the destination, the checklist now the rule for 收班 and not only 交接. Holds.
  • Item 2 (needs_decision → decision box, never left pm:dispatched): step 2's last clause + the script's new refusal [needs-decision-unreleased] — the newest os-dev-report after the session's newest claim with status: needs_decision while the card lacks needs-user-decision, refused by name with the decision box as remedy; triage's ⚠️ (not pm:queue) honoured. Holds.
  • Item 3 (stay-behind narrows): L35's second half, new L36 「留守写面 = 简报点名的这些 PR,残余定时器同界;留不了才热移交:收单注记 + 点名看护者。」, seat-lifecycle L43 「离任留守只写点名的已 ACCEPT 待队列或待人批 PR,…」; in the script acceptedPullWait: an ACCEPT as the newest ## 席位核对 record on an open pull delivering the card (REWORK after ACCEPT ⇒ not accepted) AND waiting — in the merge queue (timeline enqueuer), armed (auto_merge), or needs-user-decision on the pull (SKILL.md L189's spelling of 「等人批」); a declared stay-behind that is not waiting is refused with waitShortfall's sentence. 「落地中的评审链载体」 removed from the stay-behind set as item 3 asks. Holds.
  • Item 4 (last acts regardless of wording): L39 「收班序列不看维护者措辞:check-handover.mjs 退出 0 → 刷正文与标题 → 简报最终一笔。」; the dropped 「继任的释放判据 = 简报在」 lives at seat-lifecycle L14. seat-lifecycle L40 「收班 = 状态 flush + 在飞默认交回队列、不等落地;…」. Holds.
  • Payments: old L40 (留守写面 / 评审链载体 / 定时器) → its surviving content in new L35–36; net 0 lines both files; every line ≤120 B (two at exactly 120). Holds.
  • Readers imported from their owners (seat-record.mjs, check-governed-queue-guard.mjs, post-stamped.mjs), no second implementation. Holds.

② Semver level

  • None owed: .claude/** + scripts/pm/** publish nothing; skip-changeset correct; Clause-②: no, Metadata-protocol: none line-initial, matching the claim.

③ Boundary flags

④ Design judgment

Implemented-by: claude/issue-27359-sign-off-hands-cards-back
Reviewed-by: session_01QSeKYmhU5KEj1nukYhp1Tg

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:skills#2 · os-justin · session_01QSeKYmhU5KEj1nukYhp1Tg

维护者速读 — 席位终稿 · PR #27371 · head d457c56d · domain:skills#2 · 2026-10-29T04:13Z

席位核对 — ACCEPT · domain:skills#2 · session_01QSeKYmhU5KEj1nukYhp1Tg · 2026-10-29T04:13Z

  1. 形态:draft → main,首行 Fixes #27359,Clause-②: no、Metadata-protocol: none 行首在;三文件 +236/−33(269 行 ≤ 3000);assignee 空(席位令按 skills(os-dev, pm-dispatch): a seat-driven PR is not assigned to the seat's account at the draft open; it reaches a human only after ACCEPT + contract PASS, as a review request to os-zhuang/hotlong when Tier H #27363 上你的裁决「应该是开发完了你审核通过才会推送给我审核」免去 pr_create 的 assignees 腿;dev 点名了与今日 os-dev.md L37 的冲突并按令执行;认领评论漏写此裁决,由契约复核记录 6487643603 ③ 补正);skip-changeset 正确;两 commit;作者 objectstack-fleet[bot] 经中继,mcp_calls: 0;dev 报告首行带署名。
  2. 前提实测:席位在 head 的 detached worktree 复跑 check-handover 自测 66 ✓、post-stamped 自测 ✓、ratchet 88/88 · 96/96 ✓、id-lint 105 ✓;六行字节 109/119/114/118/120/120;付账行(旧 L40)内容在新 L35–36;「继任的释放判据」之家 seat-lifecycle L14 读到;check-governed-merges --pr 27371 ⇒ Tier S;卡的实测实例已移动(validate/lint/build refuse a lookup or record_change trigger to an object provided by a package in manifest.dependencies — the publish gate already resolves that closure #25805 已进决策箱、test budget (domain:cli): 119 test file(s) over 10 s of wall in the hourly full battery (Part of #23151) #23181 认领换了会话),规则按维护者原话仍成立。
  3. 门禁 check-runs(head d457c56d,本笔读数 2026-10-29T04:13Z):46 check-runs,26 success · 20 skipped · 0 in progress · 0 failure;20 个 skip 由 check-expected-skips.mjs --check-runs-json 判定全部在册,exit 0;dev 本地派生 36 族全绿(--ran 36/36)。
  4. 接受的偏差:① pr_create 无 assignees(见 1);② 令中假设 2–4 被实测修正(ACCEPT 读法归 seat-record.mjs;报告须晚于本会话最新认领;governed-prose / half-states 未派生);③ 旧 L40 删除、新 L36 承接而非改写 L36–37;④ 为一条全树门禁前置构建了 spec/formula/lint 闭包(非本卡所欠)。
  5. 范围外去向:none filed;Acceptance notes:check-handover.mjs --self-test 沿用无 battery floor 的旧形状(本地脚本,无 CI job);os-dev.md L37 仍写 assignees(由在飞的 skills(os-dev, pm-dispatch): a seat-driven PR is not assigned to the seat's account at the draft open; it reaches a human only after ACCEPT + contract PASS, as a review request to os-zhuang/hotlong when Tier H #27363 承接);已交回队列、问题仍开的卡不在 (a′) 内,由 H52 报。Skills-docs: 不欠(kind none)。
  6. 落地条款:Tier S;契约复核 PASS 评论 6487643603(head d457c56d,认领行 6487607811 先落);全绿即席位经中继 pr_ready + automerge_enable 落地(本班维护者「你应该按照 pm-dispatch 技能审核并落地pr」在案);MERGED 后关卡摘 pm:dispatched。

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 29, 2026 04:14
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 29, 2026
Merged via the queue into main with commit d34533c Oct 29, 2026
47 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-27359-sign-off-hands-cards-back branch October 29, 2026 04:35
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

Development

Successfully merging this pull request may close these issues.

pm-dispatch(sign-off): a seat going off shift hands every unlanded claimed card back to pm:queue, instead of keeping it as stay-behind

1 participant