docs(pm): platform-readings retires the denied enqueue tool's rows and reads the REST token class per write, not per session - #18359
Conversation
…d reads the REST token class per write, not per session The four rows that taught a seat how to call MCP `enable_pr_auto_merge` / `disable_pr_auto_merge` describe a tool lock 1 denies. The live enqueue route is the REST ccr route, whose write-side recipe `rest-channel.md` already owns, so three rows retire and the two surviving truths are re-keyed tool-neutral. The token-class rows said the REST channel's class is fixed per session. Two measured flips inside one session, with no seat action and `GET /user` constant, falsify that: the class follows the Claude Code account and is read on every write's own read-back. One consequence row records what a user-class write costs — author-bound durability, and a 422 when the PR's author is requested as its reviewer. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HZfg2AwVX191qCizp88gQr
Contract reviewServed-tier: Reviewing seat: ① Derived judgments
② Semver levelNot applicable — nothing published moves; ③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
|
Landing provenance — skills seat, session Generated by Claude Code |
…g seat reviews in-seat at CONTRACT_REVIEW_TIER, no seat list (objectstack-ai#18363) Fixes objectstack-ai#18349 Clause-②: no ## What changed The maintainer ruled (2026-09-16, recorded verbatim on the card) that the clause-② contract review is done in-seat by the dispatching seat at the contract-review tier, and that there is no longer a 「只在 spec 席」: > 1. 条款②复核 = 派发席在契约复审档席内做,记录写档位读数;不再有「只在 spec 席」。 > 2. 新 `packages/spec` 工作仍由 spec 席认领(现行规则,零延迟成本);顺手触 spec 的已派发卡照旧不转席。 Sentence 2 needs no text change (SKILL.md :527–:528 and contract-review.md :25 already say it). Sentence 1 re-keys the three lines that bound the review to a SEAT so that the served TIER is the condition — in place, no new line: | file:line | before (bytes) | after (bytes) | |---|---|---| | SKILL.md :513 | 「强制条款②:放宽接受集或扩大公开面的卡默认判断档施工;契约复审档复核只在 spec 席。」(118) | 「强制条款②:放宽接受集或扩大公开面的卡默认判断档施工;达档复核归派发席席内。」(112) | | SKILL.md :523 | 「契约复审档留给 skills 席与 spec 席的条款②复核,其余席位 ⛔ 永不起该档子代理。」(109) | 「条款②复核只在实测达档的席内做,记录写读数;未达档席 ⛔ 不审、不起该档子代理。」(114) | | contract-review.md :26 | 「逐车道:spec 席达档审契约增量;余席默认档自审加门禁、挂标;豁免仅独立性件与保险丝。」(118) | 「按档位:达档席内审契约增量;未达档默认档自审加门禁、挂标;豁免仅独立性件与保险丝。」(119) | Plus ONE new platform-readings.md row (:429, 113 B, in 「读数陷阱」 beside the other harness readings): 「harness 按内容拒写:同会话派发 PR 上 PASS 拒为 `[Self-Approval]`;同通道建卡、ACCEPT 照过。」 — measured 2026-09-16 on objectui PR objectstack-ai#9578: the harness auto-mode classifier refused a `VERDICT: PASS` record on a PR the same session had dispatched as `[Self-Approval]`, while a card create and an ACCEPT on the same channel were served seconds later; this seat's five same-shape PASS records of 2026-09-15 through `post-stamped.mjs` were not refused — so the reading is the channel-and-content pair, not the endpoint. Untouched: the SELF-REVIEW machine reading (contract-review.md :37 — `Implemented-by:` is the branch for a subagent dev) and the independence pair (:35–:36). Note: the card body and the ruling comment cite the 「逐车道」 line as contract-review.md :25 (read at `500c1b56`); on `origin/main` `1411cf2c` it is :26 (:25 is the 「归属该卡派发席」 line) — the edit went to the line by content, not by number. ## The one design choice — how the tier condition is spelled The question was how to spell the condition so a seat NOT at the tier cannot read itself as eligible. Options: (A) 「达档」 alone — a seat could read its configured tier as eligibility; (B) 「实测达档」 plus 「记录写读数」 — eligibility is the measured reading and the record must carry it (contract-review.md :29 already makes a record without `Served-tier:` no verdict; :53 already says a self-declared tier is not a reading), with the ⛔ half 「未达档席 ⛔ 不审、不起该档子代理」 closing the escape of spawning the tier subagent; (C) naming the `get_session` fields inline — over the 120-byte budget and already recorded at platform-readings :378 and contract-review :55. Chosen: B. On the four axes — 实际业务需求: the live case is real (objectui PR objectstack-ai#9578, a `Clause-②: yes` card held in the safe state) and every PM seat today measures at the tier, so in-seat review removes a handoff that had no executable spelling; 项目长远合理性: a tier condition is machine-checkable (`--pair` reads `Served-tier:`), a seat list is not; 防 AI 犯错: 实测 binds eligibility to a reading rather than a declaration, and the ⛔ half keeps the prohibition greppable; 创业阶段不扩散: no new mechanism, label, handoff artefact or line — three in-place re-keys and one fact row. ## Reverse verification BEFORE (`1411cf2c`): the three lines quoted above with their byte counts; sweep `grep -n '只在 spec 席\|其余席位 ⛔ 永不起该档\|spec 席达档审'` over SKILL.md and references = 3 hits (:513, :523, contract-review :26); `grep -c CONTRACT_REVIEW_TIER SKILL.md` = 1; SKILL.md 812 lines, contract-review.md 60, platform-readings.md 463; frame block :734–:755 md5 `3327d02c56f8a0eca88569dad2270f32`. AFTER (`55b467b0`): sweep = 0; constant count = 1 (unchanged); 812 / 812, 60 / 60, platform-readings 464 (ceiling 466; PR objectstack-ai#18359, open on other regions of the same file, nets +2, so 466 once both land and no 467th); frame md5 unchanged `3327d02c56f8a0eca88569dad2270f32`; `git diff --stat` = the three files and nothing else; zero raw control bytes in the touched files; every touched line ≤ 120 B (the ratchet's `MAX_LINE_BYTES`). ## Gates (run on `55b467b0`) `node scripts/pm/dispatch-gates.mjs --commands` over the three paths derived 18 commands; all 18 run; `--ran` reconciliation: 「18 derived, 18 run, 0 NOT-MEASURED, 0 UNRUN」. Green: `check:pm-skill-ratchet`, `check:pm-skill-id-lint`, `check:skill-frame-sync`, `check:nul-bytes`, `check:pm-governed-prose`, `check:pm-governed-merges`, `check:pm-half-states`, `check:required-contexts`, `check:doc-authoring`, `check:agent-test-spelling`, `check:watch-hint-literal`, `check:refd-timer-probe`, `check:driver-memory-census`, `check-closing-keyword-parity` (and its `--self-test`), `check-comment-mask-corpus`, `check-governed-queue-guard --self-test`, and `@objectstack/lint check:doc-formula-expressions` (first run exit 3 PREREQUISITE NOT MET — the formula and lint dist were not built; both built under the verify lock, re-run exit 0). Path face: `check-governed-merges.mjs --test SKILL.md` = GOVERNED (exit 3 is that verdict). The `check:pm-dispatch-gates` battery was not derived for these paths. `check-clause2-carriers.mjs --pair` on this PR: reading in the report comment on the card. ## Acceptance notes - Landing: the SKILL.md hunks are the rules layer, so this PR stays DRAFT; the seat hangs the four-piece and requests the authorized approval. No reviewer requested, no auto-merge, nothing flipped. - Region-level parallel: PR objectstack-ai#18357 (SKILL.md :626–:632) and PR objectstack-ai#18359 (platform-readings :38–:63 and :126–:133) are open on the same files in other regions; this branch was not rebased onto either — whichever lands second merges `origin/main`. - Nothing versioned moves (`.claude/**` is shipped by no package's `files[]`), so `skip-changeset`. - noted, not filed (承接者: the skills seat, on this card's patch round or a follow-up card): three lines OUTSIDE the declared file surface still spell the seat-bound rule and now read against the re-keyed lines — SKILL.md :641 「交付后复核由派发席席内完成:spec 席达档,余席默认档加门禁;记录 = 同形评论落 PR 或卡。」 (it escapes the sweep pattern: 「达档,」 not 「达档审」), contract-review.md :51 「保险丝只管 spec 席条款②复核与 skills 席…」 and :52 「余席条款②复核与总监席裁决非达档裁决,⛔ 不受本丝…」 (under the ruling every clause-② review is a tier verdict, so the fuse's population becomes every reviewing seat), and :41's 「档位按逐车道」. Not edited here: outside the surface, and the fuse's post-ruling shape (:51–:52) is a design choice rather than a mechanical re-key. `lanes/spec.md` and `lanes/skills.md`: no seat-bound restatement (spec.md :19, :20, :33 and skills.md :20 are trigger and gate lines only). - `dispatch-gates.mjs --tier` prints the tier's model literal rather than the constant name; this PR names only `CONTRACT_REVIEW_TIER`. ## 维护者速读(草稿) **改了什么**:把「条款②契约复核只在 spec 席做」改成「由派发席在契约复审档席内做,记录写档位读数;未达档的席不审、不起该档子代理」—— SKILL.md 两行、contract-review.md 一行原位改写,platform-readings.md 新增一行事实(harness 按内容拒写 PASS 裁决)。行数 812 / 812、60 / 60、464 / 466。 **为什么改**:您 2026-09-16 裁「同意」的两句里第一句要求去掉「只在 spec 席」;objectui 那张 Clause-② 卡在没有交接拼写的情况下被卡在安全态,席内达档复核是零延迟的路。第二句(新 spec 工作仍归 spec 席)现行文本已写,无需改。 **风险与代价(含回滚)**:三行改写全在 `.claude/**`,不发布、不动代码;回滚 = revert 本 PR 的一个 commit。残留:SKILL.md :641 与 contract-review.md :51–:52 仍按旧席位表述,本 PR 未动(超出派发面),需席位补一轮。 **席位意见**:(留空,席位定稿) **你要做的**:确认本 PR 的三行改写是否如实落了裁决第一句;确认后由席位走四件套 + 授权批准落地;顺带定 :641 / :51–:52 是并入本 PR 补丁轮还是另立卡。 --- _Generated by [Claude Code](https://claude.ai/code/session_01HZfg2AwVX191qCizp88gQr)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
…and the pipeline trap in post-stamped (objectstack-ai#18391) Fixes objectstack-ai#18337 Fixes objectstack-ai#18339 Fixes objectstack-ai#18360 Three write-side lines of `.claude/skills/pm-dispatch/references/rest-channel.md` — the table a seat opens before hand-rolling a REST write — plus the same pipeline trap in the header of `scripts/pm/post-stamped.mjs`, which is the file opened before that idiom is typed. One commit per card. The file sits at its ratchet ceiling with zero headroom (82 / 82), so the two added rules are paid for by two retired rows, each declared below with the surviving home of every clause it carried. **82 lines before, 82 lines after.** No re-wrap was used as currency, and `references/platform-readings.md` is untouched. ## Per card ### objectstack-ai#18337 — the pipeline-exit trap, beside the two tools it names **Landing point (by content):** the write-side block, immediately after the `label-write.mjs` row. **Before:** no such line; the trap was written down only in one lane's private Routine prompt. **After (verbatim):** ``` - ⛔ `post-stamped`/`label-write` 永不接进管道再 `&&`:拒收读成 0;看尾先落文件或 `set -o pipefail`。 ``` **Second half — the tool's own header.** `scripts/pm/post-stamped.mjs` gains one section, `## ⛔ Never pipe this tool, then && the write that follows`, which names the half-state shape (refused audit comment, label landed anyway, card graded with nothing saying why), the redirect-then-capture spelling, and `set -o pipefail`. It states the shape rather than citing a card number, and it does not restate the generic mechanism: that reading keeps its single home in `references/platform-readings.md`. **How this card reads independently:** both places carry the trap — the write-side block (grep the table for `pipefail`: was 0, is 1) and the tool header a seat opens before typing the idiom (`grep -c 'set -o pipefail' scripts/pm/post-stamped.mjs`). **Paid by:** retiring the write-side row ``` - 入队读 timeline `added_to_merge_queue`,落地读 `git rev-list --parents`;⛔ `auto_merge` 与回显都不作数。 ``` Every clause of it survives, twice over: `added_to_merge_queue` is already a judgment row in this same file's queue-routing block; `git rev-list --parents` is a spelling this file's own 第三桶 block declares to live in `platform-readings.md`, where it is written with its `-n1`; and the auto_merge / echo-back unreliability is in `platform-readings.md` twice plus this file's own queue-routing rule that the answer comes from the attempted action and not from the attribute field. It is the row this file's own line 「本表只指路,⛔ 不在两处各存一份」 refuses to keep. ### objectstack-ai#18339 — the mandatory header, on the table seats consult **Landing point (by content):** the write-side block, immediately after the request-body line 「请求体走文件…或引号定界 heredoc」, which is the hook the card names. **Before:** the block carried zero mentions and zero cross-references of the header (`content-type`, case-insensitive, over the whole table: 0 hits, with `GET` at 12 hits as the firing control). **After (verbatim):** ``` - 每个写请求必带 `Content-Type: application/json`;缺头的 415 与判别式见配额段。 ``` **How this card reads independently:** the reader path from the write-side block to the rule is now **0 hops** — the rule is in the block — and the reading it is measured from (the 415, and the discriminator that tells a proxy refusal from a GitHub one) is **1 hop**, named rather than copied. Was ∞. The two `platform-readings.md` rows are not copied and not edited; the pointer uses this table's existing spelling for that section, which its own opening line and its 不可迁移 heading already use. **Paid by:** retiring the reading beside it, ``` - 双引号内 shell 先展开反引号、`$(...)`、`$VAR`,请求尚未成形;只标题坏而正文完好即此形。 ``` Both of its facts are in `platform-readings.md` verbatim: the backtick expansion with the broken-title-intact-body symptom, and the quoted-delimiter rule whose general form is 「请求体永不过会展开的 shell 上下文」, which is what covers `$(...)` and `$VAR`. The prescription those bytes explained — body via file or quoted heredoc, never an inline double-quoted string, with both spellings — stays untouched on the line above. ### objectstack-ai#18360 — the direct-merge actor follows the token class, not the session **Landing point (by content):** the write-side block's direct-merge row. **Before (verbatim):** ``` - 直合仓 `PUT .../pulls/{n}/merge`;actor 记通道令牌:REST 按会话为 `claude[bot]` 或用户,MCP 恒用户。 ``` **After (verbatim):** ``` - 直合仓 `PUT .../pulls/{n}/merge`;actor 记令牌类,按账号非会话、逐写回读;见配额段,MCP 恒用户。 ``` **How this card reads independently:** `grep -c '按会话' ` on the file is 0 (was 1, that row); the row now says the actor is the token class, bound to the account and read back on each write; the fact-table rows landed by objectstack-ai#18359 are pointed at, not copied; `MCP 恒用户` stays, because it is this table's own fact and has no other home. In place, 120 bytes, at the cap the ratchet sets. ## Line budget | | lines | |:---|---:| | `rest-channel.md` before | 82 | | `rest-channel.md` after | 82 | | ceiling | 82 | Added 2 rules, retired 2 rows, re-keyed 1 row in place. `scripts/pm/post-stamped.mjs` carries no ceiling (it is not in the ratchet's CEILINGS map — checked, not assumed). Every touched line is within the 120-byte cap; the two new rules measure 119 B and 98 B, the re-keyed row 120 B. ## Gates All 39 commands derived by `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` against this branch's real change set were run, exit code captured before any pipe. 38 of 39 are green, including `pnpm check:pm-skill-ratchet` (its own verdict line: `rest-channel.md is 82 lines (ceiling 82; headroom 0)`), `pnpm check:pm-post-stamped` (233 cases), `pnpm check:nul-bytes`, `pnpm check:doc-authoring`, `pnpm check:pm-governed-merges` and `pnpm check:pm-skill-id-lint`. `pnpm --filter @objectstack/lint run check:doc-formula-expressions` first exited 3, PREREQUISITE NOT MET (unbuilt workspace packages, nothing measured); after `turbo run build --filter=@objectstack/formula --filter=@objectstack/lint` it exits 0. The remaining command, `pnpm check:pm-dispatch-gates` (a 430–450 s battery), was still running when this PR was opened; its verdict is in the dev report on objectstack-ai#18337. `node scripts/pm/check-governed-merges.mjs --test .claude/skills/pm-dispatch/references/rest-channel.md` exits 3 — GOVERNED, `.claude/**`. ⛔ No seat flips this ready, enqueues it or arms auto-merge; it lands after the skills seat's contract-tier review. ## Acceptance notes - Noted, not filed: nothing else in the write-side block was touched. The `-d @file` flag and the quoted-heredoc delimiter spelling, the shell-expansion prescription, and every ✓ channel row are unchanged. - To file (out of scope here, `scripts/pm/check-half-states.mjs`): H64's header quotes this same retired premise as its rationale — 「REST 按会话为 `claude[bot]` 或用户,MCP 恒用户」, sourced to `rest-channel.md`, with the surrounding prose reading "the write identity follows the CHANNEL rather than the account" and, further down, "the token class is handed to a session at start rather than chosen at write time". The landed fact table says the opposite in as many words: 「类按 Claude Code 账号定,⛔ 不按会话定」 and 「一会话内两次写之间可无席位动作地翻转」. The row's runtime message text carries the same sourcing. Prose only, no enforcement change; this PR's claim names `rest-channel.md` and `post-stamped.mjs` and nothing else, so it is reported rather than fixed here. Dedup words: `H64 按会话` · `check-half-states token class` · `write identity follows the channel` · `session-handed token`. ## 维护者速读(草稿) **改了什么** —— pm-dispatch 的 REST 通道对照表(席位手搓写请求前查的那张表)的写侧段,加了两条规则、改写了一条、退役了两条重复行;另外给 `scripts/pm/post-stamped.mjs` 的文件头加了一段管道陷阱说明。表的总行数没变(82 行,正好在棘轮天花板上)。 **为什么改** —— 三张 p3 卡,同一个形状:规则存在,但不在读者动手时会看的那张表上。① 把写脚本接进管道再 `&&`,管道退出码取末端,于是审计评论被拒、标签照落,卡上留下「改了标、没理由」;这条陷阱原先只写在某条车道的私有唤醒提示词里。② REST 写必带 `Content-Type: application/json`,缺了代理回 415、一个字节都不写 —— 规则写在事实表里,写侧段零提及,一个班次内六次独立踩中、四个端点,而失败长得像「别人并发把我的标签冲掉了」,会把一个加一个头就能修的问题误诊成分布式竞态。③ 直合的 actor 那行还写着「按会话」,而事实表已经改成「按 Claude Code 账号」,并记录了一个会话内无人操作就翻转两次的实测。 **风险与代价(含回滚)** —— 纯文本面,无代码行为改动,无 changeset(`.claude/**` 与 `scripts/pm/**` 不发布)。代价是退役了两行:每一行的每个子句都在别处有家(逐条列在上面的 Paid by 里),这是天花板零余量下唯一合法的付账方式 —— 删内容,不是折行。若判定某条退役行不该退,回滚是一次 revert:三个 commit 逐卡独立,可以只回其中一个。 **席位意见** —— **你要做的** —— 复核两处:① 两行退役是否同意(它们的存续副本是否真的够用);② 三条新/改写的行文字是否准确 —— 尤其 objectstack-ai#18360 那行为了压进 120 字节,把「按 Claude Code 账号」压成「按账号」,完整拼写留在它指向的配额段。这是受管面(`.claude/**`),⛔ 不会有任何席位把它 ready 或入队。 --- _Generated by [Claude Code](https://claude.ai/code/session_01Bz6hxDBqK62NP2W1LATvnt)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
…e ccr routes are counted (objectstack-ai#18396) Fixes objectstack-ai#18361 `references/platform-readings.md` only, in place at **466 / 466 lines** (ratchet ceiling, headroom 0). **Four** rows re-keyed by judgment; no row added, none removed, no re-wrap, every rewritten line under the gate's 120-byte cap. Three rows landed in the first commit (`b0c55121`). The fourth arrived with the seat's review (comment 5694684277 on objectstack-ai#18361), which widened this claim's file surface by one row: a fact table does not carry a row whose job is to say the row above it is false, so the row above is re-keyed in place instead — second commit `a7026a35`, no force-push, no rebase. Rows are located by CONTENT, not by the card's line numbers: the card was filed against `1411cf2c` and the file has landed twice since (`b57671ed` objectstack-ai#18359 at 04:18Z, `05f1cdd6` objectstack-ai#18363 at 06:40Z), so :134 / :143 / :57 have drifted. ## Row 1 — the GraphQL-only membership row (line 134 on this branch) Landing point: the row directly under 「GraphQL 池 5000/时,只留给没有 REST 对应物的几件。」 before (verbatim, 113 B): ``` - 那几件 = draft 翻转、auto-merge 挂载、语义 `/search/*`、Projects field_values、`issue transfer`。 ``` after (verbatim, 118 B): ``` - 那几件 = `rest-channel.md`〈不可迁移〉那三件;draft 翻转与 auto-merge 挂载有 ccr 路、不在内。 ``` ## Row 2 — the queue-repo landing row (line 142), widened into scope by the seat's review Landing point: the row directly under 「红窗调度:守候只给上面那几件 GraphQL-only 的,⛔ 其余一切不为配额空等。」 before (verbatim, 107 B): ``` - 走队列的仓落地必经 auto-merge ⇒ 红窗里无退路;直合仓有,合并本身有 REST 端点。 ``` after (verbatim, 118 B): ``` - 走队列的仓落地仍必经 auto-merge,红窗里照样走 ccr REST 挂载;直合仓合并本身有 REST 端点。 ``` The row's premise — that arming is GraphQL-only — is the same one the card retires, so the row is retired with it. What survives is what is still true: a queue repo's landing still goes through auto-merge (the queue is not bypassed), the mount in a red window rides the ccr REST route, and the direct-merge half keeps its REST endpoint. ## Row 3 — the red-window stand-down row (line 143) Landing point: the row directly under row 2. before (verbatim, 70 B): ``` - MCP 侧限流下,draft 翻转与 auto-merge 挂载整窗不可得。 ``` after (verbatim, 89 B): ``` - 红窗里 draft 翻转与 auto-merge 挂载照常走 ccr REST,⛔ 不为它们守候。 ``` The first commit ended this row with 「⇒ 上条无退路不成立」, a clause whose only job was to void the row above. The review rejected that shape — correctly: a reader of a fact table should not have to hold two rows in mind to get one fact. With row 2 re-keyed, the clause has nothing to do and is gone. ## Row 4 — the echo row (line 57), carrying comment 5693336603's mechanism Landing point: the row directly under 「squash 的 committer date 是入队时刻,快进不改 ⇒ 落地时刻读 merged_at/merged,不读 git log。」 before (verbatim, 114 B): ``` - 回显两向不可靠,空回显不等于未挂上 ⇒ ⛔ 不拿它当任何方向的证据、不为它空转。 ``` after (verbatim, 115 B): ``` - 回显两向不可靠:全绿加 `clean` 即吞进队列不存记录 ⇒ 空回显是成功常态,⛔ 不空转。 ``` The byte WAS there, so the note is carried here rather than left on the card: the mechanism (all checks green plus `mergeable_state: clean` is consumed straight into the queue and stores no record, so an empty echo is the normal echo of SUCCESS) replaces two clauses the row no longer has to spend bytes on. `空回显不等于未挂上` is subsumed by the stronger `空回显是成功常态`, and `⛔ 不拿它当任何方向的证据` follows from `回显两向不可靠` in the same sentence. The note's third half — never re-arm on a NULL alone — is NOT restated: the file already carries it at 「⛔ 永不据它判没挂上而重挂 —— 重挂踢队重排。」, and a second copy is what this card exists to remove. ## The card's question: mirror rest-channel's 「不可迁移 —— 只有这三件」, or point at it **Point at it.** Three reasons, in descending authority: 1. A maintainer ruling already assigns ownership. The 2026-09-01 batch (总监批 objectstack-ai#22, quoted in `scripts/pm/check-skill-line-ratchet.mjs`'s ceiling comment for `rest-channel.md`) reads, verbatim and untranslated: 「**C 同批**:退役 L58 对红窗常设规则的复述 —— 内容归属裁定:**红窗规则由 `platform-readings.md` 配额段独家持有**,`rest-channel.md` 只留指路」. The split it draws is symmetric: red-window POLICY is this file's, per-operation CHANNEL MAPPING is rest-channel's. Which operations have no REST twin is channel mapping. 2. This file already says so in its own voice, one row below the one being re-keyed: 「逐操作通道归属、写侧配方与队列路由三读法见 rest-channel.md,⛔ 不在本表复述。」 The row this PR re-keys was the one row in breach of its own neighbour. 3. The drift is the evidence. rest-channel's list stayed correct across lock 1; the mirror here went stale and stood a seat down for two operations that had a route the whole time. A mirror re-opens exactly that channel. rest-channel's heading already carries the return pointer (「红窗守候规则住 platform-readings.md 配额段」), so after this PR each fact has exactly one home and each home points at the other. What is NOT delegated is the JUDGMENT the card asked for: the two operations that left the list are named here, in the negative, because a seat that remembers the old list needs the deletion stated, not merely absent. The three that stay are not re-listed. ⛔ `rest-channel.md` is not touched by this PR. Its rows 46 and 50 are already correct. ## Acceptance notes - Adjacent rows read and left standing because they stay TRUE under the re-key: 「红窗调度:守候只给上面那几件 GraphQL-only 的…」 (line 141) becomes automatically correct once line 134 points at the three; and 「被挡住的翻转是在等窗口,不是关于该 PR 的信号…」 (line 144) survives, because a flip can still be blocked by the session gate (`GitHub access is not enabled for this session`) or by REST core, so its prohibition still has a referent. - noted, not filed: 「走队列的仓落地仍必经 auto-merge」 still reads as if arming were the only landing path. It is, on this repo, under Prime Directive 14's governed-surface carve-out and the queue rules — but the row does not say which of those makes it so. 承接者: none; recorded here so the next author of this block knows it was read and judged, not missed. - Out-of-scope finding reported to the seat (class b, contract violation): the spec seat's 2026-09-16T06:19Z measurement (seat post objectstack-ai#6017, comment 5692971090) — no `enqueued` event on this repo's `/issues/N/timeline`, the queue ref answering BUILD rather than membership, and `auto_merge` cleared on enqueue — conflicts with rows 10–12 of this same file, which make the timeline event the decisive membership reading. Out of this card's scope, untouched here. Row 4's rewrite was written to be consistent with it (a consumed request stores no record), not against it. ## Gates Targeted set for a `.claude/skills/**` docs-only diff, re-derived from the actual change at the new head with `node scripts/pm/dispatch-gates.mjs --commands` (no paths) and reconciled with `--ran`: 16 derived / 16 run / 0 NOT-MEASURED / 0 UNRUN, the same 16 as the first round. Exit codes captured to a file before reading, never across a pipe. Ratchet at the new head: 466 lines, ceiling 466, headroom 0. Per-command results are in the report comments on objectstack-ai#18361. `skip-changeset`: nothing published moves — `.claude/**` is not in any package's `files[]`, it is the fast track, not a measurement. ## 维护者速读(草稿) **改了什么** —— 只动 `references/platform-readings.md` 四行,行数 466 → 466。一是 GraphQL-only 清单那行:不再自己列五件,改成指向 `rest-channel.md`〈不可迁移〉的三件,并写明 draft 翻转与 auto-merge 挂载有 ccr 路、不在其内。二是「走队列的仓落地必经 auto-merge ⇒ 红窗里无退路」那行:改成落地仍必经 auto-merge、红窗里照样走 ccr REST 挂载,直合仓那半保留。三是红窗那行:两件在限流窗里照常走 ccr REST,别为它们守候。四是回显那行:补进「全绿加 clean 即被吞进队列、不存记录,所以空回显是成功的常态」这条机制。 **为什么改** —— 锁 1 之后 ccr 路是这两件操作的唯一通道,而清单还把它们记作没有 REST 对应物。后果不是措辞问题:读到红窗那几行的席位会白等一个根本不用等的窗口。清单归属有 2026-09-01 的裁定在先(红窗规则归本表独家,通道归属归 `rest-channel.md`),所以这次是把重复的那份退役成指路,不是再抄一遍。第二行是席位复核时扩进来的:原先我只在下一行写「上条无退路不成立」,事实表不该有一行专门宣布上一行为假,现在把上一行本身改对了。 **风险与代价(含回滚)** —— 风险有两处。① 席位要判某个操作是不是 GraphQL-only 时,多读一个文件(`rest-channel.md`,82 行、零配额);换来的是清单只有一份、不会再各自漂移。② 这四行都在同一段里,措辞收紧后相邻行的语气略有重叠(红窗里走 ccr REST 这件事在两行都出现一次),是刻意的:一行讲落地路径,一行讲要不要守候。回滚代价极低:四行就地改写,两个提交都可 `git revert`,棘轮读数不变。 **席位意见** —— **你要做的** —— 若同意「指路而不镜像」这一判断,按 references 档在席复核后落地即可;若更希望本表继续自列三件(以红窗里少读一个文件换一份可能再漂的副本),请在此说明,我改回列举形。 --- _Generated by [Claude Code](https://claude.ai/code)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
Fixes #18320
Fixes #18334
ONE file, one fold:
.claude/skills/pm-dispatch/references/platform-readings.md.463 to 465 lines (ratchet ceiling 466, headroom 1); every edited and every added
line is at or under 120 bytes; the widest-table-row pin stays at 0.
Hunk A — the denied enqueue tool's rows (the first card): lines 41, 49, 50, 58, 60
Lock 1 denies MCP
enable_pr_auto_mergeanddisable_pr_auto_merge(PR #18317),and four rows still taught a seat how to call them. The live enqueue route is
REST
PUT .../pulls/{n}/ccr/auto_merge, whichrest-channel.mdowns.:49retired (themergeMethod: "SQUASH"calling convention). It is awrite-side recipe, and
:133of this same file rules that write-side recipeslive in
rest-channel.mdand are never restated here —「逐操作通道归属、写侧配方与队列路由三读法见
rest-channel.md,⛔ 不在本表复述」.rest-channel.md:50already carries the live spelling with{"merge_method":"SQUASH"}. Re-keying it here would have created the exactduplicate that row forbids.
:58retired (the tool succeeds on an already-mergeable_state: cleanPR,against its own description). Its entire content is a contradiction between one
MCP tool and that tool's description; nothing of it survives for the REST route.
:60retired (quota exhaustion returns success with no mount, so verify theeffect and not the echo). Its conclusion is carried for the live route by
rest-channel.md:52「⛔auto_merge与回显都不作数」, and inside this file bythe surviving row 「回显两向不可靠 … ⛔ 不拿它当任何方向的证据」, which now stands
directly above 「效果读数 = …」. The dispatch asked for no duplicate, so no
replacement row was written.
:41re-keyed TOOL-NEUTRAL, deliberately not keyed to the REST unload. Thesurviving truth (unloading auto-merge alone does not kick this repo's queue) was
measured on the MCP
disablecall. I could cite no measurement of that samebehaviour on
DELETE .../ccr/auto_merge:rest-channel.md:51measures only the422 an unmounted PR answers, which is a different question. Keying the row to the
REST route would have asserted an unmeasured platform fact, so it now reads
「补救:转 draft 与卸载 auto-merge 都做 —— 本仓卸载 auto-merge 单独不踢队。」
:50re-keyed in place, forced by retiring:49::50opened with 「它」 and:49was its antecedent. The reading is unchanged and the new subject isroute-neutral — 「挂上的 auto-merge 存的方法恒为
merge」. This is the one hunkoutside the four named lines, and it is declared as a deviation in the report.
After this hunk, zero rows in the file name either denied tool in any voice, so no
history row was needed either: the deny itself is already recorded for the
operation in
rest-channel.md:53, which is where:133says it belongs.Hunk B — the REST token class (the second card): lines 129-130 become 126-132
:129said the class is fixed per session — 「按会话定」. Two measured flips insideone session, each with no seat action and with
GET /userconstant, falsify that:claude[bot]at 15:54Z, thenos-samat 22:55Z;claude[bot]at 20:04Z, thenos-zhuangat 01:53Z.The rows now say: two classes, both at core 15,000/h; the class follows the Claude
Code account and NOT the session, and can flip between two writes of one session
with no seat action; both flips are named by date and account; the class is read
from EVERY write's own read-back (
user.loginplususer.type) and is nevercarried forward from the round-open marker, whose identity reading is dated rather
than standing; and
performed_via_github_app,GET /userand the core rate-limitheader all answer the same for both classes, so none of the three discriminates.
The consequence is split across two rows because it does not fit one 120-byte line:
a user-class write is author-bound — a suspended account 404s its comments and its
filed cards, while labels, state, titles and bodies survive — so the durability
calculus is re-run per class and never assumed to sign as
claude[bot]; and underthe user class the PR's author IS that user, so requesting them as reviewer answers
422. That 422 was measured on PR #18351 at 02:32Z. The second measurement table
comes from #18350, which the second card carries as its duplicate.
Verification
Reverse verification, before and after, on the one file:
1411cf2c)grep -n -E 'enable_pr_auto_merge|disable_pr_auto_merge':41 :49 :58 :60grep -n -E 'installation|user-to-server':129only:126only:129:127reads 「⛔ 不按会话定」:129「类只认每次写回读的 …」:131and:132wc -lFiring control —
rest-channel.mdis untouched and still carries the live route:grep -n 'ccr/auto_merge' .claude/skills/pm-dispatch/references/rest-channel.mdstill answers
:50and:51, byte-identical.git diff --statagainst the mergebase is the one file, 9 insertions and 7 deletions.
Ratchet, both readings, quoted from the gate itself:
node scripts/pm/dispatch-gates.mjs --commands THE-FILEderived 16 families; all16 were run, every one at exit 0, and reconciled with
--ran:✓ dispatch-gates --ran: 16 derived famil(ies) accounted for — 16 run, 0 NOT-MEASURED.Outside that derivation I also ran
pnpm check:pm-settings-deny-roster(its roster sits under.claude/, so its silence would not have been evidence in either direction) andthe path face
node scripts/pm/check-governed-merges.mjs --test THE-FILE, whichanswers GOVERNED as expected.
No changeset: nothing versioned moves.
.claude/**ships in no packagefiles[].Landing
This diff touches
.claude/**, a governed surface (Prime Directive #14). Everygoverned path lies under
.claude/skills/pm-dispatch/references/, so the landingtier is the skills seat's in-seat review at
CONTRACT_REVIEW_TIERrather than themaintainer's word. This PR stays DRAFT. I requested no reviewers, touched no
ccr route, armed no auto-merge and flipped nothing.
维护者速读(草稿)
改了什么 — 派发座位的平台事实表改了两处。一是删掉三条、改写两条教座位去调一个已被
锁 1 禁掉的 MCP 入队工具的规则,活路线的写法本来就在
rest-channel.md。二是把「REST 写的身份按会话固定」这条改成实测的样子:身份跟 Claude Code 账号走,一次会话中间会变,每次
写都要自己回读一次。
为什么改 — 这两条都是写着的事实与实测不符。前者让座位学一个它调不到的工具;后者更贵:
座位的耐久性判断(评论和卡会不会随账号被封而 404)整个建立在「署名恒为
claude[bot]」上,而这一班里已经两次实测到写入落成了用户账号。四天内已经有两个分诊账号在班中被封并因此
丢掉全部书面记录,所以这条假设错的方向正是丢数据的方向。
风险与代价(含回滚) — 只动一个内部指令文件,不发布、不进任何包、无运行时影响。棘轮还
剩 1 行余量。回滚 = revert 这一个 commit。⛔ 本 PR 不改写入通道本身:换一个低权限账号或
改回 App installation token 是维护者的决定,这里只把事实表改成实测的样子。
席位意见 —
你要做的 — 目前不需要你做任何事。这是 references 层,按现行分层由技能席位在座评审后
自己落地;⛔ 不需要你的批准。若你希望把「写入身份」这件事本身处理掉(专用低权限账号,或
恢复 App installation token),那是另一张卡。
Generated by Claude Code