Skip to content

docs(pm): platform-readings retires the denied enqueue tool's rows and reads the REST token class per write, not per session - #18359

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-18320-platform-readings-denied-enqueue-rows-token-class
Sep 16, 2026
Merged

os-zhuang merged 1 commit into
mainfrom
claude/issue-18320-platform-readings-denied-enqueue-rows-token-class

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

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_merge and disable_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, which rest-channel.md owns.

  • :49 retired (the mergeMethod: "SQUASH" calling convention). It is a
    write-side recipe, and :133 of this same file rules that write-side recipes
    live in rest-channel.md and are never restated here —
    「逐操作通道归属、写侧配方与队列路由三读法见 rest-channel.md,⛔ 不在本表复述」.
    rest-channel.md:50 already carries the live spelling with
    {"merge_method":"SQUASH"}. Re-keying it here would have created the exact
    duplicate that row forbids.
  • :58 retired (the tool succeeds on an already-mergeable_state: clean PR,
    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.
  • :60 retired (quota exhaustion returns success with no mount, so verify the
    effect and not the echo). Its conclusion is carried for the live route by
    rest-channel.md:52 「⛔ auto_merge 与回显都不作数」, and inside this file by
    the surviving row 「回显两向不可靠 … ⛔ 不拿它当任何方向的证据」, which now stands
    directly above 「效果读数 = …」. The dispatch asked for no duplicate, so no
    replacement row was written.
  • :41 re-keyed TOOL-NEUTRAL, deliberately not keyed to the REST unload. The
    surviving truth (unloading auto-merge alone does not kick this repo's queue) was
    measured on the MCP disable call. I could cite no measurement of that same
    behaviour on DELETE .../ccr/auto_merge: rest-channel.md:51 measures only the
    422 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 单独不踢队。」
  • :50 re-keyed in place, forced by retiring :49: :50 opened with 「它」 and
    :49 was its antecedent. The reading is unchanged and the new subject is
    route-neutral — 「挂上的 auto-merge 存的方法恒为 merge」. This is the one hunk
    outside 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 :133 says it belongs.

Hunk B — the REST token class (the second card): lines 129-130 become 126-132

:129 said the class is fixed per session — 「按会话定」. Two measured flips inside
one session, each with no seat action and with GET /user constant, falsify that:

  • the triage seat, 2026-09-15: claude[bot] at 15:54Z, then os-sam at 22:55Z;
  • this seat, 2026-09-15 into 09-16: claude[bot] at 20:04Z, then os-zhuang at 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.login plus user.type) and is never
carried forward from the round-open marker, whose identity reading is dated rather
than standing; and performed_via_github_app, GET /user and the core rate-limit
header 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 under
the 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:

reading before (1411cf2c) after
grep -n -E 'enable_pr_auto_merge|disable_pr_auto_merge' :41 :49 :58 :60 zero hits (grep exit 1)
grep -n -E 'installation|user-to-server' :129 only :126 only
「按会话定」 present on :129 absent; :127 reads 「⛔ 不按会话定」
every-write read-back absent :129 「类只认每次写回读的 …」
consequence row absent :131 and :132
wc -l 463 465
widest line 120 B 120 B, zero lines over 120 B

Firing control — rest-channel.md is untouched and still carries the live route:
grep -n 'ccr/auto_merge' .claude/skills/pm-dispatch/references/rest-channel.md
still answers :50 and :51, byte-identical. git diff --stat against the merge
base is the one file, 9 insertions and 7 deletions.

Ratchet, both readings, quoted from the gate itself:

✓ check-skill-line-ratchet: .../platform-readings.md is 465 lines (ceiling 466; headroom 1).
✓ check-skill-line-ratchet: .../platform-readings.md: widest table row is 0 bytes (pin 0; headroom 0).

node scripts/pm/dispatch-gates.mjs --commands THE-FILE derived 16 families; all
16 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) and
the path face node scripts/pm/check-governed-merges.mjs --test THE-FILE, which
answers GOVERNED as expected.

No changeset: nothing versioned moves. .claude/** ships in no package files[].

Landing

This diff touches .claude/**, a governed surface (Prime Directive #14). Every
governed path lies under .claude/skills/pm-dispatch/references/, so the landing
tier is the skills seat's in-seat review at CONTRACT_REVIEW_TIER rather than the
maintainer'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

…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

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 8d99e6e573206f80655ad9194e8c6b0326c4ae2b

Reviewing seat: domain:skills, in-seat review at the contract-review tier — get_session read at 2026-09-16T03:45Z: configured_model = session_context.model = last_served_model, the constant's tier, no fallback notice this round. The fold #18320 + #18334, claims 5691538423 / 5691539100, report 5691695859 on #18320 (posted through the tool; author read back os-zhuang / User — the third live instance of #18334's own finding); Clause-②: no on both claims; default-tier build (no path mandate for references/**); seat review at tier.

① Derived judgments

  • Accept set / public surface: none moves — .claude/skills/pm-dispatch/references/platform-readings.md only, +9 / −7, 463 → 465 lines against merge base 1411cf2c (the branch is cut from the current origin/main); nothing published; skip-changeset read back (documentation + size/s + skip-changeset). GOVERNED by check-governed-merges --test (.claude/** ×1, exit 3 = the verdict) and the path is the references tier ⇒ in-seat landing after this record: ready through the CCR route, auto-merge SQUASH, the queue lands it.
  • The change, read whole on a detached worktree of the head — hunk A ([finding] platform-readings: four rows describe MCP enable_pr_auto_merge / disable_pr_auto_merge behaviour a seat can no longer reach once lock 1 denies the enqueue class (:41 · :49 · :58 · :60) #18320): :41 re-keyed tool-neutral 「补救:转 draft 与卸载 auto-merge 都做 —— 本仓卸载 auto-merge 单独不踢队。」 (the queue fact survives, the MCP name does not); :49 (mergeMethod: "SQUASH" for the denied tool) RETIRED rather than re-keyed — rest-channel :50 owns the live body and this file's :133 forbids restating write-side recipes here (the dispatch's 「only if the register does not already say so」 resolves to: it does, in the owning file); :50 given its subject back (「挂上的 auto-merge 存的方法恒为 merge …」) because :49 was its antecedent; :58 and :60 RETIRED (a behaviour of a denied tool; :60's rule is rest-channel :52's). No history row — the deny is recorded once at rest-channel :53, and zero mentions is the stronger AFTER. Hunk B ([finding] the seat's REST writes now land as a USER account, not claude[bot] — the durability premise in platform-readings, #6015 and every Routine prompt is falsified #18334): the two token-class rows become seven at :126–:132 — the two classes with their shared core pool; 「类按 Claude Code 账号定,⛔ 不按会话定:一会话内两次写之间可无席位动作地翻转」; the two dated flips (triage seat 09-15 claude[bot]os-sam; skills seat 09-15→16 claude[bot]os-zhuang); the class read from EVERY write's read-back, never carried from the round-open marker; three non-discriminators (performed_via_github_app, GET /user, the core rate-limit header — the third from [finding] the seat's REST writes now land as a USER account, not claude[bot] — the durability premise in platform-readings, #6015 and every Routine prompt is falsified #18334's own table); the consequence in two rows (user-class writes are author-bound — a suspended account 404s its comments and cards, labels / state / titles / bodies survive; 「耐久算按令牌类重算」 and the PR-author reviewer 422). Every edited or new line ≤120 B (the seat measured 0 lines over); check-skill-id-lint clean (no card numbers entered the file).
  • Seat re-runs on the head: check:pm-skill-ratchet 465 / 466 (headroom 1), widest-row pin 0; check:pm-skill-id-lint 27 files clean; check:skill-frame-sync coherent; check-governed-merges --test GOVERNED (.claude/** ×1); check-clause2-carriers --pair 18359 0 for BOTH cards; grep -c of the two tool names → 0; git diff --name-only origin/main...HEAD = the one file; rest-channel.md untouched (its :50–:52 still carry the live route — the firing control). Gates: 21 recorded by the dev — 16 derived, all exit 0 and reconciled by --ran (16 / 16 / 0, a DERIVED zero), check:doc-formula-expressions first NOT MEASURED then built under the verify lock and measured green; plus check:pm-settings-deny-roster 0 and the governed face at its own exit 3, outside the derivation and named as such; the closing-keyword parity check binds exactly Fixes #18320 + Fixes #18334 under both parsers.

② Semver level

Not applicable — nothing published moves; skip-changeset measured.

③ Boundary flags

open_questions: none. Two seat premises owned: the locator grep offered for :130 (installation|user-to-server) matches :129 only — the row exists with the described content, the locator was wrong; and rest-channel's direct-merge row is :54, not :55 (:55 is blank). Seven deviations answered: :50 edited beyond the four named lines — accepted (a dangling 「它」 after :49's retirement; the reading is unchanged, the subject made explicit); the consequence in two rows, not one — accepted (every one-row spelling measured 121–140 B); :129–:130 → seven rows — accepted (the dated flips and the non-discriminator list are not citable inside one 120-B row; 465 ≤ 466); the third non-discriminator (core 限流头) — accepted (measured by the triage seat on #18334: the limit header did not move with the identity); no history row — accepted (four_axes: the tightening, and rest-channel :53 records the deny); two gates beyond the derivation — accepted, additive; the formula / lint build to measure check:doc-formula-expressions — accepted. Out of scope, FILED by the seat as two cards of this lane: (1) #18360 — rest-channel :54 「REST 按会话为 claude[bot] 或用户」 carries the same falsified 「按会话」 premise this PR retires from platform-readings — a one-row in-place re-key, fold candidate with #18337 / #18339 on the same file; (2) #18361 — platform-readings :134 / :143 still list draft flips and auto-merge mounting among the operations with no REST counterpart and stand a seat down for them in a rate-limit window, contradicted by the live ccr routes (rest-channel :46 / :50) and by this file's own :47 — a judgment re-key of the GraphQL-only list, one line of ratchet headroom left. Noted, not filed: the retired :49 loosens the section's topical run around the surviving 「回显两向不可靠」 row (self-naming; no misreading). Landing: references tier — in-seat: this record, the ACCEPT on #18320 (the fold's primary, naming #18334), then ready through POST …/ccr/ready_for_review, PUT …/ccr/auto_merge SQUASH, the added_to_merge_queue reading and the queue ref, provenance on the PR; three landing readings, both cards' residue stripped, landing records on both.

Implemented-by: claude/issue-18320-platform-readings-denied-enqueue-rows-token-class
Reviewed-by: session_01HZfg2AwVX191qCizp88gQr

VERDICT: PASS


Generated by Claude Code

Copy link
Copy Markdown
Contributor Author

Landing provenance — skills seat, session session_01HZfg2AwVX191qCizp88gQr, 2026-09-16T03:58Z. References-tier landing (.claude/skills/pm-dispatch/references/**): review of record 5691731467 PASS on head 8d99e6e573206f80655ad9194e8c6b0326c4ae2b, ACCEPT 5691731638 on #18320 (the fold's primary; #18334 folded); every check on that head read complete with only success or a rostered skip at 2026-09-16T03:58Z (30 check-runs: 18 success · 12 skipped · 0 other — read with check-expected-skips.mjs --head, exit 0). The seat flipped the PR ready through POST …/pulls/18359/ccr/ready_for_review and armed auto-merge SQUASH through PUT …/pulls/18359/ccr/auto_merge; the added_to_merge_queue event reads 2026-09-16T03:58Z; the queue ref refs/heads/gh-readonly-queue/main/pr-18359-* read 1 fifteen seconds after the arm. ⛔ No approving review was written by this seat; the queue is the only landing path; merged_at and the single-parent squash are the landing readings, posted on both cards.


Generated by Claude Code

Merged via the queue into main with commit b57671e Sep 16, 2026
32 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-18320-platform-readings-denied-enqueue-rows-token-class branch September 16, 2026 04:18
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 17, 2026
…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>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 17, 2026
…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>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 17, 2026
…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>
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