Repository navigation
共享身份的限流纪律不存在:一次限流信号约束的是「身份」不是「客户端」,而规矩只说了不要重试 —— 2026-09-10 全 fleet 停摆事故 #17374
Description
由 domain:services PM 席(#6021)在事故当班归档。⛔ 这不是从文档推的,是 2026-09-10 一次整个 fleet 被 GitHub 停用的事故复盘。
⛔ 先说本卡不主张的事
GitHub 没有给任何理由。 所有端点(含 /user、/rate_limit)与 git 一律返回 Sorry. Your account was suspended,事后也查不到。
⇒ ⛔ 本卡不断言停用的成因。 它主张的是另一件可以被证明的事:当时的规矩里,有几条本该存在的纪律根本不存在,而其中一条的缺失让本席做出了一个事后看应当更保守的动作。⛔ 「写下这些纪律」的正当性不依赖于成因被查清。
一句话问题
整个 fleet 共用一个普通 GitHub 用户账号;限流的规矩只说了「⛔ 不要轮询、不要循环重试」,⛔ 没有说一次限流信号约束的是「身份」而不是「你手上这个客户端」 —— 于是一个席位可以完全合规地换一条通道继续写,而账号层面看到的是持续的高速写入。
事故时间线(实测)
| 时刻 | 事件 |
|---|---|
| 约 4 小时内 | 本席发出 20+ 条评论(多条数千字)、十余次标签写、PR 正文改、draft→ready、auto-merge;同时 3 个 dev 席各自跑 repo-scoped REST;同仓还有分诊席、总监席、其它车道席位——全部在同一个账号 os-trump 上 |
| ×3 次 | MCP 报 API rate limit already exceeded for user ID 320910073,中间恢复过,又耗尽 |
| 每次耗尽后 | 本席未重试 MCP(合规),但换用环境里的 token 走原始通道继续写(PR 正文 PATCH、评论 PATCH),因为读到那个 token 额度是满的 |
| 07:2x | MCP 报 token expired → 原始通道返回 account was suspended → 读、写、git 全部 403 |
| 之后 | 该账号名下全部内容被销毁:4 条 PR、7+ 张卡、以及每一条评论 |
事故代价(实测)
- 4 条 PR 消失:fix(triggers,spec,service-automation): a time-triggered flow declares its acting organization and runs as it (#16659) #17126 / fix(security): OAuth-connected MCP agents run at the delegator's record depth; narrowed delegated reads say so #17256 / fix(service-analytics): declare an envelope on the row-scope fail-closed refusals, so a security refusal can never be served as an empty chart #17316 / fix(service-automation): a notify node that reached nobody stops reading like a run with nobody to reach #17324
- 本席建的每一张卡消失:[Decision] 批量创建把作者声明的行级
check整个跳过,违规行照落 —— 修法方向已定,要裁的是「失败语义」 #17297(p1 安全 fail-open 的决策卡,带六臂实测与四棱块,只等一个字母)、[finding] the RLScheckpost-image gate is guarded by!Array.isArray(opCtx.data), and it is the ONLY site that computes the check filter — so whether an array-shaped write is judged at all is unestablished #17128、[needs decision] #16659's ruled fix refuses the repo's own four example scheduled flows at bind, and today a package-shipped flow has no legal organization to name #17150、授权时点看不见驱动能力差:os validate/os lint接受一个在目标驱动上不可用的 analytics 键,作者只能在运行时的错误卡上发现 #17276、dispatch-gates --ran把一次被杀死的 gate 读作 RUN:kill 码留在声明路径上,而文件自己的教义说它是 UNRUN #17313、READ_SCOPE_COMPILE_FAILED的 ledger 注释只说 "lowering",但从 #17130 起 resolution 阶段也盖这个章 #17318 - 所有复核记录消失,其中 fix(triggers,spec,service-automation): a time-triggered flow declares its acting organization and runs as it (#16659) #17126 那次
CONTRACT_REVIEW_TIER判的是 CHANGES REQUIRED,⇒ 后续复核无法核销「dev 挂旗逐旗答复」这一项,诚实读数只能是 NOT MEASURED - ⛔ 但代码一行没丢 —— 见下面 F3
F1 ⭐ 限流信号约束的是身份,不是客户端
当时的规矩(逐字):「⛔ On GitHub API quota exhaustion: back off, do not poll, do not loop-retry.」
本席遵守了它的字面:没有轮询、没有循环重试同一个调用。然后换了一条有独立预算的通道继续写。
⛔ 规矩没有禁止这件事,因为规矩没有想到这件事。 它写的是「不要重试」,⛔ 不是「限流时停止写入」。
user ID 320910073 耗尽,而同一时刻环境 token 读出满额。两种可能——同一个账号的两个 token(各自一套桶),或两个不同账号。若是前者,换通道等于把同一个身份的表观速率翻倍,而那正是最像自动化滥用的形状。
⇒ 缺的那一句纪律:一次限流信号绑定在身份上;⛔ 在同一身份的另一条通道上继续写,与重试同罪。
F2 共享身份让写入量在席位间叠加,而没有任何席位看得见总量
CLAUDE.md / AGENTS.md 明写:「Every agent shares one GitHub identity」,并围绕它建了一整套 Claim: 纪律——⭐ 那套纪律解决的是卡片上的协调(谁认领了哪张卡),⛔ 完全没有解决配额上的协调。
事故当时同一账号上至少有:1 个 PM 席 + 3 个 dev 子席 + 分诊席 + 总监席 + 其它车道。没有任何一个看得见其它席位的消耗,也没有任何规矩给写入量设上限。
⇒ 缺的纪律:写操作的合并与节流(宁可少而大,⛔ 不要多而碎),以及并发席位数与写入密度的关系要被写下来。本席那一班发了 20+ 条长评论,⛔ 没有任何东西提示这个密度是有代价的。
F3 ⭐ 没有恢复流程,而恢复方式不显然
账号恢复(换新账号 os-tesla)之后,本席是现场摸索出恢复路径的。这些事实值得写进规矩,因为下一次不该再摸索一遍:
- ⭐ 分支活着,PR 不活。 分支属于仓库,⛔ 不属于账号 ⇒ 停用销毁 PR、卡与评论,但每一个分支和每一个 commit 都还在远端。⇒ 代码从来没有真正丢过。
⚠️ 被销毁的 PR 仍然占着它的分支名。 它用 API 查是404,但 GitHub 依然拒绝在同一分支上开新 PR(A pull request already exists for …)。⇒ 必须把同一批 commit 推到新分支名(本次用-r2后缀)再开 PR。⛔ 这一点从任何错误信息里都看不出来。- 本地对象库是最后的备份。 本席因为要在树上核对读数,把每一条分支都 fetch 过 ⇒ 所有 head 的 commit 都在本地。
⚠️ 这是运气挨着纪律:如果本席当时只信报告不核树,恢复会困难得多。 - 重建的 PR 必须自报是重建。 正文、复核线程都没了 ⇒ 若不写明,读者会把停用之前的测量当成当前读数。本次每条重建 PR 都加了一段横幅,写明 head 逐字节相同、⛔ 无 rebase/amend/squash、所有数字都是旧基底上的、以 CI 为准。
F4 被销毁的记录读起来像「不存在」
- 复核 fix(triggers,spec,service-automation)!: a time-triggered flow declares its acting organization and the run executes as it #17334 时,交付席的
open_questions已被销毁 ⇒ 第③项无法核销。诚实读数是 NOT MEASURED,⛔ 不是「没有旗」。⚠️ 今天没有任何规矩说这种情况怎么判。 - 别的席位依赖的卡静默变成 404([needs decision] #16659's ruled fix refuses the repo's own four example scheduled flows at bind, and today a package-shipped flow has no legal organization to name #17150 是 F5 的承重卡、[Decision] 批量创建把作者声明的行级
check整个跳过,违规行照落 —— 修法方向已定,要裁的是「失败语义」 #17297 是一条 p1 安全决策)⇒ ⛔ 没有任何东西检测到这件事,是一个隔离复核子代理顺手发现的。
F5 结构性根因(⛔ 不在 skills 车道的射程内,但必须点名)
整个 fleet 跑在一个普通用户账号上。一个没有被标记为 bot/App 的账号,每小时几百次读、几十次写、多进程并发、反复触顶限流 —— ⛔ 本卡不断言这就是成因,但它是这一整类问题的土壤。
GitHub App(或每席一个 machine user)会让下列问题一起消失:预算按 installation 计而不是按人;每席独立身份 ⇒ 一个席位闯祸不再连坐全队(本次是三个 dev 席 + 分诊席 + 总监席一起停摆);且不再需要"两个客户端压一个账号"这种形状。
⛔ 三条不要走的路
- ⛔ 不要把本卡写成「禁用某种传输方式」。 REST 与 GraphQL 是同一个 API、同一个账号、同一套配额族 ⇒ 按传输方式画线挡不住风险,代价却是实的(本次禁掉原始通道之后,PM 立刻失去了修正自己发过的评论的能力,见 条款②声明载体是一扇单向门:席位能把自己写进一个自己出不去的状态 —— 一个会话里同一个坑被踩了 5 次 #17366)。⭐ 线要画在「同一身份上的并行未协调写入」这个轴上。
- ⛔ 不要靠「下次注意」。 本班另一条卡(条款②声明载体是一扇单向门:席位能把自己写进一个自己出不去的状态 —— 一个会话里同一个坑被踩了 5 次 #17366)已经用实测证明记忆不是补救:同一个坑一个会话被踩 5 次,其中三次发生在本席刚刚在派单里逐字叮嘱过别人之后。
- ⛔ 不要为了绿而扩大豁免。 事故当时如果有一条「额度耗尽可降档/可绕行」的豁免,本席会更快地走进同一个动作。⇒ 任何补救都不得引入这类出口。
期望(⛔ 不定实现,交给承接席)
至少要有可执行的答案:
- 限流信号的语义:一次耗尽绑定身份,⛔ 不是客户端。同一身份的任何其它通道上继续写,与重试同罪。写在席位实际会读到的地方。
- 写入节流的口径:一个席位一班次的写操作应当合并(少而大)。
⚠️ 承接席先量:今天有没有任何地方记载过写入量的量级,或者这是完全空白。 - 账号级事故的恢复流程:把 F3 的四条写成可照做的步骤(分支活/PR 死、幽灵 PR 占分支名、本地对象库是备份、重建 PR 必须自报)。
- 销毁记录的判据:复核项遇到已销毁的证据 ⇒ 记 NOT MEASURED,⛔ 永不记作通过或「无」。
- 依赖卡消失的可见性:一张被引用的卡变成 404 时,⛔ 不该只能靠人顺手发现。
验收
- 正面:给出一个「客户端 A 报限流、客户端 B 有额度」的情形,纪律能明确回答"停",⛔ 而不是靠席位自己推。
- 阴性对照必测:正常额度下的写入不受影响 —— ⛔ 一条把所有写入都变慢的纪律同样是坏的,它会把席位推向"攒着一次性发"这种更像突发的形状。
- ⭐ 恢复流程必须被演练过一次,⛔ 不是写下来就算:拿一条真实分支走一遍"PR 已销毁 ⇒ 重推新分支名 ⇒ 重建并自报",证明步骤可照做。
- 消融:把新纪律从文本里去掉,第 1 条的问法必须重新变得无解。
关联
- 本次事故的恢复产物:PR fix(security): OAuth-connected MCP agents run at the delegator's recorded scope, and a narrowed delegated read says so #17332(原 fix(security): OAuth-connected MCP agents run at the delegator's record depth; narrowed delegated reads say so #17256)、fix(triggers,spec,service-automation)!: a time-triggered flow declares its acting organization and the run executes as it #17334(原 fix(triggers,spec,service-automation): a time-triggered flow declares its acting organization and runs as it (#16659) #17126)、fix(service-analytics): a row-scope refusal carries a declared envelope, so queryDataset stops classifying refusals by their wording #17336(原 fix(service-analytics): declare an envelope on the row-scope fail-closed refusals, so a security refusal can never be served as an empty chart #17316)、fix(service-automation): a notify node reports the recipients it addressed, so a zero-delivery run stops reading like a successful one #17339(原 fix(service-automation): a notify node that reached nobody stops reading like a run with nobody to reach #17324)—— 四条都是同一批 commit 逐字节重推
- 被销毁且已重建的卡:[finding] a bare-Error refusal reaching queryDataset is classified by WORDING — a security refusal whose text happens to contain "not registered" becomes a 200 with an empty chart #17335(原 [finding] a bare-Error refusal reaching queryDataset is classified by WORDING — a security refusal whose text happens to contain "not registered" becomes a 200 with an empty chart #17130)、A notify node that enqueued NOTHING reports the same as one that delivered: a zero-delivery run is indistinguishable from a successful one #17337(原 A notify node that enqueued NOTHING reports the same as one that delivered:
unmeasured=0makes a zero-delivery run indistinguishable from a successful one #17123) - 被销毁且⛔ 尚未重建的卡:[Decision] 批量创建把作者声明的行级
check整个跳过,违规行照落 —— 修法方向已定,要裁的是「失败语义」 #17297(p1 安全 fail-open 决策卡)、[finding] the RLScheckpost-image gate is guarded by!Array.isArray(opCtx.data), and it is the ONLY site that computes the check filter — so whether an array-shaped write is judged at all is unestablished #17128、[needs decision] #16659's ruled fix refuses the repo's own four example scheduled flows at bind, and today a package-shipped flow has no legal organization to name #17150、授权时点看不见驱动能力差:os validate/os lint接受一个在目标驱动上不可用的 analytics 键,作者只能在运行时的错误卡上发现 #17276、dispatch-gates --ran把一次被杀死的 gate 读作 RUN:kill 码留在声明路径上,而文件自己的教义说它是 UNRUN #17313、READ_SCOPE_COMPILE_FAILED的 ledger 注释只说 "lowering",但从 #17130 起 resolution 阶段也盖这个章 #17318 - 同班次的姊妹卡:条款②声明载体是一扇单向门:席位能把自己写进一个自己出不去的状态 —— 一个会话里同一个坑被踩了 5 次 #17366(条款②声明载体是单向门)—— 两张卡在一处交汇:条款②声明载体是一扇单向门:席位能把自己写进一个自己出不去的状态 —— 一个会话里同一个坑被踩了 5 次 #17366 的自解出口之所以在本班次消失,正是因为本卡的事故让那条临时旁路被正当地关闭了
- 规矩出处:
CLAUDE.md/AGENTS.md〈Multi-agent working discipline〉的共享身份声明;.claude/skills/pm-dispatch/SKILL.md的额度相关条款
定级说明
priority:p1:这是本会话唯一一次让所有席位同时停止工作的事件,且销毁了包括一条 p1 安全决策卡在内的全部记录。
Activity
claude commentedon Sep 15, 2026
Release: session
session_01HZfg2AwVX191qCizp88gQr(skills seat, inheriting) — reason: the claiming sessionsession_01MCLBsUgfykL74aU716rzVK(os-sales) signed off 2026-09-12T15:47Z with the first half landed (PR #17860 →ed8dea17b, landing record 5646971418) and the second half deferred behind PR #17803 and PR #17855, both merged since; destination:pm:queue, re-claimed by this session for the second half in the next comment. 2026-09-15T02:07Z. A partial landing is a release under the standing rule (aPart ofPR lands ⇒ back to the queue with the assignee cleared), and a dead claim is not reused: the second half gets its own claim, branch and worktree.Generated by Claude Code
claude commentedon Sep 15, 2026
Claim: PM loop round 1
Session:
session_01HZfg2AwVX191qCizp88gQr(skills seat; claimed at 2026-09-15T02:13Z)Branch:
claude/issue-17374-rate-limit-identity-second-halfWorktree:
objectstack-issue-17374Domain:
domain:skillsFile surface:
.claude/skills/pm-dispatch/references/platform-readings.md(the quota section :95–:135 — expectation 3's recovery-step rows under the standingruledRaisesexception, and the :123 / :126 reconciliation with rest-channel :12–:13),.claude/skills/pm-dispatch/SKILL.md(expectation 2's write-throttling rule if it lands as a rule — 〈平台读数纪律〉 or 〈全体座位的不变量〉, equal-line, paid by density) +references/core-rules.md(the mirror only if a core clause moves) (stop on breach; explain in the report)Container & model:
M,mode:subagent,model: CONTRACT_REVIEW_TIER — path-derived MANDATORY by dispatch-gates --tier on b3b43b6 for SKILL.md and platform-readings.mdClause-②: no
Thread-read: 5673582323
Serial constraints cleared:
no open PR touches platform-readings.md, SKILL.md or core-rules.md (all 8 open PRs' file lists read at 2026-09-15T02:09Z; the newest, PR #18227, holds AGENTS.md :1026+ only); #17497 (SKILL.md), #18181 / #18218 (SKILL.md) and the #18158 + #18195 fold (platform-readings.md) are queued BEHIND this card by file serial — fold-or-serial answered SERIAL (different defects); verify lock free (--status: no holder, no tickets); H17 index ∩ this face: none on anchor #9857's sweep of 2026-09-15T01:57Z; batch 3, devs in flight 0 before this claimGenerated by Claude Code
claude commentedon Sep 15, 2026
os-dev-report
{ "issue": 17374, "status": "done", "branch": "claude/issue-17374-rate-limit-identity-second-half", "pr": "https://github.com/objectstack-ai/objectstack/pull/18242", "head": "694b1d284", "premise_still_valid": true, "summary": "Second half, one draft PR. (1) platform-readings.md quota block: the two rows that on b3b43b6 still told a seat to try the other channel on a rate limit (:123 and :126) are re-conditioned in place on the GET /user identity test, line-neutral (114 B / 109 B), keeping 403 and transport failures as per-side readings and leaving reads to the read/write-independence row; five new rows land after the retry-alignment row — the suspension signal (account was suspended on every endpoint including /rate_limit and on git, no reason given; a third 403 shape beside the session gate and the rate limit) and the four F3 steps (branches and commits survive; a destroyed PR still holds its branch name so the same commits go up under a NEW name; the local object store is the backup; a rebuilt PR self-reports head-identical / no rebase-amend-squash / numbers from the old base / CI decides) — +5 under the standing exception with a FIFTEENTH ruledRaises record and ceiling 454 to 459, dedup candidates 5 / landed 5 / already present 0 / refused 0. (2) SKILL.md 平台读数纪律 gains one rule line beside the rate_limit read (117 B): 写少而大 — same-card same-round conclusions merge into one comment, no per-write slowing, no burst, write volume counted per identity — paid by folding the two maintainer-abort lines into one 108 B line; 812/812. (3) review-checklist.md gains the reviewing seat's NOT MEASURED line for destroyed evidence directly under the INCONCLUSIVE row (113 B), paid by folding the dead-code deletion pair into one 113 B line; 77/77. core-rules.md untouched at 151 (no core clause moved; the mirror's header says it adds no rules). All five card expectations are now placed (1, 4, 5 by the first half and the patrol; 2, 3 here), so the body's first line is Fixes #17374; F5 is the maintainer's and not this card's. Identity: the newest Claim: (5673628965) names this branch; the assignee os-zhuang was set by the dispatch and never written by me; GET /user on this container's REST credential answers os-zhuang id 277994282 (a user-to-server token), so PM and dev are one identity here. Governed surface: draft, never flipped ready; the seat requests the approval.", "tests": "No package touched, so no dependency-closure build, package test or typecheck is owed. Gates: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack with NO paths (change set from git off merge base b3b43b6ea: 4 committed paths, 0 working-tree, 0 untracked) derived 40 commands. 39 ran in the foreground through a runner that captures the exit by redirect before any pipe (bash -c CMD with stdout and stderr redirected into LOG, then EXIT=$?): batch A 18 commands, batch B 18, lint batch 3 after building '@objectstack/lint...' under scripts/pm/os-verify-lock.sh (VERDICT command-exit 0 · held the lock 220s · waited 0s; OS_VERIFY_LOCK_SLOT=issue-17374-second-half) — every one exit 0. The 40th, pnpm check:pm-dispatch-gates (the tool's own self-test), ran detached per its header's foreground-cap warning and was waited on in the foreground with tail --pid: first run verdict line '✓ dispatch-gates self-test: 1723 cases pass.' (exit not captured, detached); second run through a wrapper writing $? to a file: exit 0, verdict line '✓ dispatch-gates self-test: 1723 cases pass.'. --ran on the exit-carrying record: Run reconciliation — 40 derived, 40 run, 0 NOT-MEASURED, 0 UNRUN. / ✓ dispatch-gates --ran: 40 derived famil(ies) accounted for — 40 run, 0 NOT-MEASURED (a DERIVED zero — all 40 recorded an exit code and none of them is 3).. Verdict lines: check-skill-line-ratchet '✓ cross-file move into .claude/skills/pm-dispatch/references/platform-readings.md: +11 (314→459, less 134 lines of ordinary ruled raise) against a net source decrease of 20' and '✓ declared cross-file moves: 1, total ceilings down 9 lines'; check:pm-skill-id-lint exit 0; check:skill-frame-sync exit 0; check:nul-bytes exit 0; check:ratchet-remedy-authority exit 0; check:pm-governed-prose exit 0. Three roster families the derivation flagged as sitting under scripts/ beside this diff were run in addition: node scripts/check-published-list-mirrors.mjs exit 0; pnpm check:pm-label-desc-cap exit 0 ('23 label descriptions in scripts/pm/ensure-pm-labels.sh, all ≤100 characters'); node scripts/check-skills-token-ratchet.mjs exit 0 ('34 authored bundle file(s) within their ceilings'). Byte cap: every added line measured with LC_ALL=C awk length: 108–117 B (SKILL 108/117, platform-readings 114/109/108/112/114/110/113, review-checklist 113/113); lines over 120 B in platform-readings.md and review-checklist.md = 0 (SKILL.md's 23 pre-existing long lines are the ratchet's exempt classes and untouched). Control bytes: grep -naP over the four files = 0 hits. Card numbers in added operative lines = 0. Grep ablation (git show origin/main:FILE at b3b43b6 vs HEAD 694b1d2, grep -c line counts, lit control in the same file): platform-readings 停用|销毁|新分支名|对象库 0 → 4 and 自报四件 0 → 1 (bare 自报 excluded: :220 carries it in another sense), 比 ID|比身份 0 → 2, the deleted switch instruction 先探 REST|整个平台的写 1 → 0, control 限流 10 → 11; SKILL.md 少而大|攒着|按身份计 0 → 1, control 限流 3 → 3; review-checklist NOT MEASURED|销毁 0 → 1, control INCONCLUSIVE 2 → 2. No build/dist ablation applies (no package code). Repo-wide pnpm lint not run (CI's). CI on PR #18242 not waited for: in_progress at report time.", "gates": { "derived": 40, "ran": 40, "unrun": 0, "ran_reconciliation": "Run reconciliation — 40 derived, 40 run, 0 NOT-MEASURED, 0 UNRUN. / ✓ dispatch-gates --ran: 40 derived famil(ies) accounted for — 40 run, 0 NOT-MEASURED (a DERIVED zero — all 40 recorded an exit code and none of them is 3).", "not_measured_locally": "CI's, outside the derived total: 3 workflow-value families (check-required-contexts --verify-required-set, check-shard-attestation --emit, check-test-completeness), the Test Core CI job, 11 declared wide-population families, 14 changeset-pending families (skip-changeset), the 51 artifact-roster families (3 of them run, below); repo-wide pnpm lint", "extra_ran": "check-published-list-mirrors exit 0; check:pm-label-desc-cap exit 0; check-skills-token-ratchet exit 0; the ratchet's own --self-test runs inside check:pm-skill-ratchet (exit 0)" }, "line_budget": { ".claude/skills/pm-dispatch/SKILL.md": { "before": 812, "after": 812, "ceiling": 812 }, ".claude/skills/pm-dispatch/references/core-rules.md": { "before": 151, "after": 151, "ceiling": 151, "note": "untouched" }, ".claude/skills/pm-dispatch/references/platform-readings.md": { "before": 454, "after": 459, "ceiling": "454 → 459", "note": "+5 of the ≤ +5 budget, standing ruledRaises exception, FIFTEENTH record dated 2026-09-15" }, ".claude/skills/pm-dispatch/references/review-checklist.md": { "before": 77, "after": 77, "ceiling": 77 }, "scripts/pm/check-skill-line-ratchet.mjs": { "before": 2925, "after": 2973, "ceiling": "none (not a ratcheted file)" } }, "deviations": [ "File face: references/review-checklist.md is edited although the claim comment's file-surface line names only platform-readings.md, SKILL.md and core-rules.md — the dispatch word's A5 asks for exactly this one reviewer line inside the ratchets; no open PR held the file (all 11 open PRs' file lists read 2026-09-15T02:46Z, zero overlap with the four files).", "The 40th derived family (pnpm check:pm-dispatch-gates) did not run in the foreground: its own header says the 1723-case battery exceeds the container cap, so it ran detached and was waited on with tail --pid; the exit code comes from a wrapper on the second run.", "platform-readings.md :143 (「报文里的 user ID 只是报文」) is left as written although it now sits in tension with the identity binding — the ratchet's own record says :97 and :143 both stay pending a discriminating read; recorded as a finding for the seat rather than edited.", "Commit trailer pair is the model-free pair AGENTS.md prescribes (Claude-Session + Co-authored-by: Claude), not the harness reminder's model-bearing form, because the pre-push hook refuses a model identifier there; check:commit-card-trailers passed on push." ], "files_changed": [ ".claude/skills/pm-dispatch/SKILL.md (+2/−2: one line added, two folded into one)", ".claude/skills/pm-dispatch/references/platform-readings.md (+7/−2: two rows re-conditioned in place, five rows added)", ".claude/skills/pm-dispatch/references/review-checklist.md (+2/−2: one line added, two folded into one)", "scripts/pm/check-skill-line-ratchet.mjs (+49/−1: ceiling 454 → 459 with its accounting comment; FIFTEENTH ruledRaises record)" ], "mcp_calls": "0 — no MCP GitHub tool called; the card, its 7 comments, the open-PR list and 20 PR file lists were read through repo-scoped REST GETs, plus GET /user and GET /rate_limit", "api_writes": "3 REST writes + 2 git pushes — POST /repos/objectstack-ai/objectstack/pulls (201, PR #18242 draft, body read back byte-identical minus the trailing newline, one footer); POST /repos/objectstack-ai/objectstack/issues/18242/labels with skip-changeset (read back = union {size/s, skip-changeset}, nothing stripped); POST /repos/objectstack-ai/objectstack/issues/17374/comments (this os-dev-report); git push ×2 (the empty probe branch answered new-branch, then 694b1d2)", "open_questions": [], "out_of_scope_findings": [ "noted, not filed: platform-readings.md :97 「⛔ 不据限流报文里的 user ID 推池子跨席共用」 and :143 「报文里的 user ID 只是报文」 are held as written by the ratchet's same-batch tension record pending a discriminating read; the first half's GET /user comparison is that read (the id in the refusal text names the identity and says nothing about cross-seat pools), so :143's 「只是报文」 could become 「只标身份」 at equal bytes in a later density pass. 承接者: the skills seat (holder of that ruling).", "noted, not filed: references/core-rules.md 〈平台读数纪律〉 carries no digest of the new write-merging line; the mirror's header says it adds no rules and the claim conditions a mirror edit on a core clause moving, which none did. 承接者:无." ] }Generated by Claude Code
claude commentedon Sep 15, 2026
ACCEPT (second half — the card's five expectations are all placed) — skills seat, session
session_01HZfg2AwVX191qCizp88gQr, 2026-09-15T03:05Z. Report 5674013532 read in full; PR #18242 (head694b1d2) reviewed in-seat at the contract-review tier:## Contract reviewPASS, record 5674046970 on the PR; 速读终稿 posted there. Checklist: draft, basemain, first lineFixes #17374(all five expectations placed: 1 and 4 by PR #17860, 5 by patrol row H40, 2 and 3 here), no other closing keyword beside a card number; changed files = SKILL.md, platform-readings.md, review-checklist.md, check-skill-line-ratchet.mjs —review-checklist.mdwas outside the claim's declared surface and is admitted here (the dispatch's A5 asked for that one reviewer line; claim 5673628965's file surface is amended to include it);skip-changesetread back withsize/s;mcp_calls0, three REST writes + two pushes inside theWrites:budget; gates 40 / 40 / 0 / 0. Seat-measured on the head: ratchet ✓ (812 / 459 / 77), frame-sync ✓, id-lint ✓, frame md5 unchanged, every added line ≤ 117 B, the five new rows deduplicated againstorigin/mainby the seat's own grep (5 / 5 / 0 / 0);check-clause2-carriers --pair 18242exit 0;check-governed-merges --test→ governed (three register paths). Acceptance criteria: 1 (the client-A-refused / client-B-full case answers 停 on one user ID) — landed by the first half and now not contradicted by the facts table; 2 (normal-quota writes untouched) — the new rule line says so in its own words; 3 (a rehearsed recovery) — the incident's own byte-identical rebuilds (PRs #17332 / #17334 / #17336 / #17339) are the rehearsal on record, cited in the PR body; 4 (ablation) — the dev's grep ablation shows the deleted switch instruction 1 → 0 and the new spellings 0 → n with lit controls. Acceptance notes carried on this card, no new card: platform-readings :143 「只是报文」 vs the identity binding — an equal-byte candidate (「只标身份」) for a later density pass; core-rules.md carries no digest of the write-merging line (the mirror adds no rules by its header; none of its clauses moved). Landing: governed rules layer ⇒needs-user-decisionon PR #18242, reviews requested from the two authorized approvers, then ruling C by this seat after an authorized approval; the card stayspm:dispatched+ assigned until MERGED.Generated by Claude Code
claude commentedon Sep 15, 2026
Landed — skills seat, session
session_01HZfg2AwVX191qCizp88gQr, 2026-09-15T06:21Z. PR #18242 (head694b1d2) merged by the queue as1a02ef17d61b00dcf1658d0ac99e35d0a706200e(single-parent squash pergit rev-list --parents); two readings at 2026-09-15T06:21Z: the queue refgh-readonly-queue/main/pr-18242-*is gone from origin, andgit log origin/maincarries(#18242). Ruling-C landing: os-zhuang's APPROVED review 5206118837 on that head; review of record 5674046970, ACCEPT 5674047468, ruling-C provenance 5675577662; the approver flipped it ready and enqueued it at 2026-09-15T06:05Z. Now onorigin/main: the quota rows ofplatform-readings.mdreconcile with the identity-bound rate-limit rule (SKILL.md :172–:185, core-rules, review-checklist),check-skill-line-ratchet.mjscarries the fifteenthruledRaisesentry and the file's ceiling reads 459. Residue (pm:dispatched, assignee) stripped throughlabel-write.mjsand read back; the platform-readings family fold (#18158 · #18195 · #16762 · #18219 · #18147 · #18258 · #18262) is unblocked and dispatches next.Generated by Claude Code
feat(pm): a scheduled backup of the board, so an account suspension d…
skills(pm-dispatch): a rate-limit refusal binds to the identity, not …
pm-dispatch: reconcile the quota rows with the identity-bound rate li…