Skip to content

pm-dispatch: reconcile the quota rows with the identity-bound rate limit; merged-write rule; account-suspension recovery readings - #18242

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

os-zhuang merged 1 commit into
mainfrom
claude/issue-17374-rate-limit-identity-second-half

Conversation

@claude

@claude claude Bot commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

Fixes #17374

Second half of the shared-identity rate-limit card. The first half (PR #17860, landed as ed8dea17b) placed expectations 1 and 4 — the identity-bound rate-limit rows in references/rest-channel.md and the destroyed-evidence NOT MEASURED line in .claude/agents/os-dev.md; expectation 5 is met by the half-state patrol's H40 row. This PR lands what was deferred behind same-file serial: expectation 2 (merged writes), expectation 3 (the account-suspension recovery steps as readings), the reconciliation of the two quota rows that still prescribed the same-identity channel switch, and the reviewing seat's NOT MEASURED line. With this the card's five expectations are all placed. F5 (GitHub App / machine users) is the maintainer's infrastructure decision and not this card's; the disciplines here hold independently of it.

Clause-②: no — no published skills/** file moves; no operator or contract semantics.
skip-changeset — nothing published moves (.claude/** and scripts/pm/** sit outside every package's files[]).

What changed — 4 files, +60 / −7, head 694b1d2 on base b3b43b6

references/platform-readings.md — 454 → 459 (+5 under the standing one-file exception)

Two quota-block rows re-conditioned in place (line-neutral; they pay nothing and buy nothing):

  • 「限流、403、传输失败都要试过另一侧才说得出我没手段。」 → 「403 与传输失败要试过另一侧才说得出我没手段;限流先比身份,同 ID 的他侧不是手段。」 (114 B)
  • 「⇒ MCP 限流先探 REST 再定退避,⛔ 不据一侧限流把整个平台的写都停掉。」 → 「⇒ MCP 限流先 GET /user 比 ID:同 ID 的 REST 满额不是退路,写排队到重置;异 ID 才是。」 (109 B)

Until this PR both rows told a seat to do the exact same-identity channel switch that rest-channel.md's identity-bound rows (landed by the first half) forbid. The 403 and transport halves stay as they were: those are per-side readings, and the row above them (「任一侧的拒绝只是那一侧的读数」) is untouched. Reads are untouched too — the row below (「MCP 的读限流与写限流彼此独立」) still governs them, and the identity test is itself a read on the other side. 「写排队到重置」 points at the standing queue-into-the-patrol-word row (:159) rather than restating it.

Five new rows after the retry-alignment row (the end of the quota-exhaustion prescriptions), one measured event per row, each under 120 B:

  1. 「停用报文 account was suspended 遍及 /rate_limit 与 git,不给理由;非会话门 403、非限流。」 (108 B) — the recognition reading: the third 403 shape beside the session gate and the rate limit, which is what tells a seat the four steps below now apply.
  2. 「账号停用销毁其名下 PR、卡与评论;分支与 commit 属仓库照留远端 ⇒ 代码从未真丢。」 (112 B) — F3 step 1.
  3. 「被销毁的 PR 仍占分支名:API 答 404,同名开新 PR 仍被拒 ⇒ 同批 commit 推新分支名再开。」 (114 B) — F3 step 2.
  4. 「本地对象库是最后备份:复核时 fetch 过的每条分支,其 head 在停用后仍在本地可推。」 (110 B) — F3 step 3.
  5. 「重建 PR 正文自报四件:head 逐字节同、无 rebase/amend/squash、数字出自旧基底、CI 为准。」 (113 B) — F3 step 4.

Dedup table for the exception (候选/落地/已有/拒收): candidates 5 / landed 5 / already present 0 / refused 0. The family grep suspended, 停用, 销毁, 幽灵, 重推, 重建, 对象库, 分支名 on origin/main at b3b43b6 hits only the Routine-rebuild rows (:445–:446, :453), the issue-transfer rebuild row (:144) and the landing criterion (:366); none carries any of the five, and the later suspension-row family the dispatch word anticipated does not exist on origin/main.

Ratchet: ceiling 454 → 459 in scripts/pm/check-skill-line-ratchet.mjs, with a FIFTEENTH ruledRaises record citing the standing exception verbatim — 「唯一例外:platform-readings.md 增量抬上限到落地行数,免决策卡,记 ruledRaises 引常设裁决。条件:席位验收评论逐条核实、去重计数(候选/落地/已有/拒收)、一事一行、不计重排」 — and the increment accounted for line by line beside the ceiling. The cross-file-move arithmetic re-derives unchanged (+11 against a net source decrease of 20).

SKILL.md 〈平台读数纪律〉 — 812 → 812 (equal-line, paid by density)

Added as the section's last line, beside the pre-dispatch rate_limit read:

  • 「写少而大:同卡同轮结论合成一条评论,⛔ 不放慢单笔、不攒着一次性发;写量按身份计。」 (117 B)

Paid by folding the two maintainer-abort lines into one: 「维护者中止只在有显式信号时成立:原话,或宿主回报 stopped by the user。」 + 「⛔ 不据推断立一道没有重启条件的门;判据是信号不是症状。」 → 「维护者中止只认原话或宿主回报 stopped by the user,⛔ 不据推断立无重启条件的门。」 (108 B). Every operative clause survives (explicit signal only; no inference-built gate without a restart condition); 「判据是信号不是症状」 is the first clause said twice. references/core-rules.md :46 already digests this pair in one line, so the mirror is unchanged (151 / 151).

A2 measured on b3b43b6: grep -c '合并\|少而大\|节流' SKILL.md counts 0 lines in the write-merging sense — every 合并 hit is merge-queue / merged-PR vocabulary — against control 限流 3 lines. The quota rows in platform-readings.md (:103–:105: the per-minute secondary limit, the ~1 s spacing between mutations, the same-second burst that hits the minute wall) answer the BURST half of the card's shape and not the MERGE half: nothing on the seat surface said fewer-and-larger, and nothing said not-slower. The dev side already carries a merged budget (os-dev.md 写预算四笔); the seat side now carries this line.

references/review-checklist.md 〈测试与门禁证据〉 — 77 → 77 (equal-line, paid by density)

  • 「复核项证据已销毁(评论、卡或 PR 答 404)⇒ 记 NOT MEASURED 并写因,⛔ 不记通过或无旗。」 (113 B) — directly under the INCONCLUSIVE row, so the reviewer's verdict vocabulary now has all three: INCONCLUSIVE (the positive control failed), NOT MEASURED (the evidence is destroyed), 不入账 (a dead-tree reading).

A5 measured: grep -c 'NOT MEASURED\|销毁\|404' on review-checklist.md and on contract-review.md at b3b43b6 = 0 and 0 (control INCONCLUSIVE = 2 in review-checklist), so the reviewer had no line to read; the first half's line lives in the dev's file. Paid by folding the dead-code deletion pair (:75–:76) into 「以死代码或不可达为由的删除,PM 先在 origin/main 用带引号精确名核引用面,再 ACCEPT。」 (113 B): the rule survives whole and the deleted half (「这是断言不是 diff 里的事实,而这一查只花十秒」) is rationale.

Face note: the claim comment's file surface names platform-readings.md, SKILL.md and core-rules.md; the dispatch word's A5 asks for exactly this one reviewer line in review-checklist.md or contract-review.md, inside the ratchets, and this is the one deviation from the claim's list. All 11 open PRs' file lists were read at 2026-09-15T02:46Z, immediately before this PR was opened; none touches any file in this diff.

Acceptance against the card

  1. Positive (client A refused, client B has quota): the quota section itself now answers 停 for the same user ID — 「限流先比身份,同 ID 的他侧不是手段」 and 「同 ID 的 REST 满额不是退路,写排队到重置」 — and 「异 ID 才是」 only when GET /user answers a different id. Measured on this container: GET /user on the REST credential answers os-zhuang id 277994282, the same login as the card's assignee, so PM and dev are one identity in this seat — exactly the case the rule is for.
  2. Negative control (normal-quota writes unaffected): every changed rule fires only on a refusal, a suspension or a destroyed record; the new SKILL.md line says 「⛔ 不放慢单笔、不攒着一次性发」 in so many words, so it can neither slow a write nor push a seat toward the burst shape. No transport is banned, no 「下次注意」 is written, no green-widening exemption is introduced — the card's three 「不要走的路」 hold.
  3. Rehearsed recovery: on record rather than staged. The incident's own recovery is the rehearsal — PRs fix(security): OAuth-connected MCP agents run at the delegator's recorded scope, and a narrowed delegated read says so #17332, fix(triggers,spec,service-automation)!: a time-triggered flow declares its acting organization and the run executes as it #17334, fix(service-analytics): a row-scope refusal carries a declared envelope, so queryDataset stops classifying refusals by their wording #17336 and fix(service-automation): a notify node reports the recipients it addressed, so a zero-delivery run stops reading like a successful one #17339 were rebuilt on new branch names from the same commits with byte-identical heads, each self-reporting the four items row 5 now spells. A staged suspension is not something this PR can or should perform; the five rows are the steps that recovery took, written so the next seat does not rediscover them.
  4. Ablation (grep line counts, origin/main at b3b43b6 versus head 694b1d2, with a lit control in the same file):
    • platform-readings.md, pattern 停用 / 销毁 / 新分支名 / 对象库: 0 → 4 (row 5's term 自报 is excluded because :220 already carries it in another sense; 自报四件 alone reads 0 → 1); control 限流 10 → 11.
    • platform-readings.md, pattern 比 ID / 比身份: 0 → 2; the deleted instruction 先探 REST / 整个平台的写: 1 → 0.
    • SKILL.md, pattern 少而大 / 攒着 / 按身份计: 0 → 1; control 限流 3 → 3.
    • review-checklist.md, pattern NOT MEASURED / 销毁: 0 → 1; control INCONCLUSIVE 2 → 2.
      Remove the new text and the positive case is again answered only by rest-channel.md, with the quota section arguing the other way.

Mechanism assumptions from the dispatch word, measured

  • A1 — confirmed: :123 and :126 on b3b43b6 prescribed the switch; both re-conditioned in place as above.
  • A2 — measured as above; a rule line was owed, and it lands in 〈平台读数纪律〉.
  • A3 — the family grep found no suspension rows on origin/main; all four F3 steps plus the recognition reading were absent and land.
  • A4 — recorded under acceptance 3.
  • A5 — measured as above; the reviewer line lands in review-checklist.md.

Gates — head 694b1d2

node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths; change set taken from git off merge base b3b43b6ea, 4 committed paths) derived 40 commands. 39 of the 40 ran in the foreground with the exit captured by redirect before any pipe, and every one exits 0; the 40th is the tool's own self-test, run detached (below). --ran on the exit-carrying record reconciles 40 derived / 40 run / 0 unrun.

  • check:pm-skill-ratchet (self-test + gate): cross-file move into platform-readings.md: +11 (314→459, less 134 lines of ordinary ruled raise) against a net source decrease of 20 · declared cross-file moves: 1, total ceilings down 9 lines.
  • check:pm-dispatch-gates (the tool's own self-test, 1723 cases): detached per its header's foreground-cap warning, waited on in the foreground with tail --pid; verdict line ✓ dispatch-gates self-test: 1723 cases pass.
  • The three lint-package families ran after pnpm --filter "@objectstack/lint..." build under os-verify-lock.sh (VERDICT command-exit 0, held 220 s, waited 0 s).
  • check:pm-skill-id-lint, check:skill-frame-sync, check:nul-bytes, check:ratchet-remedy-authority, check:pm-governed-prose, check:required-contexts: exit 0.
  • Three roster families the derivation marked as sitting under scripts/ beside this diff were run in addition: check-published-list-mirrors, check:pm-label-desc-cap, check-skills-token-ratchet — all three exit 0.
  • Not measured locally, CI's: the 3 workflow-value families, the Test Core job, the 11 wide-population families, the 14 changeset-pending families (skip-changeset), the remaining artifact rosters; repo-wide pnpm lint not run.
  • Control-byte scan on the four files: 0 hits. No changed line over 120 B in the three markdown files. No card number in any added operative line.

Acceptance notes

  • noted, not filed (承接者: the skills seat, holder of the same-batch tension ruling): platform-readings.md :97 「⛔ 不据限流报文里的 user ID 推池子跨席共用」 and :143 「报文里的 user ID 只是报文」 stay as written — the ratchet's own record says both hold pending a discriminating read, and 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. Not edited here because the ruling says neither is.
  • noted, not filed (承接者: none): core-rules.md 〈平台读数纪律〉 carries no digest of the new write-merging line; the mirror's own header says it adds no rules, and the claim conditions a mirror edit on a core clause moving, which none did.

维护者速读(草稿)

改了什么:三份 PM 席位的规则/事实文件加一份门禁台账。① 事实表 platform-readings.md 配额段:把两条还在教席位「限流了就换另一条通道继续写」的行改成「先比身份,同一身份就停写排队」,并新增五行记下账号被停用时的识别信号与恢复步骤(分支和提交不丢、被销毁的 PR 仍占着分支名要换名重推、本地对象库是备份、重建的 PR 要自报)。② 席位规则 SKILL.md:加一行「写少而大」—— 同一张卡同一轮的结论合成一条评论,既不放慢单笔也不攒着突发,写入量按身份合计;用合并两行既有规则付账。③ 复核清单:复核项的证据被销毁时记 NOT MEASURED,不记通过;同样以合并两行付账。④ 棘轮台账:platform-readings.md 上限 454 → 459,按常设例外记录。

为什么改:这是 2026-09-10 整个 fleet 被停用的事故复盘卡的后半。前半已把「限流绑定身份」写进通道表;但事实表里还留着相反的指引,席位照读就会重演事故里的那个动作。恢复流程当时是现场摸索出来的,没写下来下次还要摸一遍。写入合并的口径此前完全空白。

风险与代价(含回滚):纯文本规则与事实,不碰任何发布包,不改 CI 行为。代价是 platform-readings.md 多 5 行(每行 ≤120 字节,一事一行),其余三份文件行数不变。回滚 = revert 本 PR 的一个 commit,无迁移。误读风险:「写排队到重置」不是「所有写变慢」,新行已明写不放慢单笔。

席位意见:

你要做的:审批本 PR(受管面,需要你的 approve 后由席位入队);若认为「写量按身份计」或恢复五行的措辞有误,直接改文字或留言,席位按你的话修。


Generated by Claude Code

…account-suspension recovery readings

Second half of the shared-identity rate-limit card.

- platform-readings.md quota block: the two rows that still told a seat to
  try the other channel on a rate limit are re-conditioned in place on the
  GET /user identity test (403 and transport failures stay per-side
  readings); five new rows record the account-suspension signal and the
  measured recovery (branches and commits survive, a destroyed PR holds its
  branch name, the local object store is the backup, a rebuilt PR
  self-reports). +5 under the standing one-file exception, recorded as a
  ruledRaises record in the ratchet.
- SKILL.md 平台读数纪律: one rule line for merged writes (fewer and larger,
  no per-write slowing, no burst; write volume counts per identity), paid by
  folding the two maintainer-abort lines into one.
- review-checklist.md: the reviewing seat's NOT MEASURED line for destroyed
  evidence, paid by folding the dead-code deletion pair into one line.

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

claude Bot commented Sep 15, 2026

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 694b1d2841faf626fd3c0567153b3eefe50c594b

Reviewing seat: domain:skills, in-seat review at the contract-review tier (get_session at 2026-09-15T02:54Z: configured = session = last served, the constant's tier). Card #17374 (second half), claim 5673628965, report 5674013532; Clause-②: no on the claim and in the PR body.

① Derived judgments

  • Accept set / public surface: none moves — .claude/skills/pm-dispatch/SKILL.md (+2/−2), references/platform-readings.md (+7/−2), references/review-checklist.md (+2/−2), scripts/pm/check-skill-line-ratchet.mjs (+49/−1 — the ceiling 454 → 459 and the FIFTEENTH ruledRaises record quoting the standing exception verbatim). Nothing published; skip-changeset on the PR (read back with size/s, documentation).
  • Folds checked survivor by survivor on the head: SKILL.md 「维护者中止只在有显式信号时成立…」 + 「⛔ 不据推断立一道没有重启条件的门;判据是信号不是症状」 → one 108-B line keeping both rules (only the rationale clause 「判据是信号不是症状」 goes); review-checklist 「以死代码…核一次引用面」 + 「核过再 ACCEPT:…这一查只花十秒」 → one 113-B line keeping the rule and its ordering (the rationale goes). No rule is homeless.
  • The new rule (SKILL.md 〈平台读数纪律〉, 117 B): 「写少而大:同卡同轮结论合成一条评论,⛔ 不放慢单笔、不攒着一次性发;写量按身份计。」 — the card's expectation 2 with its acceptance 2 (no per-write slowing) stated in the line itself.
  • platform-readings :123 / :126 re-conditioned in place on the GET /user identity test (114 B / 109 B), consistent with rest-channel :12–:13; five new rows (108–114 B each, one event per row): the suspension signal, branches and commits survive, a destroyed PR holds its branch name, the local object store is the backup, a rebuilt PR self-reports four things. Dedup re-run by the seat on origin/main b3b43b6 (停用|销毁|幽灵|重推|重建|对象库|分支名|suspended → only the Routine-rebuild, transfer and branch-count rows; control 限流 → 10 lines): candidates 5 / landed 5 / already present 0 / refused 0 — the standing exception's conditions hold, per row.
  • Seat-run on the PR head in a detached worktree at 694b1d2: check-skill-line-ratchet.mjs ✓ (812/812, 459/459, 77/77), check-skill-frame-sync ✓, check-skill-id-lint 27 files clean; frame block :734–:755 md5 3327d02c56f8a0eca88569dad2270f32 unchanged; every added line ≤ 117 B by the seat's own awk length.
  • Gates: 40 derived / 40 run / 0 NOT-MEASURED / 0 UNRUN per the report, exits captured by redirect; the check:pm-dispatch-gates family ran detached and was waited on with tail --pid (its exit 0 captured through a wrapper on a second run) — the cap it exceeds is class (a): pnpm check:pm-dispatch-gates cannot complete inside the container's ~10-minute foreground cap — measured 9m0s then exit 124, twice; the wall-clock twin of #17708 #18201's card, not this PR's.

② Semver level

Not applicable — nothing published moves; skip-changeset measured by the fast track (.claude/**, scripts/pm/**).

③ Boundary flags

open_questions: none. Deviations (4), each answered: (1) review-checklist.md outside the claim's declared surface — the dispatch's own A5 asked for exactly that one line; the claim's file surface is amended on the card's ACCEPT; (2) the detached gate run — accepted as reported; (3) platform-readings :143 「报文里的 user ID 只是报文」 left as written beside the identity binding — recorded on the card as an equal-byte candidate for a later density pass, not a defect; (4) the model-free trailer pair — the repository's hook is the authority, accepted. Governed (SKILL.md = rules layer) ⇒ four-piece: needs-user-decision on the PR, 速读终稿 below, reviews requested; ⛔ not flipped ready by the seat before an authorized approval.

Implemented-by: claude/issue-17374-rate-limit-identity-second-half
Reviewed-by: session_01HZfg2AwVX191qCizp88gQr

VERDICT: PASS


Generated by Claude Code

@claude

claude Bot commented Sep 15, 2026

Copy link
Copy Markdown
Contributor Author

维护者速读(终稿)

改了什么:三份席位规则/事实文件加一份门禁台账。① 事实表 platform-readings.md 配额段:把两行还在教席位「限流了就换另一条通道继续写」的指引改成「先比身份,同一身份就停写排队到重置」;新增五行记下账号被停用时的识别信号与恢复四步(分支和提交不丢;被销毁的 PR 仍占着分支名,要换名重推;本地对象库是备份;重建的 PR 要自报四件)。② 席位规则 SKILL.md 加一行「写少而大」——同一张卡同一轮的结论合成一条评论,既不放慢单笔也不攒着突发,写入量按身份合计;用折叠两行既有规则付账。③ 复核清单加一行:复核项的证据被销毁时记 NOT MEASURED,不记通过;同样折两行付账。④ 棘轮台账:platform-readings.md 上限 454 → 459,第十五条常设例外记录。

为什么改:这是 2026-09-10 整个 fleet 被停用事故复盘卡的后半。前半已把「限流绑定身份」写进通道表;事实表里却还留着相反的指引,席位照读就会重演事故里的那个动作。恢复流程当时是现场摸索出来的,没写下来下次还要摸一遍;写入合并的口径此前完全空白。

风险与代价(含回滚):纯文本规则与事实,不碰任何发布包,不改 CI 行为。代价是 platform-readings.md 多 5 行(每行 ≤ 120 字节、一事一行,席位逐行核实,去重计数 候选 5 / 落地 5 / 已有 0 / 拒收 0),其余三份文件行数不变。回滚 = revert 本 PR 的一个 commit,无迁移。误读风险:「写排队到重置」不是「所有写都变慢」,新行已明写不放慢单笔。

席位意见:建议批准。本席核过:两处折叠的规则都幸存(砍掉的只是理由句);五行去重在 origin/main 上亲跑 grep 复核;四轴框架块 md5 不变;棘轮、frame-sync、id-lint 在 head 上全绿;--pair 0;CI 23 绿 / 11 跳过、Lint & Repo Gates 仍在跑(绿后才由本席按裁决 C 落地)。一处保留的张力不阻批:事实表 :143「报文里的 user ID 只是报文」与身份绑定并存,dev 建议等字节改为「只标身份」,留待下次密度轮。

你要做的(一个动作):approve 本 PR。approve 后本席翻 ready + 入队,你不必再点合并。


Generated by Claude Code

@claude
claude Bot requested review from hotlong and os-zhuang September 15, 2026 03:05
This was referenced Sep 15, 2026
@os-zhuang
os-zhuang marked this pull request as ready for review September 15, 2026 06:05
@os-zhuang
os-zhuang added this pull request to the merge queue Sep 15, 2026
@claude

claude Bot commented Sep 15, 2026

Copy link
Copy Markdown
Contributor Author

Ruling-C landing provenance — skills seat, session session_01HZfg2AwVX191qCizp88gQr, 2026-09-15T06:08Z. Authorized approval: os-zhuang's APPROVED review 5206118837 at 2026-09-15T06:05Z on head 694b1d2841faf626fd3c0567153b3eefe50c594b, the same head the review of record 5674046970 (## Contract review PASS) names and the ACCEPT 5674047468 on #17374 cites; not dismissed. The approver also flipped the PR ready (2026-09-15T06:05Z) and enqueued it (added_to_merge_queue 2026-09-15T06:05Z, actor os-zhuang; queue ref gh-readonly-queue/main/pr-18242-* present on origin at 2026-09-15T06:07Z), so the seat's remaining acts under ruling C are the readings and the label: check-clause2-carriers --pair 18242 exit 0 re-read at 2026-09-15T06:07Z with the C6-RECORD note naming that record; on that head Lint & Repo Gates and TypeScript Type Check both success and every other check is success or skipped; mergeable_state clean, 15 commits behind origin/main with no main commit touching any of the four files since the merge-base, so no branch update was made (the queue builds the merge); needs-user-decision cleared through label-write.mjs and read back. Serial note: PR #18250 (:645–:646) and PR #18256 (:306–:415) share SKILL.md in disjoint regions and merge origin/main at their own landing. MERGED is confirmed later by two readings (the queue ref gone + git log origin/main carrying (#18242) with one parent), then #17374's residue is stripped and the platform-readings family fold (#18158 · #18195 · #16762 · #18219 · #18147 · #18258 · #18262) is unblocked on platform-readings.md (ceiling 454 → 459 by this PR's ruledRaises entry).


Generated by Claude Code

Merged via the queue into main with commit 1a02ef1 Sep 15, 2026
44 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-17374-rate-limit-identity-second-half branch September 15, 2026 06:20
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 17, 2026
…air PR-NUMBER` is the only spelling (objectstack-ai#18250)

Fixes objectstack-ai#18181

## What changed

Three lines across two governed rules-layer files; line counts unchanged
(os-dev.md 403/403, SKILL.md 812/812), every edited line at or under the
120-byte cap, no re-wrap anywhere, and the decision-frame block SKILL.md
:734–:755 untouched (md5 `3327d02c56f8a0eca88569dad2270f32` before and
after).

**`.claude/agents/os-dev.md` :287** — the line the card names,
re-derived on `b3b43b6` (the card body's `:288` had drifted; the filer's
correction comment 5664488072 gives :287):

```diff
-- 认领写 `Clause-②: yes` ⇒ 开 PR 同笔挂 `needs:contract-review`,报告附 `--pair N` 退出码。
+- `needs:contract-review` 归席位,⛔ 不挂不摘不等;报 PR 上有无与 `--pair PR-NUMBER` 退出码作读数。
```

The dev no longer hangs the carrier. It never hangs, removes or waits
for `needs:contract-review`; it reports whether the label is present on
the PR and the `--pair PR-NUMBER` exit code — as a reading, not as a
gate it must clear.

**`.claude/agents/os-dev.md` :301** — the neighbouring `skip-changeset`
label-write rule (mechanism assumption A2, same file surface, same class
— an additive label write the container may refuse):

```diff
-- 被拒 ⇒ 停下报 `blocked` 点名端点与状态码,⛔ 不换 MCP 写道;写后必做对比式读回。
+- 被拒 ⇒ 报端点与状态码、席位代挂,⛔ 不报 `blocked`、不走 MCP;写后必做对比式读回。
```

One rule now covers both carriers: a refused additive label write is
reported with endpoint and status and the seat hangs the label; the dev
never reports `blocked` for it and never switches to an MCP write.

**`.claude/skills/pm-dispatch/SKILL.md` :646** — the one `--pair N`
token (REGION claim: this line only):

```diff
-- `Clause-②: yes` 认领同笔在卡上挂标;PR 开出即读 `check-clause2-carriers --pair N` 为 0 再请审。
+- `Clause-②: yes` 认领同笔卡上挂标;PR 开出读 `check-clause2-carriers --pair PR-NUMBER` 为 0 再请审。
```

`--pair PR-NUMBER` is now the only spelling in the three governed files
(contract-review.md :42 already spelled it so).

## Why

Direction A of the card's grading (comment 5673619664), executed as
ruled: the `needs:contract-review` carrier is the PM seat's to hang — on
the card at the claim and on the PR when the PR appears — which is what
every seat already does; the dev never blocks on a label write; `--pair
PR-NUMBER` becomes the only spelling. Option B (granting the dev
container the label write) is a permissions widening and was ruled not
this card.

The two defects the card and its correction measured: a dev that obeyed
:287 literally was refused the additive label write by its container's
write classifier on two independent objectui runs, while an objectstack
dev could write it — per container, unknowable in advance; and `--pair
N` bound `N` to the nearest referent, the card, while the script takes a
PR number and answers a card number with exit 2 (UNJUDGED), which a
naive capture reads as a red gate.

## Mechanism readings

- **A1** — :287 is the line on `b3b43b6` (108 B; the card's :288 is the
`skills/**` line). Replaced as above. Under the byte cap the `Clause-②:
yes` trigger did not fit, so the rule is now unconditional: on every PR
the dev hangs nothing, removes nothing, waits for nothing, and reports
carrier presence plus the `--pair PR-NUMBER` exit code. That superset is
deliberate — `--pair` also carries the widening-tell row, which only
fires on a `Clause-②: no` claim, so a reading on every PR is worth more
than one on `yes` claims alone.
- **A2** — measured on the text: :301's `被拒 ⇒ 停下报 blocked` is the same
container-refusal class one line over, and nothing about
`skip-changeset` requires the dev to block on it: CI's changeset gate
reads the label, the dev's report says whether it landed, and the seat
can hang it on that report. So one rule covers both carriers and :301
changed as above; :299 (the label is your step, hang it as soon as the
PR opens) and :300 (additive endpoint) stay — the dev still attempts the
write. The live half of the measurement is this PR's own
`skip-changeset` write; its outcome (landed, or the endpoint and status
of the refusal) is in the `os-dev-report` comment on the card, not here,
because the PR body is written before the label write and is not
re-sent.
- **A3** — `git grep -n -- '--pair N\b' -- '.claude/**'
'scripts/pm/**'`: before 6 hits (os-dev.md :287, SKILL.md :646, and 4 in
`scripts/pm/**`); after 4 hits, all outside the claimed surface:
`check-clause2-carriers.mjs` :24 and :490 (docblocks), :4947 (a
self-test title), and `check-widening-tells.mjs` :492 (docblock). In the
three governed files the count is 0. Lit control `--pair PR-NUMBER`: 1
before (contract-review.md :42), 3 after (:42, os-dev.md :287, SKILL.md
:646). The script docblocks bind `N` to a PR number in their own text
(:11 spells `--pair 13910 # ONE PR`), so the ambiguity is not live
there; noted below, not edited.
- **A4** — measured from the sibling checkout on this container: `cd
/home/user/objectui && PM_SWEEP_REPO=objectstack-ai/objectui node
../objectstack/scripts/pm/check-clause2-carriers.mjs --pair 9552` exits
0 with the provenance line `every row below is read from
objectstack-ai/objectui (source: PM_SWEEP_REPO)` and the verdict `PR
objectstack-ai#9552 / card objectstack-ai#6910 — the clause-② declaration is readable in the fixed
spelling and both carriers agree` (token present, 3 reads served; the
proxy re-exec happened by itself). A control on objectui#9495 (no longer
open) exits 2 — UNJUDGED, with the board still retargeted. So the
invocation works from a sibling checkout. The byte cap leaves no room on
:287 for the invocation (the line stands at 119 B), so the line carries
only the flag spelling; the script header (:20–:24) already spells the
sibling invocation and every run prints the board it read, which is the
remedy text the ruling asks to keep in the script.

## Gates

Derived with `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` (no paths; change set from the merge base):
18 families on `a2cbbef0`, re-derived on the merged head `0c934967`
(merge base `68fea8bc`) — identical list. All 18 run in the foreground
on `0c934967`, exit codes captured by redirect before any pipe, all 0:

`check-closing-keyword-parity` (+ `--self-test`) ·
`check-comment-mask-corpus` · `check-governed-queue-guard --self-test` ·
`check:doc-formula-expressions` (lint pkg) ·
`check:agent-model-declared` · `check:agent-test-spelling` ·
`check:commit-card-trailers` · `check:doc-authoring` ·
`check:driver-memory-census` · `check:nul-bytes` ·
`check:pm-governed-merges` · `check:pm-governed-prose` ·
`check:pm-skill-id-lint` · `check:pm-skill-ratchet` ·
`check:refd-timer-probe` · `check:skill-frame-sync` ·
`check:watch-hint-literal`

Reconciliation on `0c934967`: `✓ dispatch-gates --ran: 18 derived
famil(ies) accounted for — 18 run, 0 NOT-MEASURED (a DERIVED zero — all
18 recorded an exit code and none of them is 3)`.

Verdict lines: `✓ check-skill-line-ratchet: .claude/agents/os-dev.md is
403 lines (ceiling 403; headroom 0)` · `✓ check-skill-line-ratchet:
.claude/skills/pm-dispatch/SKILL.md is 812 lines (ceiling 812; headroom
0)` · `✓ check-agent-model-declared: 1 agent definition(s) under
.claude/agents/ all declare a model` · `✓ check-skill-id-lint: 27
file(s) clean` · `✓ check-skill-frame-sync: the one declared copy of the
decision frame is internally coherent`.

`check:doc-formula-expressions` first answered exit 3 (PREREQUISITE NOT
MET — `@objectstack/formula` and `@objectstack/lint` unbuilt in a fresh
worktree); prerequisite built under the verify lock (`turbo run build
--filter=@objectstack/formula --filter=@objectstack/lint`, VERDICT
command-exit 0), rebuilt again after the merge, then the gate exits 0.
The exit 3 is not a measurement and is not counted as one.

Not run locally, by design: the 11 wide-population families and the
whole-repo `pnpm lint` sweep belong to CI. Control-byte self-scan on
both edited files: no hits.

## Line budget

| file | before | after | ceiling | edited lines (bytes) |
| --- | --- | --- | --- | --- |
| `.claude/agents/os-dev.md` | 403 | 403 | 403 | :287 108→119 · :301
110→114 |
| `.claude/skills/pm-dispatch/SKILL.md` | 812 | 812 | 812 | :646 116→118
|

Paid by density in place; no line merged or split.

## Deviations, declared

1. SKILL.md :646: the token swap alone (`N` → `PR-NUMBER`, +8 B) puts
the line at 124 B, over the 120-byte cap. Two characters were dropped on
the same line (`在`, `即`), content unchanged, landing at 118 B. Still
inside the one-line REGION claim.
2. os-dev.md :287: the `Clause-②: yes` trigger is gone for the byte cap;
the rule is unconditional (see A1). If the seat wants the trigger back,
the price is the `作读数` tail or the `不等` limb.
3. os-dev.md :287 does not spell the objectui invocation (A4); the line
names the flag, the script header names the sibling form.
4. os-dev.md :301 edited under A2 (inside the file surface and the
card's class). objectui's copy and the dispatch-order template are
untouched.
5. `skip-changeset`: nothing published moves — `.claude/**` is on the
fast path (no package `files[]` ships it).
6. `origin/main` merged at `68fea8bc` (one spec commit, nothing on this
surface) before opening; objectstack-ai#18242 remains open on SKILL.md :172–:185 —
disjoint from :646.

## Acceptance notes

- **To file (class b, for the seat — dedupe words:
`needs:contract-review`, `PR 创建者`, `同笔挂`, SKILL.md, direction A):**
SKILL.md :645 still reads 「`needs:contract-review`(恒英文)由 PR
创建者随可复审契约增量同笔挂:draft PR,或先到的报告。」 For a dev-created PR the creator is the
dev, which direction A says never hangs it. Outside this card's REGION
claim (:646 only), so not edited here; it sits on the SKILL.md chain the
seat owns.
- noted, not filed: 4 `--pair N` spellings remain in `scripts/pm/**`
docblocks and one self-test title (listed under A3); each is next to
text that binds N to a PR number. 承接者:无.
- noted, not filed: os-dev.md :300's 「可达性按会话探,先探后用」 is redundant with
the new :301 — the write attempt is the probe. Left as is; deleting it
frees bytes, not a line. 承接者:无.
- Observed on this PR after opening: the labels present are reported in
the `os-dev-report` comment with a read-back; under one shared identity
the author field says nothing about who hung what.

## 维护者速读(草稿)

**改了什么**:改了两个受管规则文件里的三行,行数不变。开发 agent
定义(os-dev.md)一行:`needs:contract-review` 这个标签归席位挂,开发 agent
不挂、不摘、不等,只在报告里写 PR 上有没有它、以及 `--pair PR-NUMBER`
的退出码,作读数。另一行:加法写标签被容器拒绝时,报端点与状态码、由席位代挂,不再报 `blocked`、不走 MCP。PM
技能(SKILL.md)一个 token:`--pair N` 改成 `--pair PR-NUMBER`。

**为什么改**:两次独立实测里,开发 agent 的容器拒绝写标签,而规则要求它开 PR 同笔挂——照做就失败、如实报告又像抗命。分诊裁定 A
向:载体由席位挂(认领时挂卡、PR 出现时挂 PR),开发 agent 只报所见。同一行的 `N` 让人绑到卡号,脚本要的是 PR
号,给卡号返回 exit 2(未判),会被当红灯读;`PR-NUMBER` 是唯一拼写。

**风险与代价(含回滚)**:行数与上限不变,每行 ≤120 字节,决策框架块未动。风险是开发 agent 不再自己挂载体,若席位漏挂,条款②
PR 的载体会缺失——但席位侧已经是现行做法,且开发 agent 报告的 `--pair` 读数会暴露缺失。回滚 = revert 本
PR,无其他副作用。

**席位意见**:

**你要做的**:审阅上面三行措辞(尤其 :287 去掉了 `Clause-②: yes` 触发词、改为对每个 PR
都成立);同意即给授权批准,由席位落地;不同意请指出要保留的措辞。

---
_Generated by [Claude
Code](https://claude.ai/code/session_01HZfg2AwVX191qCizp88gQr)_

---------

Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 17, 2026
…quotes the governing line (objectstack-ai#18256)

Fixes objectstack-ai#17497

One rule line in 〈分诊座位职责〉 of `.claude/skills/pm-dispatch/SKILL.md`: a
triage grading whose direction lands inside an adjudicated area (an ADR,
a ruled card, a not-re-adjudicable rule) pastes the governing line — the
`Prior rulings read:` line that `check-prior-rulings.mjs` prints — or
names no target. Triage keeps writing directions (「写方向」 is charter
text); the line binds the direction's evidence, never removes the
direction.

Governed surface (`.claude/**`, rules layer): draft, no reviewer
requested, no ready flip — the skills seat hangs the four-piece and the
maintainer lands it. REGION claim :306–:415 only; PR objectstack-ai#18242 holds
:172–:185 and PR objectstack-ai#18250 holds :645–:646 on the same file and neither
range is touched here (the seat merges `origin/main` at landing).

## The added line (SKILL.md :339 on the head, 120 bytes)

```text
- 方向落在 ADR/已裁卡/不可重裁规则:贴 `check-prior-rulings.mjs` 的 `Prior rulings read:` 行,或不点。
```

Vocabulary is the corpus's own: 「已裁卡」 (SKILL.md 〈家族派发〉), 「不可重裁」
(〈派发词三分区〉), 「落点」 = the named fix target (〈分诊座位职责〉's `pm:queue`
definition). The tool is cited by file name and the paste line by its
literal prefix — no issue or PR number (`check:pm-skill-id-lint` green).

**Placement (P2, chosen by reading the section's order):** directly
under the line that mandates the direction — 「裁定写方向、给六态之一」 (:338 on the
head, :339 on `origin/main`). That is where 「写方向」 actually lives on this
tree: `references/lanes/triage.md` is 7 lines and its :7 is the
half-state rule, so the grading's citation of 「写方向」 at `triage.md :7` is
stale as a location while its substance holds. The English audit comment
line (`Triage: lands in …; rationale: …`, :364 on the head) was the
alternative; it names the artifact the direction is written into, but
the obligation reads best beside the mandate it qualifies, and 「方向」 is
anchored by the preceding line there.

**P1 confirmed by a live run** (`node scripts/pm/check-prior-rulings.mjs
--card 17497`, exit 0) — the tool printed exactly the shape the line
cites:

```text
Prior rulings read: triage,gradings,named,target,governing,itself,rejects,grading,quotes,line,adjudication,routes → 61 hits; ADR-0129 D4, ADR-0067 D3, ADR-0076 D11, ADR-0006 D2, ADR-0020 D2, ADR-0021 D1
```

`formatPasteLine` (`check-prior-rulings.mjs`) prints `Prior rulings
read: TERMS → N hits; ADR-xxxx Dn|none` and the whole-line form `Prior
rulings read: unresolved` when the search did not run;
`check-half-states.mjs` H62 reads the same key (`PRIOR_RULINGS_KEY`),
and `references/decision-analysis.md` already binds that line for
decision cards.

## Payment (P3, measured) — one adjacent pair inside the region
rewritten tighter

Before (`origin/main` :311 + :312, 89 B + 50 B):

```text
- fire 开局只按名加载互斥检查所需工具,`ToolSearch` 用 `select:` 形式。
- 判定本轮有活之后才加载其余工具。
```

After (head :311, 116 B):

```text
- fire 开局只按名加载互斥检查所需工具,`ToolSearch` 用 `select:`;判定本轮有活才加载其余。
```

Nothing operative is lost: every instruction token survives — `fire 开局`,
`只按名加载`, `互斥检查所需工具`, `ToolSearch`, `select:`, `判定本轮有活`, the ordering
`才加载其余` — and the three dropped words are filler (「形式」 names the code
span it follows; 「之后才」 → 「才」 keeps the only-after ordering; 「其余工具」 →
「其余」 in a line whose subject is tool loading). The PM's measured pairs
(:312+:314, :363+:364, :397+:398) all rewrite past 120 B; this is the
pair that fits. Not a re-wrap (no line-joining bought the budget), no
rule deleted, ceiling untouched.

## Reverse verification (on head `c96b507d`)

- `git diff origin/main -- .claude/skills/pm-dispatch/SKILL.md`: two
hunks, `@@ -308,8 +308,7 @@` (the fold) and `@@ -337,6 +336,7 @@` (the
insert) — both inside :306–:415; no other line moves.
- `wc -l` = 812 (ceiling 812, headroom 0 — the ratchet's own line:
`.claude/skills/pm-dispatch/SKILL.md is 812 lines (ceiling 812; headroom
0)`).
- `sed -n '734,755p' | md5sum` = `3327d02c56f8a0eca88569dad2270f32`
(frame block byte-identical).
- an `awk` length scan for lines longer than 120 bytes over :306–:415
prints nothing; touched lines measure 116 B (fold) and 120 B (new).
- Positive anchors: `grep -c 'Prior rulings read:'` = 1; the removed
:312 text = 0 hits; the fold text = 1 hit.

## Gates (derived, run in the foreground, exits captured before any
pipe, reconciled with `--ran`)

`node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` (no paths; change set derived off merge base
`cb648cb1`) → 16 commands; `--ran` verdict: `16 derived famil(ies)
accounted for — 16 run, 0 NOT-MEASURED (a DERIVED zero — all 16 recorded
an exit code and none of them is 3)`, exit 0.

| command | exit |
|---|---|
| `node scripts/check-closing-keyword-parity.mjs` | 0 |
| `node scripts/check-closing-keyword-parity.mjs --self-test` | 0 |
| `node scripts/check-comment-mask-corpus.mjs` | 0 |
| `node scripts/pm/check-governed-queue-guard.mjs --self-test` | 0 |
| `pnpm --filter @objectstack/lint run check:doc-formula-expressions` |
first run exit 3 `PREREQUISITE NOT MET` (unbuilt `@objectstack/formula`
/ `@objectstack/lint`) — built both under `os-verify-lock.sh` (`VERDICT
command-exit 0`), re-ran: 0 |
| `pnpm check:agent-test-spelling` | 0 |
| `pnpm check:doc-authoring` | 0 |
| `pnpm check:driver-memory-census` | 0 |
| `pnpm check:nul-bytes` | 0 |
| `pnpm check:pm-governed-merges` | 0 |
| `pnpm check:pm-governed-prose` | 0 (`2 instruction surface(s) name all
5 registered governed surfaces`) |
| `pnpm check:pm-skill-id-lint` | 0 (`27 file(s) clean`) |
| `pnpm check:pm-skill-ratchet` | 0 (812/812) |
| `pnpm check:refd-timer-probe` | 0 |
| `pnpm check:skill-frame-sync` | 0 (`the one declared copy of the
decision frame is internally coherent`) |
| `pnpm check:watch-hint-literal` | 0 |

Extra, outside the roster: `node scripts/check-skill-frame-freshness.mjs
--self-test` exit 0 and `--no-fetch` exit 0 (`the decision frame in this
tree is current with refs/remotes/origin/main`). The roster did not
derive `check:pm-dispatch-gates`, so no detached run was owed. Full
repo-level scans (`pnpm lint`) belong to CI.

`skip-changeset`: `.claude/**` is published by no package's `files[]`
(fast track) — nothing released moves.

## Acceptance notes

- noted, not filed: the grading's location citation for 「写方向」
(`lanes/triage.md :7`) is stale — that file's :7 is the half-state rule
and the charter text lives at SKILL.md :339 on `origin/main`; the
ruling's substance is unaffected. 承接者: the skills seat, at landing of
this PR.
- noted, not filed: the dispatch names a gate
`check:pm-skill-frame-sync`; the script's real name is
`check:skill-frame-sync` (`scripts/check-skill-frame-sync.mjs`), and it
ran green. 承接者: the dispatching PM seat, via this report.
- noted, not filed: the ratchet header's reading 「of the file's 601
adjacent bullet pairs ZERO merge under the 120-byte cap」 was a pure-join
measurement on an earlier tree; on this tree one pair folds at 116 B as
a tighter rewrite. Not a contradiction of that reading and not a defect.
承接者:无.

## 维护者速读(草稿)

**改了什么**:分诊座位职责里加一行:分诊的方向若落在 ADR、已裁卡或不可重裁规则内,须贴
`check-prior-rulings.mjs` 印出的 `Prior rulings read:` 行,否则不点落点。为了不突破 812
行上限,把开局工具加载的两行合成一行(116 字节),每条指令原样保留。

**为什么改**:两次分诊给出的落点恰是治理文本已经拒绝的拼写或形状(一次是字段 schema
拒收的扁平拼写,一次是标记为不可重裁的通则),都在执行席的围栏才被拦下,其中一次派发令已写出。分诊的推理没错,错在落点没有引治理行,所以看不见。决策卡那边已有同一行的约束,这次把它也绑到分诊评级上。

**风险与代价(含回滚)**:一行规则、一次同义压缩,不动上限、不删规则、不改工具。风险是新行 120 字节写得紧,读者需要认得
`check-prior-rulings.mjs` 这个工具(它已在仓内、有自检)。回滚 = revert 本 PR 一次提交。

**席位意见**:(留空,席位定稿成评论)

**你要做的**:确认这条规则的措辞与落位(放在「裁定写方向」那一行之下);同意即批准本 PR,由席位落地。

---
_Generated by [Claude
Code](https://claude.ai/code/session_01HZfg2AwVX191qCizp88gQr)_

Co-authored-by: Claude <noreply@anthropic.com>
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/s skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants