Repository navigation
close-cards: one card = one write — pack comment → pm:* strip → close into ONE fleet-write write set (write-pace cap unchanged, closing throughput ×3) #19824
Description
Activity
- addedpriority:p1High: required for production / M2High: required for production / M2and removed
on Sep 23, 2026 objectstack-fleet commented
on Sep 23, 2026 ContributorAuthorMore actionsClaim: PM loop round 1
Session:session_01A22sUB3mUWs6M36VgfijBq
Branch:claude/issue-19824-close-cards-one-dispatch
Worktree:objectstack-issue-19824
Domain:domain:skills
Seat:domain:skills#1
File surface:scripts/pm/close-cards.mjs(the landing spot, its own--self-testincluded) ·scripts/pm/fleet-write/{ops,dispatch,execute,validate}.mjsonly if the per-card packing needs a helper besidedispatch.mjs·scripts/pm/post-stamped.mjs/scripts/pm/label-write.mjsonly for an export the pack reuses, no behaviour change ·package.jsononly if acheck:pm-*self-test row is owed for a new script (stop on breach; explain in the report)
Container & model:M — design judgment on the pack shape, correctness judged by the offline --self-test, --dry-run and the four-step read-back,mode:subagent,model: opus — dispatch-gates --tier --repo objectstack-ai/objectstack at 30854f4 prints: "Model tier — no path-derived mandate: the surface hits none of the 3 declared glob(s), derived here, not recalled. The tier stays the PM's per-card judgment call (floor sonnet · default opus · ceiling fable)."
Clause-②: no
Thread-read: none
Serial constraints cleared: none — of the 21 open PRs of this repo at 2026-09-23T13:12Z, none changes a file underscripts/pm/(each PR's file list read); no branchclaude/issue-19824*onorigin; the newest landings on the surface arecc2e951(fleet-write fails closed on an indeterminate liveness read) and628e55d(the label vocabulary) of this date, so the dev works on the merged code; #13597 (tooling+pm:dispatched, zhuangjianguo) is the maintainer's own program — noClaim:, no branch, no dev — read as not a fleet dev in flight for the one-tooling-card rule, and reported to the maintainer in the round report; no sibling card pins this card's behaviour.Take basis: the maintainer's direct instruction recorded on the card with 出处三件 — 「甲,先立一张开发工具卡」 (the triage seat's chat, this date) — the take order's first rung; p1 by that word, so 北极星「优先级」第 3 条 (1 product P0 · 28 product P1 open in objectstack at 2026-09-23T13:09Z) does not hold it. Premise re-read on
origin/main30854f44a6at 2026-09-23T13:09Z:close-cards.mjsstill spends three relay dispatches per card (post-stamped child,runLabelWrite, then the close as ONE dispatch — its own docblock at :115–:123), no--planflag exists, #19572 and #19469 are closedcompleted— every work item of the card is still open. The dev is dispatched as a subagent of this session with this comment as its only claim.
Generated by Claude Code
objectstack-fleet commented
on Sep 23, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 19824,
"status": "done",
"branch": "claude/issue-19824-close-cards-one-dispatch",
"pr": "#19873",
"session": "session_01A22sUB3mUWs6M36VgfijBq",
"premise_still_valid": true,
"summary": "Premise held at origin/main 48c91e9 (one commit past dispatch, no scripts/pm change): close-cards spent three relay dispatches per card, no --plan existed. scripts/pm/close-cards.mjs now packs each actionable card into ONE fleet-write dispatch carrying exactly [comment, labels_remove, issue_patch] (PACK_ORDER), one card per dispatch, so write-pace counts one write per card with DEFAULT_HOURLY_MAX and the gap untouched. --plan FILE (rows N|REASON|COMMENT-FILE) replaces --list/--comment/--reason (retired flags refused by name); a plan naming a card twice, sharing one comment file, or carrying the same text on two cards (whitespace collapsed, digit runs masked) is refused before any request. Pre-flight reuses post-stamped's renderBody/claimKeyedLineRefusals per row before card one and again at pack time; every row is packed once through the relay validator before card one (64KB client_payload ceiling, body cap); step 4 reads the BOARD back (card state/reason/labels via label-write's computeLabelTarget/readBackVerdict/verdictIsClean, comment via post-stamped's pickRelayComment/readBackVerdict) and must MATCH; a failed run is read back to name what landed (exit 5 none, 4 some); no-run/timeout is UNCONFIRMED exit 6, never retried and never fallen back to direct (even under auto: the pack's first action is a comment). The strip is the pm-state plus imported PM_RESIDUE_LABELS on the card (pm:seat/pm:epic stay). Direct transport keeps its three tool-owned writes; its post-stamped child is now handed OS_FLEET_TRANSPORT=direct. runLabelWrite binds step 3 and step 4 (label-write.mjs:637), so the pack reuses its exported pure halves and deliberately does NOT borrow its step-4 re-add (a write, and direct under the relay — finding below); no change to label-write.mjs, post-stamped.mjs or fleet-write/. Optional item 6 (grading mode) not done. PR opened with first line 'Part of #19824' per the seat's course correction; the card's live one-card run is the seat's.",
"files_changed": [
"scripts/pm/close-cards.mjs (+831 / -258, one commit c474dc5)"
],
"gates": [
"node scripts/check-ci-filter-parity.mjs :: exit 0 :: OK: all 185 declared cross-package glob(s) (132 unique) are covered bycoreorcrosspkg, everycrosspkgentry still covers one, and thetestj",
"node scripts/check-closing-keyword-parity.mjs :: exit 0 :: • packages/spec/CHANGELOG.md -- 6080503 bytes exceeds the sweep's 2097152-byte cutoff for UNREGISTERED files",
"node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0 :: ✓ check-closing-keyword-parity --self-test: 40 assertions, 5 mutations of the shipped parsers each driven to red.",
"node scripts/check-comment-mask-corpus.mjs :: exit 0 :: ✓ comment-mask corpus sweep [scripts/js-comment-mask.mjs]: 7039 files, 0 disagree, 0 unparseable, 65.3s (comparator self-test: 26 cases pass).",
"node scripts/check-declaration-mirrors.mjs :: exit 0 :: (Parameter and return TYPES are not asserted — see this file's header.)",
"node scripts/check-declaration-mirrors.mjs --self-test :: exit 0 :: All 29 self-test cases passed.",
"node scripts/check-scripts-symbol-anchors.mjs :: exit 0 :: ✅ check-scripts-symbol-anchors: 3660 anchors across 280 scripts resolve — 52 symbol (52 declaration, 0 literal), 3608 file-level, 0 cross-repo, 1 exem",
"node scripts/check-scripts-symbol-anchors.mjs --self-test :: exit 0 :: ✅ check-scripts-symbol-anchors --self-test: every finding class provoked, comment-prose projection wired, declined shapes counted not missed, allowanc",
"node scripts/check-self-test-wired.mjs :: exit 0 :: scope: 315 file(s) under scripts/, 246 carrying--self-testin code (comments masked, 0 of them package-local gate(s) CI names by path); 229 of th",
"node scripts/check-self-test-wired.mjs --self-test :: exit 0 :: check-self-test-wired --self-test: 3 live ledger row(s) verified, plus the comment mask, the right boundary, alias resolution and both audit direction",
"node scripts/check-self-test-workflow-commands.mjs :: exit 0 :: scope: 229 script(s) CI runs ship a--self-test(0 of them package-local gate(s) CI names by path, present because this population is the one chec",
"node scripts/check-self-test-workflow-commands.mjs --self-test :: exit 0 :: check-self-test-workflow-commands --self-test: both measured parse rules pinned (legacy form anywhere in a line, current form only at line start), the",
"node scripts/check-whole-set-label-write.mjs :: exit 0 :: Theuses:limb is not vacuous: 196 pin(s) were judged and cleared on the rule.",
"node scripts/check-whole-set-label-write.mjs --self-test :: exit 0 :: ✓ check-whole-set-label-write --self-test: all cases pass (24 fixture trees + 5 refusals + 1 allowlist hatch)",
"node scripts/pm/bare-root-worklist.mjs --self-test :: exit 0 :: OK self-test: 81 live row(s), 59 unreachable as spelled, 46 recorded verdict(s) — none stale, none missing, none contradicted (12 row(s) whose gate c",
"pnpm check:agent-test-spelling :: exit 0 :: which drives this same sweep RED over a temp tree on disk.",
"pnpm check:bash32-floor :: exit 0 :: census: 31 by .sh extension, 2 by shebang alone; 19 constructs checked, floor bash 3.2.",
"pnpm check:cli-command-ids :: exit 0 :: ✓ check-cli-command-ids: 63 module(s) under packages/cli/src/commands examined, all of them default-export a class whose inheritance chain reaches ocl",
"pnpm check:cross-package-test-inputs :: exit 0 :: OK: 29 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob (6 of them on a split "test:repo" task); 13 walked",
"pnpm check:driver-memory-census :: exit 0 :: check-driver-memory-census: OK — every declaration is ledgered, every ledger entry is live, and every ruled file states "#6664 census: 2 ruled consume",
"pnpm check:entry-guard :: exit 0 :: ✓ check:entry-guard: 280 scripts/ file(s) — every entry guard goes through invoked-as.mjs; 219 export bindings, 219 of them inert on import (0 known-u",
"pnpm check:gitlink-declared :: exit 0 :: check-gitlink-declared: OK (9322 index entries -- 0 gitlink(s) at mode 160000; no .gitmodules in the index, so nothing is declared; nothing to declare",
"pnpm check:nul-bytes :: exit 0 :: check-nul-bytes: OK (scanned 9315 text file(s) -- 9315 tracked, 0 untracked-not-ignored; skipped 7 binary; no raw ASCII control bytes).",
"pnpm check:parse-guard :: exit 0 :: Shape decision: see the header.",
"pnpm check:pm-close-cards :: exit 0 :: OK close-cards self-test: 173 cases pass across 18 batteries — offline, no network, no token.",
"pnpm check:pm-write-pace :: exit 0 :: ✓ write-pace self-test: 113 cases pass across 12 batteries — the per-token key that never carries the token, the gap remainder, the 41st refusal and i",
"pnpm check:pnpm-filter-targets :: exit 0 :: ✓ check:pnpm-filter-targets: 154/209--filteroccurrence(s) across 42 file(s) resolve against 81 workspace package(s); 55 not judged (2 foreign, 30",
"pnpm check:ratchet-remedy-authority :: exit 0 :: OK check-ratchet-remedy-authority: 270 scripts swept (scripts/.{mjs,mts} + scripts/pm/*.{mjs,mts}); 15 mark the expanding remedy ⛔ MAINTAINER-ONLY,",
"pnpm check:refd-timer-probe :: exit 0 :: 1 code site(s), all inside the approved module, which is present and still reads it.",
"pnpm check:watch-hint-literal :: exit 0 :: ✓ check-watch-hint-literal: 71 declaration(s) across 4 rostered name(s) -- ROOT_DIR_WATCH_HINTS 47, ROOT_FILE_WATCH_HINTS 13, ROOT_WATCH_HINTS 3, DECL",
"pnpm check:pm-post-stamped :: exit 0 :: ✓ post-stamped self-test: 610 cases pass across 21 batteries — offline, no network, no token.",
"pnpm check:pm-label-write :: exit 0 :: ✓ label-write self-test: 91 cases pass across 10 batteries — the union/difference arithmetic, the ONE-OF refusal, the idempotent DELETE, the stripped-",
"pnpm check:pm-fleet-write-validate :: exit 0 :: ✓ fleet-write/validate self-test: 74 cases pass across 7 batteries — a closed envelope, one organization, closed keys per op with typed values, the pl",
"pnpm check:pm-fleet-write-execute :: exit 0 :: ✓ fleet-write/execute self-test: 48 cases pass across 9 batteries — the sender gate from the target repo's answer, every op as the request the table d",
"pnpm check:pm-fleet-write-dispatch :: exit 0 :: ✓ fleet-write/dispatch self-test: 101 cases pass across 11 batteries — the transport selector that never guesses, the session on the envelope, one pac",
"pnpm check:pm-settings-deny-roster :: exit 0 :: ✓ check-settings-deny-roster: 17 content-write tool(s) declared = enforced in .claude/settings.json (17 mcp__github__ deny entr(ies), 0 outside this g",
"pnpm check:pm-dispatch-gates :: exit 0 :: 'dispatch-gates self-test: 1905 cases pass.' / 'the battery took 892.1s on this box.' (run detached per the gate's own header prescription for a capped container, PID 11453 recorded, joined by a foreground tail --pid wait; two earlier foreground attempts were killed by MY timeout at 590s/400s, 0 failures printed — not verdicts)",
"node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran ran.list :: exit 0 :: 'Run reconciliation — 31 derived, 31 run, 0 NOT-MEASURED, 0 UNRUN.' (derived at c474dc5 = HEAD; 31 derived + 6 dispatch-named = 37 commands run)"
],
"tests": "HEAD c474dc5 (git rev-parse --short HEAD after the last commit; every gate above ran at this head). (1) node scripts/pm/close-cards.mjs --self-test -> exit 0, 'OK close-cards self-test: 173 cases pass across 18 batteries — offline, no network, no token.' (was 111/12). Acceptance cases as named batteries: 'the pre-flight refusal: a comment refused before the pack is ZERO writes on its card' (7: plan refused before any request; relay: exit 2, 0 dispatches, 0 reads; direct: 0 comments, 0 label writes, no PATCH); 'the size ceiling: a payload over 64KB is refused before it is packed, zero writes' (6: fixture 40,000 double quotes = under the 60,000-byte body cap, over 65536 serialised; exit 2, 0 dispatches, 0 reads, both cards untouched); 'the skip log: a skipped card gets no payload, and the output names why' (8: assignee / pm:retriage / pm:dispatched vs pm:queue / open PR #9001 each named, ONE payload for the one actionable card). Dry run under the relay: one payload per actionable card, exactly comment -> labels_remove -> issue_patch, printed as the JSON that would be sent, 0 sent. (2) Mutation legs via node scripts/ablation-replace.mjs (anchor 1->0, blob changed, restore 'blob == HEAD (60a9725760bd) and git diff HEAD is empty'), each red on its target: M1 drop the pre-card pack preview -> 2 of 173 fail (size ceiling); M2 burst rule off -> 5 fail; M3 step 4 ignores the label verdict -> 2 fail; M5 strip only the pm-state -> 1 fail. (3) Live read-only: close-cards --repo objectstack-ai/objectstack --plan PLAN --dry-run on #19824, #13597, #19572 -> exit 0, re-exec via --use-env-proxy, 'transport dispatch', pack preview accepted all 3 rows, SKIP lines 'has an assignee (os-zhuang)', 'has an assignee (zhuangjianguo)', 'not open (state closed/completed)', summary '3 read · 0 actionable · 3 skipped · 0 payload(s) packed, 0 sent'; write-pace.jsonl 6 records before and after, 0 stamped inside the run window (write-pace +0). Live refusals: --list -> exit 2 naming --plan; a 2-card plan whose texts differ only by card number -> exit 2 burst refusal, 'NOTHING was read and NOTHING was written'. NOT MEASURED: live payload print on an actionable card — reason: every card this dispatch names is assigned or closed and finding an actionable one needs a board pull the dev contract forbids; covered offline by the relay-pack battery. NOT MEASURED: live one-card run — reason: the seat runs it on #19824 after MERGED.",
"mcp_calls": "0 — no MCP GitHub tool called",
"api_writes": "3 — each one fleet-write relay dispatch (POST /repos/objectstack-ai/objectstack/dispatches, paced): (a) pr_create -> POST /repos/objectstack-ai/objectstack/pulls (PR #19873, draft forced; run 35872756539 success; body read back byte-identical, 11496 bytes); (b) label-write --add skip-changeset on #19873 -> labels_add POST /repos//issues/19873/labels (run 35872854916 success; step 4 MATCHES, size/xl noted as another actor's add); (c) this os-dev-report -> comment POST /repos//issues/19824/comments via post-stamped. git push (x2: empty-branch probe, c474dc5) not REST. Reads: single-card GETs #19824 (+comments), #13597, #19440, #19408, #19404, #13799, #19325, and the PR lookup by head.",
"deviations": [
"First commit was made with the harness's model-named Co-Authored-By trailer; amended BEFORE any push to AGENTS.md's model-free pair (Claude-Session + Co-authored-by: Claude). Never pushed; pre-push check:commit-card-trailers green on c474dc5.",
"The dispatch prescribed the closing keyword on this card as the PR's first line; per the seat's mid-task course correction the PR opened with the non-closing 'Part of' line plus the safe remaining-half sentence. No PATCH needed.",
"check:pm-dispatch-gates exceeds the ~10 min foreground cap (battery 892.1s): run detached (setsid nohup, PID 11453 recorded) and joined by foreground 'tail --pid' waits, the invocation its own header prescribes. The PR body (written before it finished) says NOT MEASURED pending and points here; its verdict is exit 0. PR body not PATCHed.",
"Design choices beyond the card's text, each argued in the PR body: no fall-back to direct on an auto no-run (UNCONFIRMED exit 6 instead; the old close-only path fell back); the strip widened from the pm-state alone to pm-state + imported PM_RESIDUE_LABELS; the direct path's post-stamped child is forced to OS_FLEET_TRANSPORT=direct.",
"A temporary debug line (battery-name print) was inserted to find a self-test hang and removed before the commit; never committed."
],
"open_questions": [],
"out_of_scope_findings": [
"class: a · CLAIM_COMMENT_MARKER (scripts/pm/check-half-states.mjs:1188 @ 48c91e9) backtracks cubically on a run of blank lines: its two optional-whitespace runs both match newlines. Bare regex at c474dc5: 500 newlines 53 ms, 1000 456 ms, 2000 3410 ms; via post-stamped claimKeyedLineRefusals (:1916) 2000 -> 6.5 s, 10,000 did not finish in 100 s. Failing probe: markerMatches(CLAIM_COMMENT_MARKER, 'x' + '\n'.repeat(2000) + 'end.') timed. Reachable today: every comment body post-stamped --comment judges, and every comment markerMatches reads with this marker. · dedupe words: CLAIM_COMMENT_MARKER backtracking · claim marker regex blank lines slow · markerMatches cubic · claimKeyedLineRefusals hang",
"class: b · label-write step 4 re-add leaves the relay: under the dispatch route runLabelWrite re-adds a label stripped underneath with doCall('label-add') = the direct rest with the seat token (scripts/pm/label-write.mjs:733 @ 48c91e9), i.e. a write as the seat's own user in a cloud container. Contract: fleet-write/dispatch.mjs header, fail-closed paragraph — OS_FLEET_TRANSPORT=direct is the ONLY way to write as the personal account in a cloud container; label-write header says steps 1, 2 and 4 are reads the same on both transports. Offline probe: runLabelWrite(dispatch route, relay that strips 'tooling' in the run) records POST /repos//issues/5/labels on the direct call. Seam: contract:scripts/pm/fleet-write/dispatch.mjs (auto fail-closed) -> runtime:scripts/pm/label-write.mjs runLabelWrite step 4 | consumer: every label-write caller on the relay. · dedupe words: label-write re-add relay · stripped underneath direct write · runLabelWrite step 4 dispatch · re-add personal account",
"carrier: skills seat at landing (Tier S surface, outside this card's file surface) · .claude/skills/pm-dispatch/references/platform-readings.md:107 ('close-cards 每步一次 dispatch') and rest-channel.md:19 (close-cards failed run = 4) become false when #19873 lands: now one dispatch per card; failed run 4 when the board shows a step landed, 5 when none, 6 UNCONFIRMED · noted in PR Acceptance notes, not filed",
"carrier: 承接者:无 · .github/workflows/lint.yml 'Card-closure helper self-test' comment block still describes the three-step shape; its assertions still hold on the direct path · Acceptance notes only"
]
}objectstack-fleet commented
on Sep 23, 2026 ContributorAuthorMore actionsACCEPT —
domain:skillsseat 1 (session_01A22sUB3mUWs6M36VgfijBq, seat post #7623), 2026-09-23T14:33Z — PR #19873 (headc474dc5405, draft, basemain, first linePart of #19824), report 5796696557.Checklist (read on GitHub, not from the report): draft on
main,Part ofon the first line with the safe remaining-half sentence and no closing keyword beside any card number;Clause-②: noat line start; session-URL footer; 1 changed filescripts/pm/close-cards.mjs(+831 / −258),skip-changesetcorrect forscripts/pm/**(publishes nothing);check-governed-merges.mjs --pr 19873: 0 of 1 path governed, 1089 changed lines under the 5000-line limb ⇒ ordinary queue landing;mcp_calls0;api_writes3 (pr_create, one label-write, the report), all through the relay asobjectstack-fleet[bot]. Check-runs on the head at 2026-09-23T14:33Z: 18 success · 10 skipped · 2 in progress (Lint & Repo Gates,Type Check · workspace);Governed Surface Queue Guardand everyTest Coreshard success. Ready + auto-merge are armed only once those two readsuccess.Spot-check readings of the seat's own (the full diff read from
origin/claude/issue-19824-close-cards-one-dispatchbefore the report arrived): every symbol the diff imports already exists onorigin/main(computeLabelTarget:193 ·readBackVerdict:285 ·verdictIsClean:297 ·relayActions:315 in label-write;relayAction:2799 ·pickRelayComment:2810 ·PLATFORM_COMMENT_FOOTER:1960 in post-stamped;PM_RESIDUE_LABELS:6024;CLIENT_PAYLOAD_MAX_BYTES:128,TRANSPORT_ENV:90;EXIT_UNCONFIRMED:150,exitForResult:507), so the one-file diff is complete;PACK_ORDERis exactly comment → labels_remove → issue_patch andpackCardrefuses any other shape; one card per payload; the plan refuses a card named twice, a shared comment file and same-text-modulo-digits comments (burstKey); the pre-card pack preview refuses an over-ceiling payload before any read; step ④ reads the board (card + comments since the dispatch) and requires the label read-back to MATCH; no-run / timeout ⇒ exit 6 with no fall-back;--dry-runpacks and sends nothing. The self-test roster grew 12 → 18 named batteries with a floor of 18 and no total pin (AGENTS.md's--self-testshape). Optional item ⑥ (grading mode) not done, as allowed.Deviations, accepted:
check:pm-dispatch-gatesruns 892 s and was run detached per that gate's own header, joined in the foreground — exit 0 recorded on the report, the PR body's NOT MEASURED line predates it (not PATCHed, correct); the first commit's harness trailer was amended before any push; theFixes→Part offirst line is this seat's mid-flight course correction (the card's last acceptance step, the live one-card run, is the seat's after MERGED).Out-of-scope findings, dispositions: (a)
CLAIM_COMMENT_MARKERbacktracks cubically on blank-line runs (2000 newlines 3.4 s bare, 6.5 s throughclaimKeyedLineRefusals; 10,000 did not finish in 100 s) — real, reachable through every comment post-stamped judges; (b)runLabelWritestep ④'s re-add of a label stripped underneath leaves the relay as a direct write under the seat's token (label-write.mjs:733), against the dispatch header's fail-closed contract. Both are PM-tool defects with noUnblocks:product card and no published surface, so under ruling #202 B neither enterspm:queueas a card — noted here and on the PR's Acceptance notes, not filed; both go to the maintainer in this round's report with the seat's recommendation, and a 直派 word files them. (c) two.claude/**reference lines this PR makes stale (platform-readings.md:107 「close-cards 每步一次 dispatch」,rest-channel.md:19 the failed-run exit) — carrier: this seat's ledger, folded into the next skills PR that touches those files. (d) thelint.ymlcomment block — Acceptance notes only.Landing plan: on both remaining jobs green ⇒ ready → auto-merge SQUASH through the CCR route, queue landing; at MERGED the seat runs the tool from
mainon the two lane cards verified closable this fire (#18831, #18914 —--expect-state pm:blocked, one dispatch each) as the live acceptance run, then on this card (--expect-state pm:dispatched, after clearing the assignee) — thePart ofremainder.
Generated by Claude Code
objectstack-fleet commented
on Sep 23, 2026 ContributorAuthorMore actionsClosed
completedby thedomain:skillsseat 1 (session_01A22sUB3mUWs6M36VgfijBq, seat post #7623) at 2026-09-23T15:11Z — thePart ofremainder of PR #19873 discharged. The PR LANDED through the merge queue as04a160de45faa87a08c20ec8ae995dad32defd7a(single-parent squash, merged 2026-09-23T15:07Z);scripts/pm/close-cards.mjsonorigin/mainby content packs one card into one relay dispatch inPACK_ORDERcomment → labels_remove → issue_patch.The card's last acceptance step, the live run, was taken by this seat with the merged tool at 2026-09-23T15:09Z on the two lane cards verified closable this fire:
--planof two rows,--expect-state pm:blocked,--dry-runfirst (2 read · 2 actionable · 0 skipped · 2 payloads of exactly 3 actions each, 0 sent), then the run — #18831 closedcompleted(comment 5797367314, relay run 35879407614, ④ MATCHES) and #18914 closedcompleted(comment 5797379835, relay run 35879514056, ④ MATCHES); summary line 「2 read · 2 actionable · 0 skipped · 2 closed (completed 2) · 2 dispatch(es) sent — 2 write(s) on the throttle」; the write-pace ledger carries exactly two records stamped inside the run window (the seat's reading in its round report). This comment, thepm:dispatchedstrip and the close are the tool's third row, its own card, one dispatch. ACCEPT 5796770495 carries the review record; the two out-of-scope findings and the two stale reference lines are dispositioned there.
Generated by Claude Code
- added a commit that references this issue
on Sep 28, 2026
出处(三件)
session_01Tw7jnJinGHvoGSi8aFkhPJ)的会话里。write-pace的每小时 40 次上限不动,改为把每张卡的三步写入打包成一次写入。乙(调高上限)、丙(维持现状)都没被选中。现状 —— 本地
main上读到的scripts/pm/write-pace.mjs:230DEFAULT_HOURLY_MAX = 40,按令牌计、滚动 60 分钟;每两次写入之间至少 3 秒。这个上限的来历写在该文件头注里:两个席位账号在批量处理卡片时被封号,维护者裁定「截流应该要加」(Pace every seat write — one shared throttle in front of the five PM write scripts (maintainer 「截流应该要加」) #19572)。⛔ 本卡不碰这个数。scripts/pm/close-cards.mjs把一张卡的关闭拆成三次写入,每次都是一个独立的 fleet-write dispatch:post-stamped.mjs(评论)→label-write.mjs(摘pm:*)→ 关闭。⇒ 每张卡算 3 次写入 ⇒ 每小时最多约 13 张卡。fleet-write 本来就能做到
scripts/pm/fleet-write/ops.mjs:OPS里已经有comment、labels_add、labels_remove、issue_patch(改 state / state_reason);MAX_ACTIONS = 20,也就是一个 payload 最多可以带 20 个动作。scripts/pm/fleet-write/execute.mjs:「The FIRST failure stops the run — later actions are NOT attempted」。⇒ 在一个 dispatch 里按「评论 → 摘标签 → 关闭」的顺序排,「评论没落地就不摘标签、不关卡」这条保证原样保住,跟今天三次写入的效果相同。client_payload小于 64KB 的上限,在ops.mjs里已经钉住,打包的时候要检查。⇒ 每张卡打包成一个 dispatch,
write-pace里只算 1 次写入 ⇒ 上限不变,每小时约 40 张卡。此外,由用户账号令牌直接发出的请求也少了三分之二,对封号风险是减少,不是增加。要做的
close-cards.mjs支持每张卡一个 dispatch:[comment, labels_remove(该卡当前的 pm:* 标签), issue_patch(state=closed, state_reason)],经fleet-write/dispatch.mjs发出。--comment是整批共用一份,而维护者的常驻规则是 ⛔「never post the same comment text on many cards in a burst」,每张卡的裁定评论也本来就不一样。形状建议(不作裁定):--plan FILE,每行n|reason|comment-file。post-stamped的预检:复用它导出的纯函数(renderBody、claimKeyedLineRefusals),NOW/WAS盖章令牌的规则和拒绝逻辑照旧生效,在打包前各跑一次。⛔ 不要另写一套盖章逻辑。label-write的四步核对:① 读当前标签(席位侧读取)② 算出目标标签 ③ 写入(也就是 dispatch 里的labels_remove)④ 运行结束后读回,必须 MATCHES 目标。⛔ 第 ④ 步不能因为打包而省掉。close-cards现有的逐卡实时闸门:打包之前逐卡检查,有负责人、有pm:retriage、状态不符合--expect-state、或者有开着的 PR 引用它,就跳过这张卡。⛔ 不做
DEFAULT_HOURLY_MAX,也不改最小间隔。验收
--dry-run:每张卡打印一个 payload,里面恰好 3 个动作,顺序是 评论 → 摘标签 → 关闭。--self-test(离线):必须覆盖三种情形 ——pm:retriage、状态不符、有开着的 PR 引用 ⇒ 跳过这张卡,并在输出里写明原因。write-pace --status的计数只 +1;评论、标签、关闭三个效果都能读回;第 ④ 步读回结果是 MATCHES。路由与级别
scripts/pm/**⇒domain:skills(同 #19572)。tooling。priority:p1:这张卡是维护者当面点名要立的,和它的前身 #19572(同样是维护者裁定的写入节流,p1)同级。docs/NORTH-STAR.md「优先级」第 3 条在产品 P0/P1 开着时,不派 p2/p3 的工具卡;本卡按维护者的直接指令定为 p1。Related: #19572(节流)·
scripts/pm/close-cards.mjs(#19469)·scripts/pm/fleet-write/{ops,dispatch,execute}.mjs。