docs(pm): platform-readings reads the denied MCP wrapper's footer rows as history - #18316
Conversation
…s as history Three rows of the PR-body footer block still read the MCP update_pull_request wrapper as a live edit channel. Lock 1 denies that tool, so two of the rows describe a call no seat may make and the third leaned on a wrapper observation as live guidance. Two rows retired, one re-keyed onto the denied channel; 465 -> 463 lines, the rewritten line 120 -> 114 bytes. No other row, no other file. Claude-Session: https://claude.ai/code/session_01HZfg2AwVX191qCizp88gQr Co-authored-by: Claude <noreply@anthropic.com>
…atform-readings-wrapper-rows-history
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 |
Fixes #18310
What
.claude/skills/pm-dispatch/references/platform-readings.md— the PR-body footer block— still carried three rows that read the MCP
update_pull_requestwrapper as a liveedit channel. PR #18276 put
mcp__github__update_pull_requestinto lock 1'spermissions.deny, and PR #18307 re-keyed the sibling rows of the same block onto thatfact. These three were left byte-identical because they sat outside that card's row
surface.
Measured on this branch's base
6ef33128:grep -c mcp__github__update_pull_request .claude/settings.json= 1 (line 55, insidepermissions.deny). Re-read on this PR's head: same, 1.One file, three rows, nothing else. 465 to 463 lines; the one rewritten line 120 to 114
bytes; no line over the 120-byte cap.
The three rows
6ef33128)Why :347 and :348 are retired, not re-keyed
PR #18307 set the test this card inherits: re-key wherever a row still carries a measured
fact with a live dereference; retire otherwise. Neither of these two has one.
in the file dereferences :347 itself. Its job was to stop :344 being read as a law of the
wrapper; :343 「署名页脚的写侧变异按通道与输入双重定域,⛔ 不是一条定律」 and :350
「⛔ 无受控对照 ⇒ 是拟合不是定论」 already say that unconditionally, and :344 is already
marked 「该通道锁 1 已拒,读作历史」.
channel contrast that :343 and :354 (「改侧还随通道变」) rest on — and that contrast
survives in the pair :344 (MCP wrapper deletes) against :345 (bare REST
PATCH /pullsappends a bare footer and keeps the existing one, 58 bytes). That pair is exactly why
PR docs(pm): platform-readings retires the rows that still describe MCP update_pull_request as a live channel #18307 kept :344 as history rather than deleting it.
Both rows describe a call the harness refuses, so neither can be acted on. Measured, not
assumed:
git grepfor 「同一 MCP 包装器上有反例」, 「不为无页脚正文合成页脚」,「两条页脚均逐字节存活」 and 「唯一不合此判据的观察」 across the tree outside
node_modulesfinds one file — this one. No gate, script or sibling file reads these row texts.
Why :354 is re-keyed and where 「写后必回读」 lives
:354 is the one of the three that still qualifies a live row: :351 「调用不带
body参数则页脚状态不动」 is a criterion about live channels, and :354 was its sole recorded
non-conformance. With the wrapper denied, that non-conformance sits on a channel nobody
may use — so the honest re-key is stronger than the row it replaces: the criterion now has
no counterexample on live channels, and the worst-case prescription survives verbatim.
The card required 「写后必回读」 to keep a home. It now has three, all in the same block:
the re-keyed :352 carries it as 「写后仍必回读」; :342 「⇒ PR 正文页脚不带前置横线,且写后回读正文
—— 那是唯一检测手段」 and :354 「⛔ 不由任一条推其余,写后必回读」 already carried it before this
PR and are untouched.
Scope — the ceiling map was deliberately NOT touched
The card allowed
scripts/pm/check-skill-line-ratchet.mjsinto the surface only if itsceiling were lowered to the landed count in the same PR. It is not touched, and the file
lands at 463 under the standing ceiling 466 (headroom 3). Declared reasoning:
accounting — the
ruledRaisesrecord the standing one-file exception requires is owedon a raise, and none is taken here.
16 — cost this p3 row-retirement card did not buy, on a shared container.
466 to 465 lines and left the ceiling where it was.
The paydown remains available to the next readings increment on this file, which touches
that map anyway.
Reverse verification
The rows, before and after — quoted in the table above with byte counts per line
(105, 109, 120 before; 0, 0, 114 after). Every surviving line of the block is at or under
the 120-byte cap; the widest is 120 (:349, untouched).
grep -n 'MCP'inside the PR-body footer block6ef33128: three hits — :344 (already history), :347, :348.「MCP
update_pull_request包装器删掉 PR 正文的页脚块;该通道锁 1 已拒,读作历史。」Whole-file
grep -c 'MCP': 33 to 31.Ratchet on the head —
pnpm check:pm-skill-ratchet, exit 0, its own verdict line:check-skill-line-ratchet: .claude/skills/pm-dispatch/references/platform-readings.md is 463 lines (ceiling 466; headroom 3).The table-row pin reads 0/0 and the cross-file-movearithmetic re-derives green (+11 against a net source decrease of 20).
Ablation, with hash proofs. Said plainly, as the PR #18280 dev did: no gate reads
these row texts, so the ablation cannot show a red. What the legs prove is that the
mutation reached disk and that the ratchet's reading moves with the tree.
git hash-object97569c5ae21fb3687e31f2840fde968ad50ce299HEADblob, matchgit restore --source=6ef33128)e863ac595d63f17edc49b183679d3f791ef0fcef6ef33128blob, matchgit checkout HEAD --)97569c5ae21fb3687e31f2840fde968ad50ce299HEADblob, matchAnchor observation, taken against the text the mutation was aimed at rather than a bare
--stat: the two retired row texts count 0 on the head tree and 1 each on the mutatedtree, and the re-keyed row's 「活通道无反例」 counts 1 and 0 — flipped by the mutation and
flipped back by the restore. The script carried a
trapon EXIT INT TERM restoringHEADat an absolute path, an empty-hash guard reading as FAILURE, and after the restoreboth
git diff HEADandgit status --porcelainprinted nothing.Gates
Derived by
node scripts/pm/dispatch-gates.mjs --commands .claude/skills/pm-dispatch/references/platform-readings.md— 16 commands, every one runon this head, each exit captured by redirect before any pipe. Reconciled with
node scripts/pm/dispatch-gates.mjs --ranwritten ascommand :: exit Nlines:dispatch-gates --ran: 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).node scripts/check-closing-keyword-parity.mjsnode scripts/check-closing-keyword-parity.mjs --self-testnode scripts/check-comment-mask-corpus.mjsnode scripts/pm/check-governed-queue-guard.mjs --self-testpnpm --filter @objectstack/lint run check:doc-formula-expressionsturbo run build --filter=@objectstack/formula --filter=@objectstack/lintunderscripts/pm/os-verify-lock.sh(VERDICT command-exit 0, 4 tasks, held 157s)pnpm check:agent-test-spellingpnpm check:doc-authoringpnpm check:driver-memory-censuspnpm check:nul-bytespnpm check:pm-governed-mergespnpm check:pm-skill-id-lintpnpm check:pm-skill-ratchetpnpm check:refd-timer-probepnpm check:required-contextspnpm check:skill-frame-syncpnpm check:watch-hint-literalorigin/mainmoved under the branch while the battery ran (PR #18311 landed as10a7b9fa, touchingscripts/pm/check-half-states.mjsand no file of this diff). It wasmerged in, never rebased, and the whole 16-command union above plus the reconciliation
were re-run on the merge commit. A control-character self-scan
(
grep -naPover the edited file) found nothing beyond the gate.Why this shape — the four axes, applied to the one design choice per row (retire vs. re-key as denied history)
file itself (
git grep, whole tree outsidenode_modules). The scenario the two retiredrows served — choosing how to write a PR body through the MCP wrapper — cannot occur: the
tool is in lock 1's deny list. The scenario :354 serves — a single-field PR update on a
live channel — does occur, which is why that row is re-keyed rather than retired.
channel that does not exist is a second dialect of the same operation, kept alive in
writing; the live cells (bare REST
PATCH /pulls, and the create side where bothchannels agree) carry the whole prescription on their own.
包装器上有反例:两条页脚均逐字节存活」 describes a safe-looking practice on a tool the
runtime refuses; a seat that reads it goes looking for the wrapper and must then read its
absence, per :222 of this same file, as the deny working. Removing the row removes the
trap instead of asking every future reader to tolerate it. The same axis kept :354: the
criterion above it is live, and a criterion whose sole exception is silently dropped reads
as better-established than it is — the re-key states the exception's status instead.
rows go with the deny that made them inert, and no compatibility note is left behind for a
channel nobody may call. The re-keyed row is not a transition note; it is the qualifier a
live row still needs.
The four axes agree here, so there is no trade-off to hand up.
Acceptance notes
that hung off it are the three this card touched, and the one that remains (:352) names
its observation rather than its mechanism. PR docs(pm): platform-readings retires the rows that still describe MCP update_pull_request as a live channel #18307 kept it for those dereferences.
Noted, not filed — it is a compression opportunity, not a defect, a contract violation or
an authoring trap, and the skills seat's next readings increment on this file is the
carrier that would take it.
same carrier; see the scope section above for why it is not taken here.
维护者速读(草稿)
改了什么 —— 只动 PM 读数登记册一个文件里的三行:两行删除、一行原地改写。465 行降到 463 行,
改写的那行 120 字节降到 114 字节。没有第二个文件,没有其它行,不影响任何代码、接口或产物。
为什么改 —— 这三行描述的是一个 harness 已经在锁 1 里禁用的 GitHub 写工具的行为。登记册是每个
席位开工前都要读的作业手册;把一条「已经不能用的通道」的用法留在手册里,读到的人会照着去找那个工具,
浪费一轮并可能误判是权限出了问题。上一张卡(PR #18307)已经把同一段里的兄弟行按同样口径处理过,
这三行当时在那张卡的范围之外,这次补齐。第三行里仍然成立的那条规矩「写后必回读」被完整保留,并且在
同一段内还有两处独立出处。
风险与代价(含回滚) —— 风险很低:纯文档行,无代码路径,16 个派生门禁全绿,行数棘轮绿。唯一的
信息损失是两条关于已禁用通道的历史观测——它们仍留在 git 历史里,需要时可按本 PR 号取回。回滚就是
revert 这一个提交,不牵动任何其它文件。
席位意见 ——
你要做的 —— 这是受管面(
.claude/**),按 references 层归技能席位评审后在席位内落地;维护者这边不需要动作,除非你对「两行删除、一行改写」这个取舍有不同意见。
Generated by Claude Code