docs(pm,agents,settings): write-identity locks 1–4 — deny MCP content writes, REST-only dev writes with api_writes, batch default 2, user-account roles - #18072
Conversation
…rite lands through the REST proxy (lock 1) Maintainer, skills seat chat, 2026-09-13T16:14Z, verbatim and untranslated: 「机制层的五道锁 现在就派发处理」 Lock 1 of the write-identity locks: `.claude/settings.json` gains `permissions.deny` naming the fourteen MCP GitHub tools that create content or history (issue_write, create_pull_request, add_issue_comment, add_comment_to_pending_review, add_reply_to_pull_request_comment, pull_request_review_write, push_files, create_or_update_file, delete_file, create_branch, sub_issue_write, merge_pull_request, create_repository, fork_repository). Content written through those tools is authored by the claude.ai account's linked GitHub USER and vanishes with a user suspension; the REST proxy authors as `claude[bot]`. State tools stay allowed (update_pull_request, since REST cannot un-draft; enable/disable_pr_auto_merge; actions_run_trigger; resolve/unresolve review thread; subscribe/unsubscribe) and so does every read tool. The `allow` list and the hooks are untouched; `node -e 'JSON.parse(...)'` passes. Docs reading (code.claude.com/docs/en/permissions and /settings): rules are evaluated deny, then ask, then allow; a deny at any scope blocks an allow at any other scope; `mcp__server__tool` is the per-tool rule spelling; the repository's shared `.claude/settings.json` is read in cloud sessions, and deny rules apply without workspace trust. Claude-Session: https://claude.ai/code/session_01DAcomhvR9kKizeYgg89Vo8 Co-authored-by: Claude <noreply@anthropic.com>
… four-write budget, and reports `api_writes` (lock 2)
Maintainer, skills seat chat, 2026-09-13T16:14Z, verbatim and untranslated:
「机制层的五道锁 现在就派发处理」
Triage ruling ③ (the reason the dedupe-channel lines were stale):
「立卡者不查重,只在卡面附 3–5 个查重词」
Lock 2 of the write-identity locks, in `.claude/agents/os-dev.md`, net 0
lines (403/403), every added prose line within the 120-byte cap:
- The eight dedupe-channel lines under rule 3 (probe-then-choose, the
403 ⇒ MCP `search_issues` fallback, the no-wide-scan line, the
payload-tier description and the "MCP is for writes + that one dedupe"
line) are replaced by seven lines: every GitHub write goes through the
REST proxy (`curl` with the environment `GITHUB_TOKEN`), authored by
the App's `claude[bot]`; the budget is `git push` + one `POST /pulls`
(draft) + `POST /issues/{n}/labels` + the `os-dev-report` comment; no
MCP GitHub write tool (user-account authorship, hidden on suspension);
no board enumeration and no wide search; card and thread reads go
through the payload tier or a single-card REST read; three-class
findings go into the report with dedupe words for the seat to file;
zero writes outside the budget (no `PATCH` of the PR body); the
rest-channel table pointer is kept; the report records `api_writes`
(count + endpoints) beside `mcp_calls`.
- The control-word rule that lived among the dedupe lines is kept as a
premise-check rule under rule 6 (folded into the line it belongs to).
- Resource rule 6's tail now routes late verification results into the
report instead of a body PATCH, so it agrees with the budget.
- The label-write fallback that instructed an MCP `issue_write` on a
refused REST label POST now says: stop and report `blocked` naming the
endpoint and status; never switch to an MCP write; still read back.
- The report template gains `"api_writes"` after `"mcp_calls"`, and the
`out_of_scope_findings` example no longer shows a filed card number,
since under the budget the dev files none.
Claude-Session: https://claude.ai/code/session_01DAcomhvR9kKizeYgg89Vo8
Co-authored-by: Claude <noreply@anthropic.com>
Maintainer to the services seat, 2026-09-13, verbatim and untranslated: 「当前任务处理完,后续并发降到2」 Lock 3 of the write-identity locks: the `batch:<n>` row of the `/pm-dispatch` argument table reads 「默认 `2`」 instead of 「默认 `3`」; the maintainer ceiling `5` is unchanged, the seat-post protocol line is unchanged. `core-rules.md` :11 states the parallelism rule without a default, so no mirrored line moves. 812/812, one table row edited in place (widest row 342 B untouched). Claude-Session: https://claude.ai/code/session_01DAcomhvR9kKizeYgg89Vo8 Co-authored-by: Claude <noreply@anthropic.com>
…tten as `claude[bot]` and identified by session ID (lock 4) Maintainer, skills seat chat, 2026-09-13T16:14Z, verbatim and untranslated: 「机制层的五道锁 现在就派发处理」 Lock 4 of the write-identity locks. `SKILL.md` 〈全体座位的不变量〉 gains two lines, paid inside the same section (812/812, every added line within the 120-byte cap, frame block :733–:754 md5 3327d02c56f8a0eca88569dad2270f32 unchanged): - 「用户账号仅三用:assignee、授权批准、维护者亲手;⛔ 席位与 dev 永不以用户账号写内容。」 - 「内容恒经 REST 代理(`claude[bot]`);批准账号永不跑席位、不作席位 claude.ai 的关联用户。」 Paid by density, deletions named: the state line absorbs 「循环必须能从 全新会话恢复」 (one line freed); the four Chinese channels are listed in the English-only line as short names, dropping the two parentheticals 「(受管 PR 与决策卡)」 and 「(评论与四棱块)」, both stated in the 复核 and 升级与决策 sections (one line freed). The identity half — the body's session ID, never the author field — lands in place on the shared- identity line of 〈认领〉 (「身份只认正文 session ID,⛔ 不认作者字段」). `core-rules.md` 〈全体座位的不变量〉 mirrors the rule in one line (「用户账号仅三用:assignee、授权批准、维护者亲手;写恒经 REST 代理;批准账号 永不跑席位」), 151/151, paid by folding the three language lines into two with no rule dropped (「一律」 kept, 「每个域恰好一个 PM」 → 「每域恰一 PM」). Claude-Session: https://claude.ai/code/session_01DAcomhvR9kKizeYgg89Vo8 Co-authored-by: Claude <noreply@anthropic.com>
…readings (+2 under the standing exception) Maintainer, skills seat chat, 2026-09-13T16:14Z, verbatim and untranslated: 「机制层的五道锁 现在就派发处理」 — the card orders the deny-list reading recorded in `references/platform-readings.md` when a row is owed. The standing one-file exception, pm-dispatch SKILL.md, verbatim and untranslated: 「唯一例外:`platform-readings.md` 增量抬上限到落地行数,免决策卡, 记 `ruledRaises` 引常设裁决。条件:席位验收评论逐条核实、去重计数(候选/落地/ 已有/拒收)、一事一行、不计重排」 Two readings under 〈读数陷阱〉, beside the MCP invalid-session pair, both from the Claude Code settings and permissions documentation and marked 文档载明未实测 in the file's own convention: the repository's shared `.claude/settings.json` `permissions.deny` is read in a cloud session and a deny at any scope is evaluated before every allow; a denied MCP tool is removed from the tool roster entirely, so a tool's absence reads as the deny working, never as a dead MCP server. Candidates 2 / landed 2 / already present 0 / refused 0; 451 → 453, the THIRTEENTH `ruledRaises` record (delta 2) citing the exception; `check:pm-skill-ratchet` green (the cross-file move verdict is unchanged at +11 against a net source decrease of 20). Claude-Session: https://claude.ai/code/session_01DAcomhvR9kKizeYgg89Vo8 Co-authored-by: Claude <noreply@anthropic.com>
…ite-identity-locks
…file's 120-byte line convention The `out_of_scope_findings` example rewritten under lock 2 ran 141 bytes against a file whose every line sat at or under 120 on the base; the ratchet exempts fenced lines, so the gate stayed green while the file's own convention regressed. Shortened to 107 bytes, same line count (403/403), same meaning: the dev lists three-class findings to file with dedupe words attached, and the seat files them. Claude-Session: https://claude.ai/code/session_01DAcomhvR9kKizeYgg89Vo8 Co-authored-by: Claude <noreply@anthropic.com>
Contract reviewHead: ① derived judgments — locks 1–4 of the write-identity plan, one commit each, every file at its ceiling and net 0:
Seat measurements on the head tree ( ② semver: ③ boundary flags: Implemented-by: Verdict: PASS — the four locks land as rules a session cannot step around (the deny list) and rules a seat can be held to (the budget, the default, the account roles); awaiting an authorized approval. 维护者速读(终稿)改了什么:四把锁。① 仓库 Generated by Claude Code |
|
Ruling C landing step — skills seat (session Generated by Claude Code |
…ite-identity-locks # Conflicts: # .claude/agents/os-dev.md
Contract reviewHead: ① derived judgments — the merge added no content of its own:
Seat measurements: ② semver: unchanged — nothing published; ③ boundary flags: none new. Landing consequence unchanged: the MCP GitHub content-writing tools are denied in every session of this repository, Implemented-by: Verdict: PASS — the reviewed diff, now on a head that merges cleanly with Generated by Claude Code |
|
Ruling C re-landing step — skills seat (session Generated by Claude Code |
Fixes #18068
Dev session
session_01DAcomhvR9kKizeYgg89Vo8on branchclaude/issue-18068-write-identity-locks(worktreeobjectstack-issue-18068), offorigin/main6d64785, merged e248c4d before opening (no incoming commit touched these files). One commit per lock; each quotes its ruling.Rulings (verbatim, untranslated)
platform-readings.md增量抬上限到落地行数,免决策卡,记ruledRaises引常设裁决。条件:席位验收评论逐条核实、去重计数(候选/落地/已有/拒收)、一事一行、不计重排」 — the +2 on platform-readings.What landed
.claude/settings.json:permissions.denywith the 14 content-writingmcp__github__*tools the card lists;allowand hooks untouched;JSON.parsepasses. Docs reading (code.claude.com/docs/en/permissions and /settings): rules evaluate deny, then ask, then allow; a deny at any scope beats an allow at any scope;mcp__server__toolis the per-tool spelling; the shared.claude/settings.jsonis read in cloud sessions and deny needs no workspace trust — lock 1 is ENFORCED, and a denied tool is removed from the roster. Recorded as two 文档载明未实测 lines inreferences/platform-readings.md(references tier, declared): 451 → 453, THIRTEENTHruledRaisesrecord; candidates 2 / landed 2 / already present 0 / refused 0, one matter per line..claude/agents/os-dev.md403/403: :51–:58 replaced by the REST-proxy write rule (curl+ environmentGITHUB_TOKEN, authoredclaude[bot]), the four-write budget, ⛔ no MCP GitHub write tool and no board enumeration, payload-or-single-card reads, findings reported for the seat to file, zero writes outside the budget (no PR-bodyPATCH), the rest-channel pointer (kept), andapi_writes+mcp_callsin the report; the report template gains"api_writes". In place, net 0: the control-word rule now sits under rule 6; resource rule 6 routes late results to the report; the label-write fallback no longer prescribes an MCPissue_write; theout_of_scope_findingsexample no longer shows a dev-filed card number.3」 → 「默认2」; ceiling5unchanged; core-rules :11 states no default, so nothing mirrored.3327d02c56f8a0eca88569dad2270f32unchanged.Executable criterion, BASE 6d64785 → HEAD 7e1aeca
grep -c mcp__github__issue_write .claude/settings.json0 → 1 (insidedeny); SKILL.md 「默认2」 0 → 1 and 「默认3」 1 → 0; os-dev.mdapi_writes0 → 2 (rule + template),search_issues1 → 0; SKILL.md 「批准账号」 / 「永不跑席位」 0 → 1, core-rules 「批准账号」 0 → 1. Lit controls unchanged: 「每个方案必须沿四条固定评估轴分析」 1 → 1, 「一座位一车道双射」 1 → 1 in both files, os-dev.mdmcp_calls2 → 2.Gates
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackat 88e0610: 42 families, every one exit 0 in the foreground with the code captured before any pipe;--ran: 42 derived, 42 run, 0 NOT-MEASURED, 0 UNRUN.check:doc-formula-expressionsfirst answered exit 3 (PREREQUISITE NOT MET, lint/formula unbuilt) — built under the verify lock (187 s held) and re-run: exit 0. Rule ⑤ for the ratchet-script edit: its--self-test(insidecheck:pm-skill-ratchet),check:ratchet-remedy-authority,scripts/check-published-list-mirrors.mjsandscripts/check-skills-token-ratchet.mjsall exit 0. Re-run at 7e1aeca after the merge:pm-skill-ratchet,pm-skill-id-lint,skill-frame-sync,pm-governed-prose,nul-bytes,agent-model-declaredexit 0. Every added prose line ≤ 120 B (the one longer added line is inside the report's JSON fence, structurally exempt); SKILL.md over-120 baseline 23 → 23; control-character scan empty.skip-changeset: nothing published moves (.claude/**,scripts/pm/**only).Deviations, declared
PATCH; the rule says so explicitly and routes late gate results to the report comment. Allowing a body refresh would be one clause on that line, the seat's call.Acceptance notes
mcp__github__update_pull_request_branchwrites merge commits, andrequest_copilot_reviewwrites a review request, yet neither is on the card's list, so both stay allowed.维护者速读(草稿)
改了什么:四道机制锁。① 仓库的 Claude 设置里禁掉所有"以用户账号写内容"的 MCP GitHub 工具(建 issue、开 PR、评论、审查、推文件、合并等 14 个),状态类与只读工具照旧;② 开发 agent 对 GitHub 的写只走 REST 代理(署名
claude[bot]),预算固定四笔,报告新增api_writes供席位核对;③ 并发默认 3 → 2,天花板 5 不变;④ 写明用户账号只做三件事(assignee、授权批准、维护者亲手),批准账号永不跑席位,内容身份只认正文里的 session ID。为什么改:今天的封号事故证明,用 MCP 工具写的内容署在关联用户名下,用户一被停,内容整批消失;走 REST 代理的内容署在 App 名下不受影响。并发只是放大器,身份才是被封的对象。
风险与代价(含回滚):deny 名单在会话启动时读入,已开的会话不受影响;席位仍可用
update_pull_request等状态工具翻 ready、挂 auto-merge。回滚 = revert 本 PR 的任一 commit(每锁一个 commit,互不依赖)。platform-readings 上限 +2 走常设例外,不另开决策卡。席位意见:(留空,由席位定稿)
你要做的:一个动作 —— 在本 PR 上给出授权批准;受管面,席位按裁决 C 落地。
Generated by Claude Code