Skip to content

skills(pm-dispatch): a rate-limit refusal binds to the identity, not the client; destroyed evidence reads NOT MEASURED - #17860

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-17374-rate-limit-identity
Sep 12, 2026
Merged

skills(pm-dispatch): a rate-limit refusal binds to the identity, not the client; destroyed evidence reads NOT MEASURED#17860
os-zhuang merged 1 commit into
mainfrom
claude/issue-17374-rate-limit-identity

Conversation

@claude

@claude claude Bot commented Sep 12, 2026

Copy link
Copy Markdown
Contributor

Part of #17374 — first half only (expectations 1, 4 and the record for 5). The second half (expectation 2, the write-throttling 口径; expectation 3, the F3 recovery steps; and the reconciliation of platform-readings.md :95–:125) is deferred until PR #17803 and PR #17855 land; #17374 remains open after this merges.

Governed surface (.claude/**): draft PR at the human terminal. No changeset — dispatch-gates.mjs derives 17 gate families for the diff and none is a publish family; nothing in any package files[] moves.

What changed (head 58885bc0b, base 7f625364b)

Two files, both at their ratchet ceilings, both paid in place by deletion — line counts unchanged (82/82, 403/403), every edited line ≤120 B, ⛔ no ceiling raise, ⛔ no re-wrap, ⛔ no cross-file move.

1. references/rest-channel.md 〈通道边界〉 — the identity-bound rate-limit rule (expectation 1)

Added after the /rate_limit credential-shape line (:11), where a seat already reads 「先跑一条 repo-scoped 探针再选通道」:

- 限流拒绝绑定被拒身份(报文 user ID):同身份各写通道一并耗尽,⛔ 换通道续写与重试同罪。
- 他侧只在身份不同时是退路:凭据 `GET /user` ≠ 被拒 user ID 才换;席内 PM 与 dev 同一身份。

This is the operative form of the card's F1 sentence 「一次限流信号绑定在身份上;⛔ 在同一身份的另一条通道上继续写,与重试同罪」, with the one legitimate fallback stated as a check the seat performs before switching: the refusal names a user ID; the other side is a different reading only if its credential answers a different user ID on GET /user. Within one seat the PM and its dev subagents are one identity, so a dev's refusal is the seat's refusal.

Consistency with the standing facts in platform-readings.md (read-only here): :95–:97 (quota is per account, one identity holds only inside a seat, ⛔ no cross-seat pool inferred from a refusal's user ID) — the rule binds to the identity named in the refusal and nothing wider; :122 (a refusal is that side's reading) — the identity comparison is how a seat tells whether the other side is another reading or the same one. The 限流 clause of :123 「限流、403、传输失败都要试过另一侧才说得出我没手段」 is the reconciliation the claim assigns to the second half; it is not touched here.

Paid by (deleted, with where the content survives):

  • :12 「按班矩阵与降级梯住 platform-readings.md,⛔ 不在本表复述。」 — a pointer restated at :3 (「见 platform-readings.md 配额段」) and :67 (「本表只指路,⛔ 不在两处各存一份」); the 降级梯 lives in the quota section :3 already points to.
  • :43 「GraphQL 池为 0 的同一分钟里开得出 draft PR ⇒ 交付不必等重置。」 — the capability survives at the ✓ row directly above it (:42, POST .../pullsdraft=true); the prescription half 「交付不必等重置」 is the unconditional form of the act the new rule conditions on identity (same identity ⇒ the same act as a retry), so it cannot stand beside the rule. Its conditional replacement is the new line 2.

2. .claude/agents/os-dev.md — destroyed evidence reads NOT MEASURED (expectation 4)

Added directly after :174 「两类跑了却没测到,都读作 NOT MEASURED,不读作绿也不读作红。」, the home of the NOT MEASURED family (:96, :100, :174, :344 on main); the report-contract section defines open_questions as a field but carries no verdict-reading rules, so the family home is the tighter fit:

- 证据已销毁(评论、卡或 PR 答 404)的复核项记 NOT MEASURED 并写因,⛔ 不记通过或「无旗」。

Paid by: :172 「引用门禁结果时点名它自己印的判定行,永不引裸 $?:判定行由门禁写,$? 由你的管道写。」 — restated at :166 「门禁结果的读法:退出码在任何管道之前捕获,报告里引门禁自己印的判定行。」 and at 资源纪律 1 「结论读它印的 VERDICT command-exit 行,⛔ 不读裸 $?」; the trailing clause was rationale.

3. Expectation 5 — already met on main, nothing built

The half-state patrol's H40 「Dangling references」 row on anchor #9857 (scripts/pm/check-half-states.mjs) lists every open card/PR whose # reference answers 404. Read on the anchor's current sweep body: 363 of 400 attempted resolutions answered over 6365 distinct references; 37 do not resolve; ⛔ only HTTP 404 is read as unresolvable; every count a lower bound. A destroyed card is therefore detected by patrol, not by someone noticing.

Acceptance reading

  • Criterion 1 (「客户端 A 报限流、客户端 B 有额度」): the text now answers. Same identity (the refusal's user ID equals what B's credential answers on GET /user) ⇒ 「停」 — B's full quota is not a fallback. Different identity ⇒ verify the two user IDs differ, then switch. The seat does not infer; it compares two numbers.
  • Criterion 2 (normal-quota writes untouched): both new lines fire only on a refusal; nothing slows or batches a write under normal quota, and no transport is banned (REST, GraphQL and MCP all remain in the table).
  • Criterion 3 (rehearsed recovery): second half, with F3.
  • Criterion 4 (ablation, grep on the two files): at origin/main git grep -c -E '身份|限流' on rest-channel.md = 0 and git grep -c -E '销毁|404' on os-dev.md = 0 (lit controls: 探针//rate_limit hit :7/:10/:11/:31; NOT MEASURED hit :96/:100/:174/:344). At head 58885bc0b the same greps read 2 and 1, and NOT MEASURED reads 5. With the new lines removed, criterion 1's question is again unanswerable by the text.
  • Criterion 5: item 3 above.

The three 「⛔ 三条不要走的路」 hold: no transport is banned; nothing relies on 「下次注意」 (the rule is a check with two inputs); no new quota-exhaustion exit is introduced — the fallback that existed unconditionally is now narrower, not wider.

A measured falsification of the dispatch word

The dispatch cited a 2026-09-12 reading (MCP refusal naming user ID 319429713; seat /rate_limit 15000/15000; GET /user answering os-sales) as a switch between two different users. Measured on this container at PR time: GET /user answers login: os-sales, id: 319429713 — the same user ID the MCP refusal named. Under the rule as written the two are one identity and that switch answers 「停」, consistent with the evidence comment on the card (5628795815: one client's bucket dry while another on the same user reads full — per-(user, app) buckets on one identity). The rule is unchanged by this; it is the comparison that catches it. Reported for the seat's reading, no state changed.

Gates (run on head 58885bc0b, exit codes captured by redirect before any pipe)

node scripts/pm/dispatch-gates.mjs --commands derived 17 families; all 17 run, all exit 0; --ran reconciles 17/17 (0 UNRUN). Named: check:pm-skill-ratchet (rest-channel.md 82/82, os-dev.md 403/403, 「declared cross-file moves: 1, total ceilings down 9 lines」 unchanged), check:pm-skill-id-lint (27 files clean), check:skill-frame-sync (frame untouched), check:nul-bytes, check:agent-model-declared, check:doc-authoring, check:agent-test-spelling, check:commit-card-trailers, check:watch-hint-literal, check:refd-timer-probe, check:driver-memory-census, check:pm-governed-merges, closing-keyword-parity (+ self-test), comment-mask-corpus, governed-queue-guard self-test, and the lint-package check:doc-formula-expressions after building the @objectstack/lint closure under os-verify-lock.sh (VERDICT command-exit 0, held 176s). Repo-wide pnpm lint is CI's run, not run here.

Acceptance notes

  • noted, not filed: references/rest-channel.md :53 carries a provenance date (「两条 2026-09-12 两席实调」) of the kind the rules-only rewrite removed elsewhere; a density candidate for any later net-reducing PR on this file. 承接者:无.

维护者速读(草稿)

改了什么:两个文件各加一条纪律、各删一条已在别处写过的话,行数不变。① rest-channel.md〈通道边界〉:限流拒绝绑在被拒的身份上(报文里的 user ID),同一身份的所有写通道一起耗尽,换通道继续写等同重试;只有另一侧凭据 GET /user 答出的是不同的 user ID 才算退路;席内 PM 与 dev 是同一身份。② os-dev.md:复核项的证据被销毁(评论、卡、PR 答 404)时记 NOT MEASURED 并写原因,不记通过、不记「无旗」。期望 5 已由 H40 巡检行满足,不另建。

为什么改:2026-09-10 整队被停用的复盘卡指出,当时的规矩只禁「重试」,没说限流信号绑的是身份而不是手上的客户端,于是一个席位可以合规地换通道继续写。本 PR 只写纪律,不动舰队身份结构(#17392 已关)。

风险与代价(含回滚):纯文本纪律,不改代码、不改门禁、不发布任何包;正常额度下的写入不受影响,也不禁用任何传输方式。删掉的两条在同文件别处仍有原话或被新条款取代。回滚 = revert 本 PR 一个 commit。

席位意见:(留空)

你要做的:确认 ① 「同一身份 ⇒ 停」这条线画在你要的位置(而非按传输方式画);② 期望 2、3 与 platform-readings.md :123 的对齐留给第二半;然后人工直合。


Generated by Claude Code

…the client; destroyed evidence reads NOT MEASURED

rest-channel.md 通道边界: a rate-limit refusal binds to the identity named
in the refusal text; every write channel of that identity is exhausted with
it, and continuing to write on another channel of the same identity is the
same act as a retry. The other side is a fallback only when its credential
answers a different identity on GET /user; within one seat the PM and its
dev subagents are one identity. Paid in place: the platform-readings pointer
(restated at :3 and :67) and the unconditional "open the draft PR without
waiting for reset" prescription (the capability stays at the ✓ POST row).

os-dev.md NOT MEASURED family: a review item whose evidence was destroyed
(its comment, card or PR answers 404) is recorded NOT MEASURED with the
reason, never as passed and never as "no flag". Paid in place: the verdict-
line restatement (already stated at 资源纪律 1 and 门禁结果的读法).

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MCLBsUgfykL74aU716rzVK
@claude

claude Bot commented Sep 12, 2026

Copy link
Copy Markdown
Contributor Author

维护者速读

事情:9 月 10 日整个 fleet 被 GitHub 停用的复盘卡(#17374,p1)提出的第一条纪律:限流拒绝约束的是被拒的那个用户身份,不是手上这个客户端——同一身份换一条通道继续写,与重试同罪;只有对方凭据 GET /user 答出不同的 user ID 才算退路。第二条:证据已被销毁(评论/卡/PR 变 404)的复核项记 NOT MEASURED,永不记作通过。第三条(引用的卡变 404 要看得见)巡查 H40 行已经做到,不再建。

改了什么:rest-channel.md 加两行、删两行(其中删掉的「GraphQL 池空也能开 draft PR ⇒ 不必等重置」正是这条纪律要禁止的动作);os-dev.md 加一行、删一行(被删规则在 :166 逐字存活)。82/82、403/403 不增,门禁 17/17 绿。不改 fleet 身份结构(你 9 月 11 日已裁 #17392 关)。

顺带的自查:本席今早 MCP 被限流后改走 ccr REST 路由继续挂 auto-merge,当时读成「MCP 是另一个身份」。dev 实测 GET /user 答的正是被拒的那个 user ID —— 同一身份两个客户端,按这条新规则应当。已在验收里如实记录并改口。

没做的(第二半,等 PR #17803#17855 合并后再派):写入节流口径、账号事故恢复四步、platform-readings.md :123「要试过另一侧」与本规则的对接。

风险:低。协议文本;这条规则只在收到限流拒绝时生效,正常额度下的写入不受影响。

只问一字:是否合并?


Generated by Claude Code

@os-zhuang
os-zhuang marked this pull request as ready for review September 12, 2026 15:28
@os-zhuang
os-zhuang enabled auto-merge September 12, 2026 15:28
@os-zhuang
os-zhuang added this pull request to the merge queue Sep 12, 2026
Merged via the queue into main with commit ed8dea1 Sep 12, 2026
36 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-17374-rate-limit-identity branch September 12, 2026 15:52
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 needs-user-decision size/xs 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.

2 participants