docs(pm): platform-readings retires the rows that still describe MCP update_pull_request as a live channel - #18307
Conversation
Lock 1 now denies `mcp__github__update_pull_request`, so the register's rows
that prescribed or described it as a live channel were false. Four rows, one
file, nothing else:
- :43 folded with :44 into one history row — the prescription ("every call
must carry `draft: true`") has no surviving truth under the deny; the
measured mechanism (single-field calls send the draft bit too) and the
incident it caused survive as one line ending 锁 1 已拒 (466 -> 465 lines).
- :46 (now :45) keeps the true half (the dedicated REST review-request route
never touches the draft bit) and drops the dead MCP-only-seat branch.
- :47 (now :46) keeps the CCR undraft route, which is now the only one.
- :345 (now :344) is re-keyed, not deleted: rows below it hang off its
antecedent, so it keeps the measured wrapper reading and is marked history
of a denied channel.
Every edited line is <= 120 bytes and the line count falls, so the ratchet is
paid by deletion: platform-readings 465/466.
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 |
Fixes #18280
What
.claude/skills/pm-dispatch/references/platform-readings.mdcarried four rows that prescribed or described MCPupdate_pull_requestas a live channel. PR #18276 putmcp__github__update_pull_requestinto lock 1'spermissions.deny, so no seat can call it — measured on this branch's base53dd5aaa:grep -c mcp__github__update_pull_request .claude/settings.json= 1. Those rows therefore pointed at a channel the harness refuses.One file, four rows, nothing else. 466 → 465 lines; every edited line ≤ 120 bytes.
update_pull_request不管传不传都发送draft位 ⇒ 任何调用必须显式带draft: true。update_pull_request单字段调用也发送draft位,曾把治理面 draft 发进队列 ⇒ 锁 1 已拒。draft: true。POST .../pulls/{n}/ccr/ready_for_review,MCP 传draft: false。POST .../pulls/{n}/ccr/ready_for_review;MCP 兜底已拒。update_pull_request包装器删掉 PR 正文的页脚块。update_pull_request包装器删掉 PR 正文的页脚块;该通道锁 1 已拒,读作历史。:44's status — asked by the card, answered here
:44 is a reading about the past (an incident), not a prescription, so it survives as history. But it survives only by absorbing :43: its subject 「同坑」 dereferenced :43's pit, so deleting :43 alone would have left :44 pointing at nothing. The merged row carries all three surviving facts in one line — the mechanism (any single-field call still sends the
draftbit), the incident it caused (a governed-surface draft went into the merge queue), and the current status (锁 1 已拒). That is what pays the ratchet: two rows about a denied tool become one.Why :345 is re-keyed and not deleted
Deleting it would break rows this card may not touch: :347 hangs off :346 (the bare REST route), but :348 「同一 MCP 包装器上有反例」 and :349 「送无页脚正文经 MCP 编辑回读仍无页脚」 dereference :345's wrapper, and :355 names :345's observation as 「唯一不合此判据的观察」. The row keeps its measured content and is marked as a fact about a denied channel.
Why this shape (four axes; the one design choice per row: delete vs. re-key as denied history)
git grepfor 「同坑」, 「两条通道」, 「MCP-only」, 「包装器删掉」 across the tree outsidenode_modulesfinds no consumer but the file itself; the one other 「同坑」 hit, :316, is a different reading). The live routes the rows named —POST /pulls/{n}/requested_reviewersandPOST .../ccr/ready_for_review— do have consumers, which is exactly the half kept in each re-keyed row.draft: true」 is a declaration the runtime does not honour: the seat that follows it finds the tool absent from its roster and, per :222 of this same file, must read that absence as the deny working. Removing the line removes the trap rather than asking the reader to tolerate it.The same axes said re-key, not delete, wherever a row still carries a measured fact with a live dereference (:44's incident, :46's 「它不碰 draft 位」, :47's CCR route, :345's antecedent for three rows below it).
Gates
Derived by
node scripts/pm/dispatch-gates.mjs --commands .claude/skills/pm-dispatch/references/platform-readings.md— 16 commands, every one run, each exit captured by redirect before any pipe. The derivation is byte-identical when re-derived from the committed tree.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-expressions@objectstack/formulaand@objectstack/lintunbuilt; nothing was measured), then 0 afterturbo run build --filter=@objectstack/formula --filter=@objectstack/lintunderscripts/pm/os-verify-lock.sh(VERDICT command-exit 0, 4 tasks)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-literalnode scripts/pm/dispatch-gates.mjs --ran ran.list→ exit 0: 「16 derived famil(ies) accounted for — 16 run, 0 NOT-MEASURED」. The batterypnpm check:pm-dispatch-gatesis NOT derived for this path and was not run.Beyond the derivation: the control-byte sweep
grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]'over the edited file matches nothing (exit 1). ESLint is measured, not assumed:pnpm exec eslint --format jsonover the file reports 1 file,errorCount0, message 「File ignored because no matching configuration was supplied」 —eslint.config.mjspopulates only**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}, so Markdown is outside ESLint's population entirely and this diff cannot move the verdict on any untouched file (no type-aware rule can reach a file outside the population). Repo-widepnpm lintis CI's run. No packagebuild/test/typecheckis owed: the diff touches no package, noexportsand no publishedfiles[]content.Reverse verification
grep -n update_pull_requeston the file: 3 hits before (:43 the prescription, :66update_pull_request_branch— a different tool, untouched, and :345 the wrapper) and 3 hits after (:43 the merged history row, :65update_pull_request_branch, :344 the re-keyed wrapper row). The card's expected count of 5 did not hold on the base: :46 and :47 name the tool as 「update」 and 「MCP」 without the identifier, so they never matched that grep — recorded as a premise check, not a deviation.Ablation, both legs hash-proved on the committed tree (HEAD blob
e863ac59, base blobd9bd0fda):git checkout 53dd5aaa -- .../platform-readings.mdrestored the four false rows;git hash-objecton disk =d9bd0fda(equal to the base blob, so the mutation really landed), anchor greps 1/1/1 for the three retired texts and 0 for the new merged row, file at 466 lines. Reading:pnpm check:pm-skill-ratchetexit 0, 「465」 becomes 「466 lines (ceiling 466; headroom 0)」.git checkout HEAD --on the file by ABSOLUTE path;git hash-object=e863ac59(equal to the HEAD blob),git diff HEADempty (0 lines), file back at 465. Re-reading: ratchet exit 0, headroom 1.The script carried an EXIT/INT/TERM trap whose handler restores the file from HEAD by absolute path, and the restore is proved by the hash comparison plus the empty
git diff HEAD, not by an exit code.The observed direction is neither red-to-green nor a diagnostic count change, and is reported as measured: no gate reads these row texts, so no ablation of them can turn one red by construction. What the two legs do establish is that the mutation reached disk and that the only gate that reads the file at all — the line ratchet — is green on both trees, at 466/466 and at 465/466. Recording that as a green ablation would be a fit, not a finding.
Changeset
skip-changeset: the whole diff is under.claude/**, the fast lane — nothing under any released package'sfiles[]moves, so no.changeset/*.md.维护者速读(草稿)
改了什么:pm-dispatch 的平台读数表(
references/platform-readings.md)里有四行仍把 MCPupdate_pull_request当成能用的通道。本 PR 把其中的处方删掉、把仍成立的实测事实改写成「这个通道已被拒」的历史记录:两行合并成一行(:43+:44),另外三行原地改写(:46、:47、:345)。文件 466 → 465 行,只动这一个文件。为什么改:PR #18276 已把该 MCP 工具加进
.claude/settings.json的 deny 清单,席位根本调不到它。留着这些行等于让 AI 席位照着一条运行时必拒的路走——而同一张表的另一行又告诉它「工具从工具表消失就是 deny 生效」,两边打架。声明与强制对齐,一处说清楚就够。风险与代价(含回滚):纯文档面,无代码、无发布内容、无 changeset;风险是把某条仍有人引用的读数删过头,所以 :345 只改写不删除(下面三行还在引用它)。回滚 = revert 本 PR 的唯一提交。
席位意见:
你要做的:这是
references/层(受管面的 references 档),按分层规则由 skills 席位做 contract-tier 审查后落地;维护者无需操作。Acceptance notes
PATCH /pulls), not the MCP wrapper — the third MCP row in that block is :349. Nothing edited either way; recorded so the follow-up card names the right rows. 承接者:同上。scripts/pm/check-skill-line-ratchet.mjs, outside this card's declared single-file surface, so it was NOT touched. 承接者:the skills seat on its next touch of the ceiling map (the declared cross-file move into this file re-prices green either way: +11 against a net source decrease of 20 today, +10 at a 465 ceiling).rest-channel.md:39 already spells the house answer for that shape (无通道 ⇒ 交有通道席位), so no new rule was invented here. 承接者:none..claude/**), all of it under.claude/skills/pm-dispatch/references/: left DRAFT, no reviewer requested, no ccr route touched, no auto-merge armed.Generated by Claude Code