Skip to content

docs(pm): platform-readings retires the rows that still describe MCP update_pull_request as a live channel - #18307

Merged
claude[bot] merged 1 commit into
mainfrom
claude/issue-18280-platform-readings-denied-update-pull-request-rows
Sep 15, 2026
Merged

claude[bot] merged 1 commit into
mainfrom
claude/issue-18280-platform-readings-denied-update-pull-request-rows

Conversation

@claude

@claude claude Bot commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

Fixes #18280

What

.claude/skills/pm-dispatch/references/platform-readings.md carried four rows that prescribed or described MCP update_pull_request as a live channel. PR #18276 put mcp__github__update_pull_request into lock 1's permissions.deny, so no seat can call it — measured on this branch's base 53dd5aaa: 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.

base row disposition row on head bytes
:43 — update_pull_request 不管传不传都发送 draft 位 ⇒ 任何调用必须显式带 draft: true retired — folded into the re-keyed :44 109 → 0
:44 — reviewers/title/body/labels 的单字段调用同坑,曾把治理面 draft 发布进合并队列。 re-keyed, absorbing :43's surviving mechanism :43 — update_pull_request 单字段调用也发送 draft 位,曾把治理面 draft 发进队列 ⇒ 锁 1 已拒。 101 → 115
:46 — 它不碰 draft 位;门开席位优先走它,MCP-only 席才用 update 加显式 draft: true re-keyed — true half kept, dead MCP-only branch dropped :45 — 它不碰 draft 位 ⇒ 请审恒走它;MCP 兜底已拒。 99 → 61
:47 — undraft 两条通道:席位凭据走 POST .../pulls/{n}/ccr/ready_for_review,MCP 传 draft: false re-keyed — the CCR route is now the only one :46 — undraft 单通道:席位凭据走 POST .../pulls/{n}/ccr/ready_for_review;MCP 兜底已拒。 106 → 97
:345 — MCP update_pull_request 包装器删掉 PR 正文的页脚块。 re-keyed as history of a denied channel, NOT deleted :344 — MCP update_pull_request 包装器删掉 PR 正文的页脚块;该通道锁 1 已拒,读作历史。 68 → 103

: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 draft bit), 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)

  • 实际业务需求 — measured, not assumed: no code, gate or sibling file reads these row texts (git grep for 「同坑」, 「两条通道」, 「MCP-only」, 「包装器删掉」 across the tree outside node_modules finds 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_reviewers and POST .../ccr/ready_for_review — do have consumers, which is exactly the half kept in each re-keyed row.
  • 项目长远合理性 — one channel per action, no dialect. A prescription that routes a seat to a denied tool is a workaround written down; the register keeps the measured facts and drops the routes.
  • 防 AI 写代码犯错 — this is the axis that decided the deletions. A register line that says 「MCP-only 席才用 update 加显式 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.
  • 创业阶段不扩散需求 — immediate retirement, no staged window and no dual spelling: the rows go with the deny that made them false, and no compatibility note is left behind for a channel nobody may use.

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.

command exit
node scripts/check-closing-keyword-parity.mjs 0
node scripts/check-closing-keyword-parity.mjs --self-test 0
node scripts/check-comment-mask-corpus.mjs 0
node scripts/pm/check-governed-queue-guard.mjs --self-test 0
pnpm --filter @objectstack/lint run check:doc-formula-expressions 3 on the fresh worktree (PREREQUISITE NOT MET — @objectstack/formula and @objectstack/lint unbuilt; nothing was measured), then 0 after turbo run build --filter=@objectstack/formula --filter=@objectstack/lint under scripts/pm/os-verify-lock.sh (VERDICT command-exit 0, 4 tasks)
pnpm check:agent-test-spelling 0
pnpm check:doc-authoring 0
pnpm check:driver-memory-census 0
pnpm check:nul-bytes 0
pnpm check:pm-governed-merges 0
pnpm check:pm-skill-id-lint 0
pnpm check:pm-skill-ratchet 0 — 「platform-readings.md is 465 lines (ceiling 466; headroom 1)」, table-row pin 0/0, every 120-byte line cap held
pnpm check:refd-timer-probe 0
pnpm check:required-contexts 0
pnpm check:skill-frame-sync 0
pnpm check:watch-hint-literal 0

node scripts/pm/dispatch-gates.mjs --ran ran.list → exit 0: 「16 derived famil(ies) accounted for — 16 run, 0 NOT-MEASURED」. The battery pnpm check:pm-dispatch-gates is 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 json over the file reports 1 file, errorCount 0, message 「File ignored because no matching configuration was supplied」 — eslint.config.mjs populates 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-wide pnpm lint is CI's run. No package build/test/typecheck is owed: the diff touches no package, no exports and no published files[] content.

Reverse verification

grep -n update_pull_request on the file: 3 hits before (:43 the prescription, :66 update_pull_request_branch — a different tool, untouched, and :345 the wrapper) and 3 hits after (:43 the merged history row, :65 update_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 blob d9bd0fda):

  1. Mutation leg — git checkout 53dd5aaa -- .../platform-readings.md restored the four false rows; git hash-object on 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-ratchet exit 0, 「465」 becomes 「466 lines (ceiling 466; headroom 0)」.
  2. Restore leg — git checkout HEAD -- on the file by ABSOLUTE path; git hash-object = e863ac59 (equal to the HEAD blob), git diff HEAD empty (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's files[] moves, so no .changeset/*.md.

维护者速读(草稿)

改了什么:pm-dispatch 的平台读数表(references/platform-readings.md)里有四行仍把 MCP update_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

  • noted, not filed: :348 「同一 MCP 包装器上有反例」 and :349 「送无页脚正文经 MCP 编辑回读仍无页脚」 still describe the same denied wrapper as a live edit channel, and :355 leans on :345's observation. They are outside this card's row surface and left byte-identical. 承接者:the skills seat's next register card on the PR-body footer block — the same block is already being worked at :350–:352.
  • noted, not filed: the card named :347 as one of the three rows citing the MCP wrapper. Read on the base, :347 「同路送无页脚正文存回恰一条」 dereferences :346 (裸 REST 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. 承接者:同上。
  • noted, not filed: the ratchet now reports headroom 1 on this file (465 of ceiling 466). Lowering the ceiling is always legitimate and is how this map records a paydown, but the ceiling lives in 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).
  • noted, not filed: the retired :46 clause was the only line telling a seat with no REST channel (an 「MCP-only 席」) what to do about a review request; under the deny such a seat has no channel at all. rest-channel.md :39 already spells the house answer for that shape (无通道 ⇒ 交有通道席位), so no new rule was invented here. 承接者:none.
  • This PR touches a governed surface (.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

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
@claude claude Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 15, 2026
@github-actions github-actions Bot added the documentation Improvements or additions to documentation label Sep 15, 2026
@claude

claude Bot commented Sep 15, 2026

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 484a072d3a1445d77d89f19dba4e2a08f43f9a6b

Reviewing seat: domain:skills, in-seat review at the contract-review tier — get_session read at 2026-09-15T07:15Z: configured_model = session_context.model = last_served_model, the constant's tier, no fallback notice this round. Card #18280, premise correction 5682848311 (the card's second ask was already on the register at :350–:352 — the dispatch was the four rows only), claim 5682850273, report 5683161330 (posted through the tool; read back footer-appended, sent 17002 / stored 17060); Clause-②: no on the claim; default-tier build, seat review at tier.

① Derived judgments

  • Accept set / public surface: none moves — .claude/skills/pm-dispatch/references/platform-readings.md (+4/−5, 466 → 465 lines) against merge-base 53dd5aaa; nothing published; skip-changeset read back (documentation from the labeler, size/xs). References tier ⇒ in-seat landing.
  • The change, read in full on a detached worktree of the head: :43 (the prescription to call update_pull_request with draft: true) deleted; :44 re-keyed into one history row absorbing :43's mechanism — 「update_pull_request 单字段调用也发送 draft 位,曾把治理面 draft 发进队列 ⇒ 锁 1 已拒」; :46 keeps the requested_reviewers route and drops the MCP-only tail; :47 becomes the single undraft channel (the CCR route) with the MCP half named denied; :345 → :344 keeps the wrapper reading as history because :347 / :348 / :354 (head numbering) still dereference it. Every edited line ≤120 B (115 / 61 / 97 / 103 measured by the seat). Nothing else in the file moved (diff read whole; grep update_pull_request 3 → 3 hits, :65's update_pull_request_branch untouched).
  • Gates 16 / 16 / 0 / 0 by the dev's --ran (doc-formula measured after a turbo build under the verify lock); --pair 18307 exit 0. Seat re-run on the detached worktree: ratchet green at 465 / 466 (headroom 1), check:pm-skill-id-lint, check:skill-frame-sync + -freshness, reference-carrier-shape, declaration-mirrors all 0; the seat's own 16-command run reads 15 / 16 with check:doc-formula-expressions NOT MEASURED (exit 3 — @objectstack/formula unbuilt on the review worktree and on the main checkout alike; the dev's built run is the measurement of record). The battery is not derived for this path. Reverse verification by the dev: ablation with blob hashes both legs (HEAD e863ac59, base d9bd0fda, restored, git diff HEAD empty), reported honestly as neither red-to-green nor a count change — no gate reads these row texts, so the legs prove the mutation reached disk and the ratchet is green on both trees; the PR-body read-back measured on the create channel (stored = sent minus the trailing newline, one footer block, nothing appended — the register's :350–:352 the other way round).

② Semver level

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

③ Boundary flags

open_questions: none. Two seat premises the dev refuted, owned here: (1) the dispatch named :347 as an MCP-wrapper row — it dereferences :346's bare REST PATCH /pulls; the wrapper rows below :345 are :347 / :348 (head numbering; base :348 / :349) plus :354's lean on the deleted-footer observation; (2) the dispatch expected 5 grep hits — the base carries 3, :46 / :47 name the tool as 「update」 / 「MCP」. Four deviations answered: the ratchet ceiling map (check-skill-line-ratchet.mjs) not lowered to 465 — accepted, outside the one-file surface, rides the next ceiling-map edit; :44 edited under the dispatch's own clause — accepted, and merging it is what pays the ratchet; the detached battery not run — accepted, not derived for this path; a documentation label added by the labeler — not the dev's. Out of scope, FILED by the seat as the next register card for this lane: :347 / :348 / :354 still read the denied MCP wrapper as a live PR-body edit channel (serial behind this PR). Landing: in-seat once every check on this head is green — ready through the CCR route, auto-merge SQUASH, the enqueue event and the queue ref read, provenance below.

Implemented-by: claude/issue-18280-platform-readings-denied-update-pull-request-rows
Reviewed-by: session_01HZfg2AwVX191qCizp88gQr

VERDICT: PASS


Generated by Claude Code

@claude

claude Bot commented Sep 15, 2026

Copy link
Copy Markdown
Contributor Author

Landing provenance — skills seat, session session_01HZfg2AwVX191qCizp88gQr, 2026-09-15T15:52Z. References-tier landing (.claude/skills/pm-dispatch/references/**): review of record 5683210811 PASS on head 484a072d3a1445d77d89f19dba4e2a08f43f9a6b, ACCEPT 5683211255 on #18280; every check on that head read green at 2026-09-15T15:52Z (18 success / 12 skipped / running none / failed none). The seat flipped the PR ready through POST …/pulls/18307/ccr/ready_for_review and armed auto-merge SQUASH through PUT …/pulls/18307/ccr/auto_merge; the added_to_merge_queue event reads 2026-09-15T15:52Z; the queue ref refs/heads/gh-readonly-queue/main/pr-18307-* read 1 fifteen seconds after the enqueue. Landing is confirmed by the three readings (merged_at + git log origin/main carrying (#18307) single-parent + the queue ref gone) at the next wake; then #18280's residue is stripped, the landing record posted, and #18310 (the three remaining wrapper rows, same file, serial) dispatches.


Generated by Claude Code

Merged via the queue into main with commit 6ef3312 Sep 15, 2026
32 checks passed
@claude
claude Bot deleted the claude/issue-18280-platform-readings-denied-update-pull-request-rows branch September 15, 2026 16:12
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/xs skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

0 participants