docs(pm,agents): write identity follows the channel, not the account — REST-only content writes, the ACCEPT refuses MCP writes, a stale shared checkout re-seats (#18205) - #18216
Conversation
…— REST-only content writes, the ACCEPT refuses MCP writes, a stale shared checkout re-seats Equal-line edits under every ratchet (SKILL.md 812, os-dev.md 403, platform-readings 454, rest-channel 82, core-rules 151), every touched line at or under 120 bytes. - facts and invariants: content writes go only through the REST proxy, never an MCP content-write tool; `user.login` on a write names the channel's token (installation ⇒ `claude[bot]`, user-to-server ⇒ the bound user — per session, not the seat's to choose), never the actor; attribution is the session ID in the text carrier. Rewritten in os-dev.md :51/:53, SKILL.md :97–:98, core-rules :25, platform-readings :129, rest-channel :54. - dispatch order and acceptance: every dispatch order carries a `Writes:` line; an os-dev-report whose `mcp_calls` names a write tool (the settings deny list + `update_pull_request`) is refused, on both the seat side (SKILL.md 复核) and the dev side (os-dev.md :369). - propagation: at fire time the seat also reads the latest origin/main touch of the harness-loaded files (.claude/settings.json, .claude/agents/*.md, .claude/hooks/*) against the shared checkout's HEAD; a touch not in HEAD ⇒ close the shift and re-seat in a fresh session, never advance the shared checkout in place. The reading is scripts/pm/check-harness-current.mjs (git only, seat-side, not CI). - fleet: the new-repo registration checklist gains the write-identity locks port (deny + hooks). Density paid inside each file: four SKILL.md bullet pairs merged (终报要求/机器可核字段, mode:cloud/build-heavy, marker spellings/ missing-notification, 报告契约 authority/second-copy); os-dev.md :53 drops the board/wide-search clause already carried by :50 and :54. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DAcomhvR9kKizeYgg89Vo8
The list indent under 基本规则 3 costs three bytes the draft measurement left out; `带环境` becomes `带`, 122 → 116 bytes, same content. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DAcomhvR9kKizeYgg89Vo8
Contract reviewServed-tier: 4/4 In-seat record on the GOVERNED rules layer ( ① Derived judgments
② Semver levelNone. ③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
维护者速读改了什么:三件,全部等行、五个文件都顶着行数上限:① 章程里「署名恒 为什么改:锁 1 落地之后 objectui 仍出现经 MCP 建的 PR,原因是 harness 只在克隆那一刻读一次这些文件;dev 顺手测出本会话的共享检出里根本没有 风险与代价(含回滚):纯规则文本加一个 59 行的只读脚本,零 CI 接线;回滚即 revert。代价是每次开轮多一次 git 读数,以及可能的换会话。 席位意见:dev 为付行数把 core-rules :25 的「批准账号永不跑席位」并回 SKILL.md :97 保留,os-dev.md :53 的「不枚举板面、不宽词搜」由 :50/:54 承担,本席采信。dev 另报一个空子:deny 名单漏了 你要做的:os-zhuang 或 hotlong 一次 approve;之后由本席落地。是 / 否? Generated by Claude Code |
Fixes #18205
The maintainer's order (verbatim, ⛔ not translated)
「派发令硬性指定 REST 通道:建议改。 你应该修改skills吧?」 and 「不只是 objectui 仓库,其他第三方元数据app仓库怎么办」 — the maintainer, 2026-09-14, in the skills seat's chat (audit comment 5666103417 on the card). Lock 1 (PR #18072,
7ef05f9973) stays in force as the channel rule; this PR corrects its identity claims and does not weaken it. Landing is governed by ruling C (#17971): 「C. approve 后不管后续改动都由席位落地:」 — this PR is a DRAFT and stays one; the seat does the four-piece after ACCEPT and lands only after an authorized approval.What changed — equal-line under every ratchet, every touched line at or under 120 bytes
Line numbers are on this branch at
7103d0b09f; B = bytes of the line as stored. Readings taken 2026-09-14T16:27Z..claude/agents/os-dev.mdcurl带GITHUB_TOKEN);归属 = 文本里的 session ID,非user.login。.claude/agents/os-dev.mdclaude[bot],user-to-server ⇒ 用户。.claude/agents/os-dev.md"mcp_calls": "N — MCP GitHub calls with tool names; a write tool in the list = this report is refused",(the file spells the placeholder N inside angle brackets, as the template always has)SKILL.md.claude/{settings.json,agents/*.md,hooks/*}的最新触碰是否已在共享检出 HEAD。SKILL.mdscripts/pm/check-harness-current.mjs。SKILL.mdSKILL.mduser.login记令牌不记席位,归属 = 文本里的 session ID。SKILL.mdSKILL.mdSKILL.mdWrites:行:只走 REST 代理、写预算(端点清单)、mcp_calls计数,dev 两数都报。SKILL.mdmode:cloud只保留给 L/XL、活过 PM 会话的工作、浏览器/dogfood 验证;build 重的 M 卡逐卡判。SKILL.mdos-dev-report);⛔ 永不把没收到失败通知读作还在跑。SKILL.mdmcp_calls点名写工具(settings.jsondeny 清单 +update_pull_request)⇒ 拒收,⛔ 不带注放行。SKILL.md.claude/agents/os-dev.md终报消息节,⛔ 本文不抄第二份。references/core-rules.mdreferences/platform-readings.mdclaude[bot])或 user-to-server(用户),core 15,000/时。references/rest-channel.mdPUT .../pulls/{n}/merge;actor 记通道令牌:REST 按会话为claude[bot]或用户,MCP 恒用户。SKILL.mdandreferences/are.claude/skills/pm-dispatch/. The two os-dev.md rule lines carry their 3-space list indent inside the count.user.loginon a write names the channel's token — installation ⇒claude[bot], user-to-server ⇒ the bound user — per session, not the seat's to choose, never the actor; attribution is the session ID in the text carrier. os-dev.md :51/:53, SKILL.md :97–:98, core-rules :25, platform-readings :129, rest-channel :54.Writes:line (REST proxy only, the write budget as an endpoint list,mcp_callscounted, the dev reports both numbers); SKILL.md :602 — a report whosemcp_callsnames a write tool (thesettings.jsondeny list plusupdate_pull_request, which that list does not carry) is refused, ⛔ not accepted with a note; os-dev.md :369 says the same from the dev side.origin/maintouch of.claude/settings.json,.claude/agents/*.md,.claude/hooks/*against the shared checkout's HEAD; a touch not in HEAD ⇒ close the shift and re-seat in a fresh session before the next dispatch, ⛔ never advance the shared checkout in place. The reading isscripts/pm/check-harness-current.mjs(59 lines, git only, seat-side, ⛔ not wired into CI): exit 0 CURRENT, 1 STALE (each stale path with its touch), 2 UNDECIDED (shallow-clone negative that is not date-decided).cloud,objectos,hotcrm,www.objectos.ai. Named here only; no cards from this PR — the seat that can reach each files its card (recorded on [PM seat] domain:skills — 🟢 os-elon-musk · R2 · 在飞 1 / 3 (#18425) · 落地 7 (PR #18423 · #18444 · #18450 · #18449 · #18457 · #18436 · #18437) · 待批准 1 (PR #18447 ← #18384) · 决策箱 1 (#18434) · queue 2 (维护者门控 #17163 · #18290) #7623 until then).Premise readings (falsified against the tree before writing; all UTC)
af3add1601): all seven quoted lines read exactly as the dispatch quotes them — os-dev.md :51 「- GitHub 写一律走 REST 代理(curl带环境GITHUB_TOKEN),署名恒 App 的claude[bot]。」 and :53 「- ⛔ 不用任何 MCP GitHub 写工具:用户账号署名,封号即隐;⛔ 不枚举板面、不宽词搜。」; SKILL.md :95 「- 用户账号仅三用:assignee、授权批准、维护者亲手;⛔ 席位与 dev 永不以用户账号写内容。」 and :96 「- 内容恒经 REST 代理(claude[bot]);批准账号永不跑席位、不作席位 claude.ai 的关联用户。」; core-rules :25 「- 用户账号仅三用:assignee、授权批准、维护者亲手;写恒经 REST 代理;批准账号永不跑席位。」; platform-readings :129 「- 容器 curl 的 REST 通道 = App installation token,core 15,000/时,与 GraphQL 池独立计。」; rest-channel :54 「- 直合仓另有PUT .../pulls/{n}/merge;ccr 的 timeline actor 记claude[bot],MCP 记席位账号。」. Holds.git -C /home/user/objectstack rev-parse HEAD=84e6b05b6d295f1c744d236921300f447cf7791e,log -1 --format=%cI=2026-09-13T06:14:23+00:00;merge-base --is-ancestor 7ef05f9973 HEADexit 1. Control legs for the negative (shallow checkout,rev-list --count HEAD= 4024):is-ancestor 84e6b05b6d HEADexit 0 and, twelve commits deep,is-ancestor d88a47d766 HEAD(committed 2026-09-12T22:39:41Z) exit 0 at 16:12:17Z; the negative is also date-decided —7ef05f9973was committed 2026-09-13T23:27:23Z, seventeen hours after the shared HEAD.grep -c 'mcp__github__' .claude/settings.json: shared 1, worktree 15. Holds — with one sharpening: the shared file's single hit is a PreToolUse hook matcher (mcp__github__enable_pr_auto_merge|mcp__github__merge_pull_request), and the shared file has nopermissions.denykey at all (grep -c '"deny"'= 0 against 1 onorigin/main), so in this session no deny list was ever loaded, not a pre-lock-1 one.origin/main=af3add1601): the grep hits are SKILL.md :31 (never edit the shared checkout), :160 (never verify main from its worktree), :506/:606/:774 (paths named as protocol/governed surfaces or as the report authority); core-rules :44/:149 (the same two); dispatch-runbook :215 (frontmattermodel:exemption); platform-readings :30–:33 (merge-driver registration per clone), :214 (deny documented-not-measured), :344/:373/:415 (footer, transcript, sleep) and :413 (shallow-clone deepen); app-platform-boundary :60 and contract-review :57 (the word harness in other senses). None prescribes re-seating when a harness-loaded file lands after the session's clone; SKILL.md :86–:90 re-READS the three charter files, and reading does not reload the harness. Controlgit grep -c '收班简报'on SKILL.md = 5. Holds.派发令hits are SKILL.md :153/:164/:214/:433/:444/:461/:478/:540/:543/:715/:790 and dispatch-runbook :184/:205/:232/:236 — all rule lines about what the order carries; 〈模板与表〉 holds only the claim-comment template. No fixed shape exists, so theWrites:mandate lands as a rule line (SKILL.md :538). Holds.mcp_calls/api_writesreport fields; :57 already orders both counts. Holds; :369 rewritten, :370 untouched.check-skill-line-ratchetexit 0 with every one of the five files at its ceiling (812 / 403 / 454 / 82 / 151, headroom 0, table-row pins 342 / 0 / 0 / 0 / 0);check:skill-frame-syncexit 0;git ls-remote --heads originmatched only this branch for issue-17497/18205/18181/18158. Holds.scripts/pm/dispatch-gates.mjsis untouched; dispatch-gates: STAGE-THEN-RUN reaches a program by an edge neither follow traverses — check:objectui-changeset inherits nothing from scripts/bump-objectui.sh #14290'sRestart-touchsurface is left alone; the seat-side check is the sibling filescripts/pm/check-harness-current.mjs.The mechanism, measured in this session (16:25Z)
git show 84e6b05b6d:.claude/agents/os-dev.md(1 hit) and absent onorigin/main(0);api_writesis the inverse (0 in the old copy, 2 onorigin/main); controlWorktree-first1 / 1.node scripts/pm/check-harness-current.mjsfrom this worktree (shared checkout resolved through--git-common-dir): exit 1 —.claude/settings.jsonand.claude/agents/*.mdlatest touch7ef05f9973NOT in shared HEAD84e6b05b6d,.claude/hooks/*latest touchd79f249915(2026-09-12T09:41:28Z) in HEAD.--shared /home/user/objectstack-issue-18205: exit 0 CURRENT ataf3add1601.--shared /nonexistent: exit 2.Gates (final head
7103d0b09f, 16:28Z–16:37Z)node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack(no paths; change set derived from git, 6 paths, committed 6 / working tree 0 / untracked 0) printed 40 commands. All 40 run with redirect-then-capture, each recorded asCMD :: exit N:check:pm-skill-ratchet,check:skill-frame-sync,check:pm-governed-prose,check:pm-skill-id-lint,check:nul-bytes,check:agent-model-declared,check:entry-guard,check:parse-guard,check-self-test-wired,check-scripts-symbol-anchors,check:commit-card-trailers,check:pm-governed-merges.pnpm --filter @objectstack/lint run check:doc-formula-expressionsfirst read exit 3 = PREREQUISITE NOT MET (compiled@objectstack/formulaand@objectstack/lintabsent in the fresh worktree; the gate says "Nothing was measured"). Prerequisite cleared under the verify lock —os-verify-lock.sh -c 'pnpm exec turbo run build --filter=@objectstack/formula --filter=@objectstack/lint --concurrency=2': VERDICT command-exit 0, held the lock 172 s, waited 0 s — then rerun: exit 0, "22 record-scoped formula example(s) across 438 files / 1377 TS blocks judged clean by @objectstack/formula."dispatch-gates --ran ran.list --repo objectstack-ai/objectstackat 16:37:21Z on7103d0b09f: "Run reconciliation — 40 derived, 40 run, 0 NOT-MEASURED, 0 UNRUN." (exit 0; the derived 40 is recomputed by the tool from the tree, never read back from the record).files: ['**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}'], which coversscripts/pm/*.mjs); the only non-markdown file in the diff isscripts/pm/check-harness-current.mjs;eslint --no-inline-config --format jsonon it: 1 file, 0 errors, 0 warnings (exit 0); invariance:eslint.config.mjsstates it "never enables type-aware linting (noparserOptions.project, no typed@typescript-eslintrules) for ANY file", so a one-file addition cannot move any untouched file's verdict. The repo-widepnpm lintis CI's run..claude/**andscripts/pm/**publish nothing, soskip-changesetapplies (fast lane:.claude/**·scripts/pm/**). The seat writes the label; this container does not.Density paid inside each file
mode:cloud+ build-heavy-M → one line (drops 「必须」); marker spellings + missing-notification → one line (the dropped 「仅凭 HTML 注释形式缺失永不读作报告未达」 is what 「两种拼写等效」 states); 报告契约 authority + no-second-copy → one line (drops the implied 「字段与拼写以那里为准」).维护者速读(草稿)
user.login只说明这条会话的令牌是 App 的还是用户的,不说明是谁在写;身份看文本里的 session ID。② dev 报告里出现任何 MCP 写工具即拒收,不带注放行。③ harness 读的文件(settings.json、agents、hooks)在 main 上动了而共享检出没跟上时,席位收班、换新会话再派,永不原地推进共享检出。claude[bot]」被四个会话的实测证伪。Acceptance notes
.claude/settings.json): read, because the file is part of the clone." / "User and project local settings (~/.claude/settings.jsonand.claude/settings.local.json): not read. Both stay on your machine, and the local file isn't in the clone." / "Managed settings: only server-managed settings reach a cloud session; amanaged-settings.jsonfile or MDM profile on your device doesn't." And code.claude.com/docs/en/server-managed-settings: "Server-managed settings are available for Claude for Teams and Claude for Enterprise customers." So a personal account has no managed tier, and the maintainer-level lever the card names (a user-level file written by the environment setup script inside the cloud VM) is not the file those sentences describe — the docs speak of the file on the user's own machine; whether a user file written inside the VM is read is not stated. Bearer: the round report (the card already routes the lever there).mcp__github__*entry (pre-lock-1)" in the shared checkout's settings is a hook matcher, not a deny entry; the shared file has nopermissions.denyat all. Whether a deny list loaded from the clone takes effect in a cloud session therefore remains documented-not-measured (platform-readings :214 stands); the first session cloned after7ef05f9973measures it by tool-table absence. Bearer: the skills seat's next fresh session.mcp__github__update_pull_requestedits PR bodies and titles through MCP and is not in.claude/settings.json's deny list; the ACCEPT line names it explicitly for that reason. Reported in the dev report for the seat to file or fold (⛔ not changed here:.claude/settings.jsonis outside this card).--self-teston purpose: it is not CI-wired (check-self-test-wiredpopulates from workflows), it exports nothing (check:entry-guardrule two does not apply), and its three readings above are the measurement. Bearer: whoever wires it into a workflow later owes the self-test then.#18181 remains open(os-dev.md :287 label write is not addressed here);#18158 remains open(the identity reading itself); the objectui port (PR fix(devx): the objectui pin guard tests walk completeness, not object presence #9448) is untouched.Clause-②: no
Generated by Claude Code