Skip to content

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

Merged
os-project-manager merged 8 commits into
mainfrom
claude/issue-18068-write-identity-locks
Sep 13, 2026
Merged

os-project-manager merged 8 commits into
mainfrom
claude/issue-18068-write-identity-locks

Conversation

@claude

@claude claude Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor

Fixes #18068

Dev session session_01DAcomhvR9kKizeYgg89Vo8 on branch claude/issue-18068-write-identity-locks (worktree objectstack-issue-18068), off origin/main 6d64785, merged e248c4d before opening (no incoming commit touched these files). One commit per lock; each quotes its ruling.

Rulings (verbatim, untranslated)

  • Maintainer, skills seat chat, 2026-09-13T16:14Z: 「机制层的五道锁 现在就派发处理」 — the whole card.
  • Maintainer to the services seat, 2026-09-13: 「当前任务处理完,后续并发降到2」 — lock 3.
  • Triage ruling ③: 「立卡者不查重,只在卡面附 3–5 个查重词」 — why os-dev.md :51–:58 were stale (lock 2's payment).
  • Standing exception, pm-dispatch SKILL.md: 「唯一例外:platform-readings.md 增量抬上限到落地行数,免决策卡,记 ruledRaises 引常设裁决。条件:席位验收评论逐条核实、去重计数(候选/落地/已有/拒收)、一事一行、不计重排」 — the +2 on platform-readings.

What landed

  1. Lock 1 .claude/settings.json: permissions.deny with the 14 content-writing mcp__github__* tools the card lists; allow and hooks untouched; JSON.parse passes. 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__tool is the per-tool spelling; the shared .claude/settings.json is 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 in references/platform-readings.md (references tier, declared): 451 → 453, THIRTEENTH ruledRaises record; candidates 2 / landed 2 / already present 0 / refused 0, one matter per line.
  2. Lock 2 .claude/agents/os-dev.md 403/403: :51–:58 replaced by the REST-proxy write rule (curl + environment GITHUB_TOKEN, authored claude[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-body PATCH), the rest-channel pointer (kept), and api_writes + mcp_calls in 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 MCP issue_write; the out_of_scope_findings example no longer shows a dev-filed card number.
  3. Lock 3 SKILL.md :60 「默认 3」 → 「默认 2」; ceiling 5 unchanged; core-rules :11 states no default, so nothing mirrored.
  4. Lock 4 SKILL.md 〈全体座位的不变量〉 +2 lines (account roles; REST-proxy content and approver never seats), paid in the section (state and resume lines merged; the four Chinese channels named inline, dropping two parentheticals restated in 复核 and 升级与决策); the 〈认领〉 shared-identity line now reads 「身份只认正文 session ID,⛔ 不认作者字段」. core-rules 〈全体座位的不变量〉 one mirror line, paid by folding the three language lines into two. 812/812 (widest row 342 B), 151/151, frame :733–:754 md5 3327d02c56f8a0eca88569dad2270f32 unchanged.

Executable criterion, BASE 6d64785 → HEAD 7e1aeca

grep -c mcp__github__issue_write .claude/settings.json 0 → 1 (inside deny); SKILL.md 「默认 2」 0 → 1 and 「默认 3」 1 → 0; os-dev.md api_writes 0 → 2 (rule + template), search_issues 1 → 0; SKILL.md 「批准账号」 / 「永不跑席位」 0 → 1, core-rules 「批准账号」 0 → 1. Lit controls unchanged: 「每个方案必须沿四条固定评估轴分析」 1 → 1, 「一座位一车道双射」 1 → 1 in both files, os-dev.md mcp_calls 2 → 2.

Gates

node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at 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-expressions first 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 (inside check:pm-skill-ratchet), check:ratchet-remedy-authority, scripts/check-published-list-mirrors.mjs and scripts/check-skills-token-ratchet.mjs all 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-declared exit 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

  • Lock 4 is two SKILL.md lines, not one: the card's four clauses do not fit one 120-byte line; both are paid inside the section.
  • Lock 2 edits four lines outside :51–:58 (rule 6 premise line, resource rule 6, the label fallback, the report template), each net 0, each of which would otherwise contradict the budget.
  • The card's budget has no PR-body 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.
  • PR docs(pm,agents): three rules-layer lines catch up with the charter rulings #18051 (open draft) edits os-dev.md :50, adjacent to this diff's :51 — whichever lands second takes a one-hunk merge; its SKILL.md :106/:113 lines do not overlap.

Acceptance notes

  • noted, not filed (承接者: the skills seat reviewing this PR): mcp__github__update_pull_request_branch writes merge commits, and request_copilot_review writes a review request, yet neither is on the card's list, so both stay allowed.
  • noted, not filed (承接者: the skills seat): SKILL.md :778 still reads 「只列三类立卡与 noted, not filed」; under lock 2 the dev lists findings to file and the seat files them.

维护者速读(草稿)

改了什么:四道机制锁。① 仓库的 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

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

claude Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor Author
  • Served-tier: 1345/1345 claude-fable-5-1 — harness model stamp counted over this seat's own transcript (non-sidechain assistant messages a model served; <synthetic> harness notices excluded) at 2026-09-13T17:22Z; get_session external_metadata.last_served_model read claude-fable-5-1 at 2026-09-13T17:22Z.

Contract review

Head: a4dfd9d0 (PR #18072, card #18068) — read at 2026-09-13T17:23Z by the skills seat at the contract-review tier. GOVERNED rules layer, measured: .claude/settings.json (+16), .claude/agents/os-dev.md (11 lines, 403/403), .claude/skills/pm-dispatch/SKILL.md (6 lines, 812/812), references/core-rules.md (3 lines, 151/151), references/platform-readings.md (+2, 451 → 453 under the standing exception), scripts/pm/check-skill-line-ratchet.mjs (the ceiling row + a ruledRaises record) ⇒ four-piece: this record + 速读终稿 below, ACCEPT on #18068, needs-user-decision on this PR, reviews requested from os-zhuang and hotlong; ⛔ draft until an authorized approval exists, then ruling C landing by this seat. Built at claude-fable-5-1 by the tiering mandate; reviewed at the same tier. Ruling of record: the maintainer's 「机制层的五道锁 现在就派发处理」 (2026-09-13T16:14Z); lock 3 also carries 「当前任务处理完,后续并发降到2」.

① derived judgments — locks 1–4 of the write-identity plan, one commit each, every file at its ceiling and net 0:

  1. Lock 1, settings.json: permissions.deny names exactly the 14 content-writing MCP GitHub tools the card lists; the allow list (47 entries) and the hooks are untouched; the file parses. The Claude Code permissions documentation reads deny before allow at every scope, shared project settings are read in cloud sessions, and a bare-name deny removes the tool from the roster — recorded as two 文档载明未实测 lines in platform-readings (seat-verified against the diff: candidates 2 / landed 2 / already present 0 — the base's only neighbour, :280, speaks of permissions.allow — / refused 0; one fact per line; no reordering). Enforced by the runtime, not merely declared; the two update_pull_request_branch / request_copilot_review tools the dev notes stay allowed by the card's own list (state-shaped, not content) — accepted as noted.
  2. Lock 2, os-dev.md: the write channel is the REST proxy only, the budget is four writes (push, POST /pulls draft, POST /issues/{n}/labels, the report), ⛔ no MCP GitHub write tool, ⛔ no board enumeration, findings go to the report for the seat to file, and the report gains api_writes; paid by replacing the :51–:58 dedupe-channel lines that ruling ③ made stale, plus four net-0 touches outside them (each named in the report and each consistent with the rule: the label-write fallback no longer prescribes issue_write; the template example no longer shows a dev-filed card). The one line that ran 141 B on 7e1aecad is 106 B on this head; the file has zero lines over 120 again (base parity).
  3. Lock 3, SKILL.md :60: 「默认 3」 → 「默认 2」, ceiling 5 unchanged; core-rules.md :11 states no default, so no mirror was owed — measured, not assumed.
  4. Lock 4, SKILL.md 〈全体座位的不变量〉 + 〈认领〉 + core-rules.md: user accounts do three things only (assignee, authorized approval, the maintainer's hand); ⛔ seats and devs never write content as a user account; content goes through the REST proxy as claude[bot]; approver accounts never run a seat nor are a seat account's linked user; identity is the body's session ID, never the author field. Two lines instead of the card's one (the four clauses do not fit 120 B) — accepted; paid in the same section; the 〈认领〉 shared-identity line rewritten in place. core-rules.md mirrors the account-roles rule in one line. Widest row still 342 B (L244); frame block :733–:754 md5 3327d02c56f8a0eca88569dad2270f32 unchanged.
  5. The dev's open question is answered here: A — the four-write budget stands; a dev's late gate results go into its report comment, which the seat reads and verifies against api_writes; no fifth PATCH /pulls/{n} write.
  6. Boundary noted, not owed here: SKILL.md :778 「只列三类立卡与 noted, not filed」 now reads against lock 2's 「the dev lists findings to file and the seat files them」 — one-word residue for the next density pass on this file (this seat's), not this PR.

Seat measurements on the head tree (git archive a4dfd9d0): check-skill-line-ratchet ✓ on every touched file at headroom 0 (platform-readings 453/453 by the ruledRaises record); frame md5 equal; settings.json parses; check-governed-prose ✓; os-dev.md over-120 count 0, max 120. Executable criterion re-read on the diff: mcp__github__issue_write in settings.json 0 → 1 inside deny; 「默认 2」 0 → 1 / 「默认 3」 1 → 0; api_writes 0 → 2; 「批准账号」 0 → 1 in both SKILL.md and core-rules.md; controls unchanged. --pair 18072 → exit 0 at 2026-09-13T17:22Z. Checks on a4dfd9d0 at 2026-09-13T17:22Z: 29 runs, 0 red, 10 running — the landing check ③ reads them green before ruling C fires. The dev's 42-family derivation ran green at 88e0610f; the two later commits touch os-dev.md (one line) and merge origin/main.

② semver: .claude/** and scripts/pm/** publish nothing; skip-changeset is right.

③ boundary flags: open_questions — one, answered (A). Landing consequence: every seat and dev session in this repository loses the MCP GitHub content-writing tools; writes go through the REST proxy as claude[bot]; the batch default is 2; the objectui copy of settings.json and its AGENTS.md §9 line are objectui cards in this seat's next filing batch.

Implemented-by: claude/issue-18068-write-identity-locks
Reviewed-by: session_01DAcomhvR9kKizeYgg89Vo8

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.

维护者速读(终稿)

改了什么:四把锁。① 仓库 .claude/settings.json 加 deny 名单,禁掉 14 个会以用户身份写内容的 MCP GitHub 工具(建 issue、建 PR、发评论、审查、推文件、合并等);翻 ready、挂 auto-merge、重跑 CI 这些状态操作保留。② os-dev.md:dev 的 GitHub 写入只走 REST 代理(署名 claude[bot]),预算四笔(push、建 draft PR、贴标签、报告),不枚举板面,发现交席位立卡,报告新增 api_writes 计数。③ SKILL.md 里 batch 默认 3 改 2,天花板 5 不变。④ SKILL.md 和 core-rules 各加一条:用户账号只做 assignee、批准、你亲手三件事;席位和 dev 永不以用户身份写内容;批准账号不跑席位、不做席位账号的关联用户;身份看正文 session ID,不看作者栏。另外 platform-readings 记了两行文档读数(deny 在云会话里生效、被拒工具从工具表消失),按常设例外把该文件上限 451 抬到 453。
为什么改:今天 os-musk 被封,它用 MCP 建的 12 张卡全部隐藏;测得 MCP 写 = 用户署名,REST 代理写 = App 署名。这四把锁让「以用户身份写」在机制上不可达,并把并发和写量压到限额之下。
风险与代价(含回滚):纯配置与指令文本,无运行时影响;各文件行数不变、棘轮不动(platform-readings 的 +2 走常设例外并有记录)。回滚 = revert 对应 commit,四把锁彼此独立。deny 生效后,任何席位再想用 MCP 建卡会直接报工具不存在——这是预期,不是故障。
席位意见:通过。dev 问的一句(要不要给 dev 一次改自己 PR 正文的写)我定了 A:不给,晚到的结果写进报告。
你要做的:一次批准(os-zhuang 或 hotlong)。批准后我按裁决 C 落地,不再找你。
一句问:deny 名单落地后你自己的会话也不能再用 MCP 建 issue、发评论,只能走 REST 或网页——接受吗(是/否)?


Generated by Claude Code

@os-zhuang
os-zhuang marked this pull request as ready for review September 13, 2026 22:41
@os-zhuang
os-zhuang enabled auto-merge September 13, 2026 22:41
@os-zhuang
os-zhuang added this pull request to the merge queue Sep 13, 2026
@claude

claude Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor Author

Ruling C landing step — skills seat (session session_01DAcomhvR9kKizeYgg89Vo8), 2026-09-13T22:42Z. Authorized approval read: os-zhuang APPROVED on head a4dfd9d0 (review 5192548807, submitted 2026-09-13T22:41Z; not dismissed), who also flipped the PR ready and armed auto-merge. Seat readings at 2026-09-13T22:42Z: ① the contract review of record 5654849272 names this head a4dfd9d0 (unchanged since); ② check-clause2-carriers --pair 18072 exit 0; ③ 36 checks on a4dfd9d0, 0 red, 1 running — the enqueue follows its green; ④ needs-user-decision cleared in this pass and read back. PR #18051 is ahead of it in the queue on the same two files (different lines); the queue stacks this PR on top of that merge, and the seat re-reads and re-arms only if the queue ejects it. Landing record follows on #18068 once origin/main carries (#18072) — from that commit the MCP GitHub content-writing tools are denied in every session of this repository and batch defaults to 2.


Generated by Claude Code

@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to a conflict with the base branch Sep 13, 2026
…ite-identity-locks

# Conflicts:
#	.claude/agents/os-dev.md
@claude

claude Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor Author
  • Served-tier: 1450/1450 claude-fable-5-1 — harness model stamp counted over this seat's own transcript (non-sidechain assistant messages a model served; <synthetic> harness notices excluded) at 2026-09-13T23:03Z; get_session external_metadata.last_served_model read claude-fable-5-1 at 2026-09-13T23:03Z.

Contract review

Head: 3a3f5e0d (PR #18072, card #18068) — re-issued at 2026-09-13T23:04Z by the skills seat at the contract-review tier for the head that moved: the merge queue ejected a4dfd9d0 at 2026-09-13T23:00Z (MERGE_CONFLICT against PR #18051's landing 137eb00e), and the dev merged origin/main as a merge commit (no rebase, no amend; follow-up 5656804868). GOVERNED rules layer; the authorized approval (os-zhuang 5192548807 on a4dfd9d0, not dismissed) stands under ruling C for the seat's landing.

① derived judgments — the merge added no content of its own:

  1. The diff against origin/main is byte-for-byte the reviewed one: the same six files, +70 −21 (settings.json +16, os-dev.md 11 lines, SKILL.md 6, core-rules.md 3, platform-readings +2, the ratchet ceiling row + ruledRaises record). The record 5654849272's judgments 1–6 hold unchanged on this head.
  2. The one conflicted file resolved with both sides intact (.claude/agents/os-dev.md :50–:57): docs(pm,agents): three rules-layer lines catch up with the charter rulings #18051's landed line 立卡者不查重,只在卡面附 3–5 个查重词 at :50 (present, 1 hit) and lock 2's REST-only write rule, the four-write budget, the no-MCP line and api_writes right below it (present); SKILL.md carries both docs(pm,agents): three rules-layer lines catch up with the charter rulings #18051's 改路由(限未派发) rows and lock 3's 默认 2 plus lock 4's account-roles lines.
  3. Ceilings on the merged head, seat-measured on git archive 3a3f5e0d: SKILL.md 812/812 with the widest row 342 B and the frame block :733–:754 md5 3327d02c56f8a0eca88569dad2270f32; os-dev.md 403/403 with zero lines over 120 B; core-rules.md 151/151; platform-readings 453/453 by the ruledRaises record; settings.json parses; check-governed-prose ✓; check-skill-line-ratchet ✓ exit 0.

Seat measurements: --pair 18072 → exit 0 at 2026-09-13T23:03Z. Checks on 3a3f5e0d at 2026-09-13T23:03Z: 29 runs, 0 red, 7 running — the enqueue follows their green.

② semver: unchanged — nothing published; skip-changeset is right.

③ boundary flags: none new. Landing consequence unchanged: the MCP GitHub content-writing tools are denied in every session of this repository, batch defaults to 2, user accounts hold three roles only.

Implemented-by: claude/issue-18068-write-identity-locks
Reviewed-by: session_01DAcomhvR9kKizeYgg89Vo8

Verdict: PASS — the reviewed diff, now on a head that merges cleanly with origin/main; re-armed for the queue under the standing approval.


Generated by Claude Code

@claude

claude Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor Author

Ruling C re-landing step — skills seat (session session_01DAcomhvR9kKizeYgg89Vo8), 2026-09-13T23:05Z. After the queue ejection (2026-09-13T23:00Z, MERGE_CONFLICT against PR #18051's landing), the dev merged origin/main as a merge commit → head 3a3f5e0d (follow-up 5656804868). Seat readings at 2026-09-13T23:03Z: ① the contract review of record re-issued on this head (5656815610, PASS); ② check-clause2-carriers --pair 18072 exit 0; ③ 29 checks on 3a3f5e0d, 0 red, 7 running — the enqueue follows their green; ④ needs-user-decision already cleared (22:41Z) and still absent. Authorized approval: os-zhuang 5192548807 (not dismissed) stands. Auto-merge re-armed by this seat at 2026-09-13T23:04Z; ready was never lost. Landing record follows on #18068 once origin/main carries (#18072).


Generated by Claude Code

@os-project-manager
os-project-manager added this pull request to the merge queue Sep 13, 2026
Merged via the queue into main with commit 7ef05f9 Sep 13, 2026
32 checks passed
@os-project-manager
os-project-manager deleted the claude/issue-18068-write-identity-locks branch September 13, 2026 23:52
This was referenced Sep 14, 2026
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 17, 2026
…, and rest-channel.md names it instead of the denied MCP fallback (objectstack-ai#18085) (objectstack-ai#18099)

Fixes objectstack-ai#18085

`references/rest-channel.md:39` routed a seat whose REST channel is shut
to MCP `issue_write`. Lock 1 (`7ef05f997`, objectstack-ai#18072) added that tool to
`permissions.deny`, so the documented recovery path terminated in a
denial — and because it was the only spelling of the fallback, a
gate-closed seat had no label channel named anywhere on the board.

Two halves, and only the second is a documentation fix:

1. **`scripts/pm/label-write.mjs` (new, non-governed PM tooling)** — the
four steps SKILL.md mandates (取现集 → 只增删目标 → 写合并集 → 回读 diff 对 union(现集,
增删)) as a program rather than a paragraph. Every invocation performs all
four and prints each with the UTC stamp that step was taken at. Additive
`POST .../labels` and directed `DELETE .../labels/{name}` first; a
platform refusal falls back **once** to `PATCH /issues/{n}` with the
full target set **and the current assignees echoed**; step ④ reads back
and diffs against the target either way.
2. **`references/rest-channel.md:37–39`** — repaid equal-line (82 stays
82) to name the channel that exists.

`package.json` and `.github/workflows/lint.yml` wire
`check:pm-label-write` the way every sibling `scripts/pm/` tool with a
`--self-test` is wired — an alias plus an unconditional lint step, which
is what puts the script inside `check:self-test-wired`'s population at
all (that gate's population is "a script a **workflow** names", so a
`package.json` alias on its own would have left the self-test outside it
and silently unrun).

## Premise readings

All four checked against `origin/main` `d438b3a9` before the first edit,
on 2026-09-14.

| | premise | reading | at |
|---|---|---|---|
| **P1** | `rest-channel.md:39` sends a gate-closed seat to MCP
`issue_write` | **HOLDS.** Line 39 read `- 门关席位无此端点 ⇒ 回退 = MCP
读现值、并集、整组写、读回;读回是它安全的全部理由。` File was 82 lines. | 01:24Z |
| **P2** | `.claude/settings.json` denies `mcp__github__issue_write` |
**HOLDS.** 14 deny entries, `mcp__github__issue_write` is the **first**.
47 allow entries. | 01:25Z |
| **P3** | no script under `scripts/pm/` writes labels or assignees |
**HOLDS for `scripts/pm/`**, and with one correction worth recording:
`scripts/pr-labels.mjs` (repo root, objectstack-ai#10703) already writes **PR** labels
additively from `pr-automation.yml`, and
`scripts/check-whole-set-label-write.mjs` (objectstack-ai#10778) already bans `PUT
.../labels` over `.github/workflows/**`, `.github/actions/**` and
`scripts/**`. Neither is a seat-facing card tool and neither touches
assignees, so the gap the card names is real — but the new script is a
sibling of an existing discipline, not a first.
`check-label-desc-cap.mjs` and `ensure-pm-labels.sh` are
description/existence tools as stated. | 01:26Z |
| **P4** | `sweep-closed-cards.mjs --write` is wired in
`half-state-patrol.yml` as the bot | **HOLDS.** So closed-card residue
already has a seat-free channel; this card's gap is the **open-card
transition** only. | 01:27Z |

No premise was falsified, so the PR stands.

## The live proof — one idempotent no-op on this card

Run against `objectstack-ai#18085` itself with `--add
domain:skills`, a label it already carries. ⛔ Nothing on the card
changed: step ③ made **zero** write calls, and the read-back is the card
as it was.

```
[2026-09-14T01:34:16Z] ① 取现集 — objectstack-ai#18085 (open) carries 3 label(s): `priority:p1`, `pm:dispatched`, `domain:skills` · 1 assignee(s): `os-project-manager`
[2026-09-14T01:34:16Z] ② 目标 labels — 3: `priority:p1`, `pm:dispatched`, `domain:skills` · POST none · DELETE none · already present, no call `domain:skills` · already absent, no call none
[2026-09-14T01:34:16Z] ② 目标 assignees — 1: `os-project-manager` · add none · remove none
[2026-09-14T01:34:16Z] ③ 写 — 0 calls: the target already equals the current set. An idempotent no-op is a success, not a skip.
[2026-09-14T01:34:16Z] ④ 回读 — 3 label(s): `priority:p1`, `pm:dispatched`, `domain:skills` · 1 assignee(s): `os-project-manager`
[2026-09-14T01:34:16Z] ④ MATCHES the target — labels `priority:p1`, `pm:dispatched`, `domain:skills` · assignees `os-project-manager`.
```

Exit 0. The `--dry-run` of the same command at 01:33:40Z printed the
fallback body it would send if every additive verb were refused, which
is where the assignee echo is visible:
`{"labels":["priority:p1","pm:dispatched","domain:skills"],"assignees":["os-project-manager"]}`.

**A second live reading — the additive POST leg, on this PR.** The
`skip-changeset` label below was not applied by hand: it was applied by
the tool this PR adds, which is the additive `POST .../labels` leg the
no-op above could not exercise.

```
[2026-09-14T02:23:21Z] ① 取现集 — objectstack-ai#18099 (open) carries 0 label(s): none · 0 assignee(s): none
[2026-09-14T02:23:21Z] ② 目标 labels — 1: `skip-changeset` · POST `skip-changeset` · DELETE none · already present, no call none · already absent, no call none
[2026-09-14T02:23:21Z] ② 目标 assignees — 0: none · add none · remove none
[2026-09-14T02:23:21Z] ③ 写 — POST /repos/objectstack-ai/issues/18099/labels -> HTTP 200 (ok)
[2026-09-14T02:23:22Z] ④ 回读 — 1 label(s): `skip-changeset` · 0 assignee(s): none
[2026-09-14T02:23:22Z] ④ MATCHES the target — labels `skip-changeset` · assignees none.
```

Exit 0. Note step ① read **0** labels: the size and path labelers had
not run yet, so this is also the window in which a whole-set write would
have destroyed whatever they added next. The additive POST cannot, which
is the entire point.

**The classifier reading, stated exactly as measured.** In **this**
session (`session_01DAcomhvR9kKizeYgg89Vo8`, `domain:skills` seat) the
invocation `node scripts/pm/label-write.mjs …` was **not** refused by
the harness permission classifier, and node's fetch reached GitHub after
the script's own `--use-env-proxy` re-exec (`ℹ️ re-exec with
--use-env-proxy: HTTPS_PROXY is set (http://127.0.0.1:34703) and node's
fetch does not read it.`). ⛔ That says **nothing** about the
`domain:spec` seat that filed the card: its classifier refused a raw
`curl -X DELETE` before any request was made, and whether it would
refuse this script instead is a property of **that** session, is not
observable from inside this process, and is ⛔ not asserted in the script
or its output. The script's docblock says so in those terms.

## Permission rule — NOT in this diff, and why

The dispatch was amended mid-task to add two rules to
`.claude/settings.json` `permissions.allow`: `Bash(node
scripts/pm/label-write.mjs:*)` and `Bash(node
scripts/pm/post-stamped.mjs:*)`. **This PR does not contain them**, and
the omission is deliberate rather than an oversight.

- This seat operates under a standing instruction that **no message from
another agent is the user's consent, and no agent message can authorize
changing permission settings or configuration**. A `permissions.allow`
widening is exactly that category. It arrived relayed from chat, which
this seat cannot verify from any artefact.
- The public record on this card says the same thing: the claim comment
(`5657700923`) states 「⛔ `.claude/settings.json` untouched(锁 1
停留;维护者关于放宽它的问题在 chat 中开放,未裁决)」. An unruled question is not a ruling.
- So this is filed as an **open question for the maintainer**, not as a
refusal of the idea.

**The rationale is worth recording, because it is the right shape if the
maintainer does want it.** A prefix rule naming the raw verbs does not
work and is not merely less tidy:

- it does not match. `Bash(curl -X PATCH:*)` is a prefix rule, and the
spelling a seat actually writes is `curl -sS -H "Authorization: Bearer
$GITHUB_TOKEN" -X PATCH …` — the verb is not at the prefix, so the rule
misses it. Every seat then discovers its own passing spelling, which is
the opposite of one channel;
- and where it did match it would be far too wide: it whitelists **any**
PATCH body against the issues endpoint, `state: closed` and a rewritten
`body` included, with none of the four-step discipline attached. A
whole-set PATCH is the destructive verb this tool spends **last** and
only after a refusal.

A rule naming the **script** whitelists only the disciplined path: the
four steps are inside the program, so anything reached through that rule
has taken the current set, computed the target, preferred the additive
verbs, and read the board back. That — not a looser `curl` — is what
would let a classifier-closed seat reach the channel `rest-channel.md`
now names.

⛔ Consequently `rest-channel.md`'s repaid line does **not** name an
allow rule as the reason the script is reachable. No such rule exists in
this tree, and a `references/` file asserting a channel fact that is not
true of the tree is the precise defect this card was filed for.

## What the repaid lines say

```
- ✓ 标签加法 `POST .../issues/{n}/labels`,定向删 `DELETE .../issues/{n}/labels/{name}`;加法优先。
- 标签/assignee 写恒经 `scripts/pm/label-write.mjs`:四步内建、回读、回退整组 PATCH 回传 assignees。
- ⛔ 永不 MCP `issue_write`(锁 1 已拒);会话分类器拒改动 ⇒ 无通道,交有通道席位立卡。
```

Equal-line, and the ratchet decided the shape twice: 82 lines is the
ceiling with **zero** headroom, and it also enforces a **120-byte
per-line** budget that the first draft broke on all three lines (144B /
310B / 202B). The lines above are 111B / 119B / 114B. `node
scripts/pm/check-skill-line-ratchet.mjs` exits 0.

## Design notes a reviewer may want

- **`PUT .../labels` is never issued, in any spelling.**
`check-whole-set-label-write.mjs` bans it over `scripts/**` and this
file is in that root; the gate passes. The `PATCH /issues/{n}` fallback
is a **different endpoint carrying the same hazard**, which is why it is
reached only after a refusal, echoes assignees, and is always read back.
- **A 403 with `x-ratelimit-remaining: 0` does not reach the fallback.**
It exits 3. Rate-limit refusal binds the identity, every seat on this
board shares that identity, and `rest-channel.md`'s own rule is
「换通道续写与重试同罪」 — so a fallback there would be the banned act, spelled as a
recovery. Pinned in the self-test.
- **A 404 on a directed DELETE is idempotent success**, not a refusal —
the label is already gone. Pinned both ways: the plan skips a removal
that is not in the current set, and a 404 from a removal that raced is
read as success.
- **The three vocabularies are imported from `check-half-states.mjs`,
never restated**: `PM_EXCLUSIVE_STATE_LABELS`, `PM_STATE_CLAIM`,
`PM_RESIDUE_LABELS`. A target carrying two ONE-OF states is refused
before any write — H29's finding asked of the **target**, at the one
moment the pair is still one keystroke from correct — with
`--allow-two-states` as the declared exception that still prints what it
let through.
- **`concurrentAdds` is reported and is ⛔ not a mismatch.** Preserving
another seat's additive label is the entire reason POST/DELETE come
first; treating it as a failure would push a seat toward the whole-set
write.

## Tests

`pnpm check:pm-label-write` — `✓ label-write self-test: 64 cases pass
across 8 batteries` (the union/difference arithmetic, the ONE-OF
refusal, the idempotent DELETE, the stripped-underneath
re-add-and-report, the mismatch exit, the rate-limit non-fallback, and
the assignee echo on the whole-set PATCH). The fake board it drives
models `PATCH /issues/{n}` **destructively** on purpose — a fake that
merged instead would pass every assertion while the echo went untested.
Batteries carry a declared floor and a verdict handshake, so "every case
held" and "the cases never ran" cannot print the same line.

CLI exits verified directly: usage 2, `--help` 0, missing token 3.

## Gates

`node scripts/pm/dispatch-gates.mjs --commands` derived **71 families**
from the four changed paths. All 71 were run with per-command
redirect-then-capture, twice — once before the `origin/main` merge and
once after, at `0e803e026` — and the recorded exit codes were fed back:

```
Run reconciliation — 71 derived, 68 run, 3 NOT-MEASURED, 0 UNRUN.
✓ dispatch-gates --ran: 71 derived famil(ies) accounted for — 68 run, 3 NOT-MEASURED (3 DERIVED from a recorded exit 3).
```

**68 green at `0e803e026`.** The 3 NOT MEASURED are
`check:dual-build-cjs-loads`, `check:lean-entry-closure` and
`check:type-check-debt` — every one exits **3, PREREQUISITE NOT MET**,
refusing because it reads a full workspace build that is not on disk. ⛔
Exit 3 is neither a pass nor a finding, and none of the three is read as
either here.

This is a **declared narrowing, and the narrowing is proven**, not
asserted: the three are matched by the `Build Core` job's `package.json`
path filter, and nothing in this diff can move them —

- `git diff --name-only origin/main...HEAD -- packages/` returns **0
files**;
- the entire `package.json` delta is **one line added to the `scripts`
map**, touching no `exports`, no `files`, no dependency;
- so the built closure those three read is byte-identical to `main`'s.

CI's `Build Core` runs them against a real build, which is the right
place for them. Three others (`check:doc-formula-expressions`,
`check:dts-closure`, `check:sourcemap-no-sources-content`) also exited 3
at first and **were** measured rather than declared: they needed only
`@objectstack/formula` + `@objectstack/lint` built (`pnpm exec turbo run
build --concurrency=2 --filter=@objectstack/formula
--filter=@objectstack/lint`, through `scripts/pm/os-verify-lock.sh`,
`VERDICT command-exit 0`), and all three exit 0 after it.

`origin/main` moved to `a26a114d7` after the readings above; re-deriving
against it returns the **identical** 71-family list, so the reading
stands and the PR's own CI covers the merge.

`check:nul-bytes` is green, and a direct `grep -naP` for control bytes
over the four changed files finds none.

## 维护者速读(草稿)

**改了什么。** 新增一个 PM 工具
`scripts/pm/label-write.mjs`,把「标签写恒四步」从一段规矩变成一个可执行程序;把
`references/rest-channel.md` 里那三行改成指向它。另加两处接线(`package.json`
别名、`lint.yml` 自检步骤)。共 4 个文件,不动任何包源码。

**为什么改。** 锁 1 把 `mcp__github__issue_write` 加进了 deny,而那个工具正是
`rest-channel.md`
给「通道关闭的席位」写的唯一退路。锁是对的,不该回滚;错的是没人把那句退路偿上。结果是一个席位无法认领、因此无法派发任何卡,只能在卡上写段散文。

**风险与代价(含回滚)。** 风险很低:新脚本没有任何调用方,不进 CI 的活路径(CI 只跑它的离线
`--self-test`),不碰任何发布物。回滚就是 revert 这一个
PR,无残留。唯一值得注意的代价:`rest-channel.md` 已到顶(82/82 行),以后再加行必须先删行。

**席位意见。**

**你要做的。** 两件。① 这是受管面(`.claude/**`),只能你亲自合,或给一个授权的 APPROVED review
让队列落地;席位不翻 ready、不入队、不挂 auto-merge。② 上面「Permission
rule」一节里的问题需要你一句话:要不要把 `Bash(node scripts/pm/label-write.mjs:*)` 和
`Bash(node scripts/pm/post-stamped.mjs:*)` 加进
`permissions.allow`。本席位没有加,因为改权限配置需要你本人的话,而不是另一个 agent
转述的话。加了以后,分类器关着的席位才真的能用上这个脚本;不加也不阻碍本 PR 落地,只是那半个缺口留着。

## Acceptance notes

Observed, ⛔ not filed, ⛔ not fixed here:

- **The two remaining `issue_write` references are still standing, by
dispatch.** `pm-dispatch/SKILL.md:378` and
`.claude/skills/checklist-test/SKILL.md:123` both still name the denied
tool. They are the **rules** layer rather than the 事实 layer, and the
dispatching seat holds the follow-up governed card for them. This PR
deliberately does not widen into them — one governed file is already one
too many to mix.
- **Whether the script is reachable from a classifier-closed seat is
still unmeasured.** This seat's classifier allowed it; the filing seat's
refused raw `curl`. Nobody has run this script from a session in that
state, and the script cannot measure it about itself. That is the open
half of the card, and the permission-rule question above is what would
settle it.
- **`references/rest-channel.md` is at its ceiling with zero headroom**
(82/82 lines, 120 bytes per line). Any future line there has to buy its
space by deleting one. Worth knowing before the next channel fact needs
recording; not a defect.
- **`scripts/pr-labels.mjs` and `scripts/pm/label-write.mjs` are now two
additive label writers** with no shared code. That is correct today —
one is a CI job writing PR size/path labels from a workflow, the other
is a seat writing card state over the proxy, and they share neither
transport nor vocabulary — but it is the kind of pair that drifts. No
card: nothing is wrong yet, and a premature merge would couple a CI
script to the PM vocabulary module.

Clause-②: no


---
_Generated by [Claude Code](https://claude.ai/code)_

---------

Co-authored-by: claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 17, 2026
…03 for the seat token, MCP rerun_failed_jobs 201 (objectstack-ai#18025) (objectstack-ai#18129)

Fixes objectstack-ai#18025

One row in `.claude/skills/pm-dispatch/references/platform-readings.md`,
in the Actions / re-run block, plus the ceiling increment it costs. No
other file moves.

Dev session `session_01DAcomhvR9kKizeYgg89Vo8` on branch
`claude/issue-18025-platform-readings-rerun-channel` (worktree
`objectstack-issue-18025`), off `origin/main` `ca788604`, BASE
`ca7886047b27667122f9c2c44de3b6e67eb361fe`. No merge was needed: nothing
landed on `main` under either path since the branch point.

## What landed

`platform-readings.md` :285, placed immediately after the existing
re-run pair (:283–:284):

```text
- 读数 2026-09-13:席位 REST `/jobs/{id}/rerun` 403、MCP `rerun_failed_jobs` 201 ⇒ 重跑可做,锁 1 未禁。
```

117 bytes against the 120-byte line cap; one matter on the line; a dated
读数 in the file's own register voice. It records the CHANNEL split the
card measured, and nothing else:

- the seat's REST token (`GITHUB_TOKEN` through the session proxy)
answers **403** `Resource not accessible by integration` on `POST
/repos/objectstack-ai/objectstack/actions/jobs/103706765153/rerun`;
- MCP `actions_run_trigger` with `method: rerun_failed_jobs`, `run_id:
34750740645`, answers **201** and re-queues the failed shards — attempt
2 visible on the run.

The consequence rides the same line, which is why the row is worth a
ceiling increment at all: **the one confirming re-run of a failure that
is not this PR's is exercisable by the seat**, and lock 1 (PR objectstack-ai#18072,
`7ef05f997`) does not take it away — `actions_run_trigger` is
state-shaped, so it is not on the write-identity deny list. The standing
text that read 「re-run is 403 to this seat」 was true of ONE channel,
which is the error the triage comment 5654613958 generalises: 「the seat
cannot do X」 is a claim about a channel, ⛔ never about the seat.

Two things the 120-byte cap did NOT buy, declared rather than quietly
dropped:

- **the 403's message text.** Error prose is not pinned unless a
consumer parses its original words; nobody parses this one, and the
operative discriminant is the status pair 403 / 201. The full text stays
on the card.
- **the `POST` verb and the `/actions` path segment.** The path
`/jobs/{id}/rerun` exists only as a POST, and :291 already carries the
sibling `GET /actions/jobs/{id}/logs` spelling, so the family is legible
from the neighbourhood. Spelling both would have cost 8 bytes the line
does not have.

## Ceiling 453 → 454 — the standing exception, no decision card

`scripts/pm/check-skill-line-ratchet.mjs`: the `platform-readings.md`
ceiling moves by exactly the landed delta, and the raise is recorded as
the FOURTEENTH `ruledRaises` record on the cross-file-move declaration,
in the same shape as the thirteenth (added by objectstack-ai#18072 at `7ef05f997`) and
quoting the same ruling verbatim and untranslated — pm-dispatch SKILL.md
〈分诊座位职责〉:

> 唯一例外:`platform-readings.md` 增量抬上限到落地行数,免决策卡,记 `ruledRaises`
引常设裁决。条件:席位验收评论逐条核实、去重计数(候选/落地/已有/拒收)、一事一行、不计重排

⛔ No other ceiling moves. ⛔ No decision card, because the exception says
none is owed.

**Density was MEASURED, not assumed** — the exception's own precondition
and the 2026-08-17 no-re-wrap-funding rule:

- of the file's **427** adjacent bullet pairs, **ZERO** merge within the
120-byte cap; the smallest merged width is **134 B**.
- the two neighbours the row joins offer **37** spare bytes (:283, 83 B)
and **6** spare bytes (:284, 114 B), against the row's **115** bytes of
content after the `- ` marker. Neither can absorb it, and folding it
into :283 would put a second matter on a line — 一事一行.

⇒ the row could not be paid in place, so the increment is +1 and the
landed count is 454.

## Premise readings — all four hold, none falsified

Taken on this worktree at `BASE` `ca788604` unless noted.

| # | premise | reading | verdict |
|:--|:--|:--|:--|
| P1 | the file is 453 lines at ceiling 453 | `node
scripts/pm/check-skill-line-ratchet.mjs` at 2026-09-14T03:42Z, exit 0:
`✓ check-skill-line-ratchet:
.claude/skills/pm-dispatch/references/platform-readings.md is 453 lines
(ceiling 453; headroom 0).` | **holds** |
| P2 | no row states the channel split | `git grep -n -i -E 'Resource
not accessible|actions/jobs|rerun_failed_jobs'` on the file at
2026-09-14T03:40Z returns exactly TWO hits, quoted below | **holds** |
| P3 | the objectstack-ai#18085 footer fact is already on :333–:334 | quoted below,
byte-for-byte from `BASE` | **holds — already present** |
| P4 | no in-flight branch touches the file | scan at 2026-09-14T03:47Z,
below | **holds** |

**P2, every hit quoted:**

```text
283: - `rerun_failed_jobs` 复用原 run 的提交与合并 ref,不拿新 main 重算。
291: - ⇒ 两者都答不了到底挂在哪;`GET /actions/jobs/{id}/logs` 被出口代理拒绝,CONNECT 403。
```

:283 states only that a re-run reuses the original run's commit and
merge ref — a fact about WHICH TREE is re-run, silent on WHO may re-run
it. :291 is a different endpoint (`GET .../logs`, not `POST .../rerun`)
failing a different way (the egress proxy's CONNECT 403, not GitHub's
`Resource not accessible by integration`). Neither says a re-run is
unavailable, so the card's 「if the file already carries a row saying
re-runs are unavailable, replace it rather than add」 branch does not
fire: this is an ADD, and the two lit controls prove the grep was
looking in the right place rather than returning a silent zero.

**P3, already present, ⛔ not a second row:**

```text
333: - 裸 REST `PATCH /pulls` 追加一个裸页脚并保留既有 session-URL 页脚,差恰 58 字节。
334: - 同路送无页脚正文存回恰一条(平台裸形)⇒ 该格处方是不送页脚,⛔ 不是不重送正文。
```

**P4:** `git ls-remote --heads origin` lists 1102 branches; a name grep
for `reading|rerun|re-run|channel|18025` hits only
`claude/issue-13326-platform-readings-family` (landed long ago),
`claude/issue-10979-...`, `claude/issue-7018-...` and this branch. Every
remote-tracking ref dated today (`2026-09-14`) was then checked directly
— `objectstack-ai#18074`, `objectstack-ai#18037`, `objectstack-ai#18085`, `objectstack-ai#18061`, `objectstack-ai#18055`, `objectstack-ai#18060`, `objectstack-ai#18047`,
`objectstack-ai#18069`, `objectstack-ai#17959` — and `git log origin/main..origin/BRANCH --
the-file` returns **0** commits for each. ⚠️ Scope declared: that scan
sees the refs this container has fetched, which is every in-flight seat
branch in this shift but not a branch pushed from elsewhere and never
fetched here.

## Verification counts for the seat's ACCEPT

One matter per line, deduplicated, re-wraps not counted:

| | count | what |
|:--|--:|:--|
| candidates | 2 | ① the re-run channel split (REST 403 / MCP 201); ②
the PR-body footer reading from objectstack-ai#18085's dev report |
| landed | 1 | ① as :285, 117 B |
| already present | 1 | ② — :333–:334 carry it verbatim; refused as a
duplicate rather than restated |
| refused | 0 | — |

⇒ landed 1 = the ceiling delta 1 = 453 → 454.

## Gates

`node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` at `9303a7b7e` (no paths passed — the script
derives its own change set: 2 paths vs merge base `ca7886047`, three-dot
semantics): **39 families**. Every one run in the foreground with its
exit code captured by redirect-then-capture BEFORE any pipe; **39 of 39
exit 0**.

Reconciliation, `--ran` with the exit code recorded per family:

```text
Run reconciliation — 39 derived, 39 run, 0 NOT-MEASURED, 0 UNRUN.
✓ dispatch-gates --ran: 39 derived famil(ies) accounted for — 39 run, 0 NOT-MEASURED
  (a DERIVED zero — all 39 recorded an exit code and none of them is 3).
```

One family needed a second pass: `pnpm --filter @objectstack/lint run
check:doc-formula-expressions` first answered **exit 3 — PREREQUISITE
NOT MET** (`@objectstack/lint` not built; nothing measured, ⛔ not a
finding). Built under the shared verify lock
(`OS_VERIFY_LOCK_SLOT=issue-18025`, `VERDICT command-exit 0 · held the
lock 1s · waited 0s`) and re-run: **exit 0**. The reconciliation above
is the post-fix record.

Named explicitly by the dispatch, and the roster families whose baseline
sits under a directory this diff is in — outside the derived 39, each
run and each exit code captured the same way:

```text
pnpm check:pm-governed-prose                          :: exit 0
node scripts/check-published-list-mirrors.mjs         :: exit 0
node scripts/check-published-list-mirrors.mjs --self-test :: exit 0
node scripts/check-skills-token-ratchet.mjs           :: exit 0
node scripts/check-skills-token-ratchet.mjs --self-test :: exit 0
```

Rule ⑤ — this diff edits a gate script, so that script's own suite is
owed beyond the derived families.
`scripts/pm/check-skill-line-ratchet.mjs` has no `*.test.ts`: `git grep
-l` over `*.test.ts` / `*.test.mts` / `*.test.mjs` / `*.spec.ts` returns
nothing, and its suite IS its `--self-test`, wired into `pnpm
check:pm-skill-ratchet` (in the 39, exit 0): `✓ check-skill-line-ratchet
self-test: 157 cases pass.` Its siblings that read the same module —
`check:ratchet-remedy-authority`, `check:pm-dispatch-gates` — are in the
39 and green.

Line-ratchet verdict after the edit:

```text
✓ check-skill-line-ratchet: .claude/skills/pm-dispatch/references/platform-readings.md is 454 lines (ceiling 454; headroom 0).
✓ check-skill-line-ratchet: cross-file move into .claude/skills/pm-dispatch/references/platform-readings.md:
  +11 (314→454, less 129 lines of ordinary ruled raise) against a net source decrease of 20 …
```

The move's own arithmetic is unchanged at +11 against −20, which is what
the `ruledRaises` record is for.

Control characters: `grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]'` over
both changed files is empty, and `pnpm check:nul-bytes` is in the 39 at
exit 0. Every added prose line is ≤ 120 B (the added row is 117 B; the
added ratchet comment lines are JS source, outside that cap).

⛔ Not measured here, by design: whole-farm CI. The derivation itself
names 52 artifact-roster families, 11 declared-wide families, 14
pending-changeset families and 1 path-scheduled CI job as outside the
derived 39 — CI owns those.

## Changeset

None owed, and `skip-changeset` is the declaration rather than a
shortcut. There is no `check-changeset-presence.mjs` in this tree; the
changeset gate is `changeset-check` in
`.github/workflows/pr-automation.yml`, and it reads **no path filter at
all** — its only two exemptions are the `skip-changeset` label (re-read
live, because the event payload's label snapshot is stale by
construction) and the `changeset-release/main` branch pushed by
`github-actions[bot]`. So a docs/tooling PR cannot be exempted by its
paths; it has to carry the label.

Nothing here publishes: both paths —
`.claude/skills/pm-dispatch/references/platform-readings.md` and
`scripts/pm/check-skill-line-ratchet.mjs` — are on the fast-track
non-publishing list (`.claude/**`, `scripts/pm/**`), inside no released
package's `files[]`.

## Acceptance notes

- noted, not filed (承接者: the skills seat reviewing this PR): the
register now carries THREE `/actions/jobs/{id}/...` readings across
:283–:291 — re-run tree reuse, the re-run channel split, and the logs
endpoint's proxy CONNECT 403. They are three separate matters and each
is one line, so no line merges; the observation is only that a future 段落
boundary there would read better if the endpoint family were contiguous.
Not a defect, not a contract breach, not an authoring trap ⇒ ⛔ no card.
- noted, not filed (承接者: 无): the card body's own budget line reads
「449/449 — pay inside the file」, measured when it was filed on
2026-09-13T12:28Z. The file was 453/453 by the time of dispatch. Nothing
to act on — the card's instruction was the ceiling DISCIPLINE, not the
number — and no PR or person will read that line again once this lands.

Clause-②: no — the diff widens no declared contract; it records a
reading and pays its line.

## 维护者速读(草稿)

**改了什么**:事实表 `platform-readings.md` 加一行(453 → 454),记一条读数:CI 失败 job
的重跑,席位自己的 REST 令牌回 403,而 MCP 的 `rerun_failed_jobs` 回 201 并把失败分片重新排队。顺带把
`check-skill-line-ratchet.mjs` 里这个文件的行数上限抬 1,按常设例外记一条
`ruledRaises`,引用裁决原话。

**为什么改**:此前的记录只说「重跑对席位是
403」,那是一个**通道**的事实,被当成了**席位能力**的事实。结果是:一条本来能执行的规则(CI
红了先做一次确认性重跑)被当作做不到,objectstack-ai#18010 那张卡直接把它写进了阻塞原因,还惊动维护者问「17990
你自己不能解决吗」。这一行把通道和能力分开,下次没人再为这件事找人点一下。

**风险与代价(含回滚)**:只动文档与一个门禁脚本的常量,不发布任何包,不改运行时行为。代价是事实表长 1
行(每个座位每次会话都要读它,这就是上限存在的理由);本次为此实测了密度——全文 427 对相邻条目没有一对能合并进 120
字节,所以省不出来。回滚 = revert 本 PR 的那一个 commit。

**席位意见**:(留空,定稿成评论)

**你要做的**:这是受管面(`.claude/**` + `scripts/pm/**`),按 Prime Directive objectstack-ai#14
只能由你手动合并 —— 席位不合、不进队列、不挂 auto-merge。你要确认的是两件事:① 这一行的读数属实(卡面 objectstack-ai#18025
有原始测量表);② 抬 1 行的上限动作走常设例外、不另开决策卡,是否照你的意思。

---
_Generated by [Claude
Code](https://claude.ai/code/session_01DAcomhvR9kKizeYgg89Vo8)_

Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 17, 2026
…g 5 unchanged (objectstack-ai#18125) (objectstack-ai#18128)

Fixes objectstack-ai#18125

## Ruling

The maintainer, in the skills seat's chat at 2026-09-14T03:45Z, verbatim
and untranslated: 「并发2 还是太慢了,默认恢复3吧」. It supersedes lock 3's default (PR
objectstack-ai#18072, commit `7ef05f997`, 「`batch` 默认 2」). The maintainer ceiling 5
stays. Ruling C (objectstack-ai#17971, 「C. approve 后不管后续改动都由席位落地:」) governs the
landing: this PR stays a DRAFT; the seat does the four-piece; nothing
here is readied, queued, armed or approved by the dev.

## Change — one line

`.claude/skills/pm-dispatch/SKILL.md` line 60, the `batch:N` row of the
〈入口与角色〉 argument table:

- before: `| batch:N | 同时在飞的 dev 上限 | 默认 2;N 的维护者天花板 5 |`
- after: `| batch:N | 同时在飞的 dev 上限 | 默认 3;N 的维护者天花板 5 |`

(The row is quoted with `N` standing in for the angle-bracket
placeholder the file uses, so the body survives the sanitizer; the file
itself is unchanged in that respect.)

Equal-line and byte-neutral: 812 lines before and after (ceiling 812,
`scripts/pm/check-skill-line-ratchet.mjs:320`), 73524 bytes before and
after, one insertion / one deletion in `git diff --stat ca78860
06c1015`. Nothing else moves: write-identity locks 1, 2, 4, 5,
`LOCK_DEPTH_HOLD`, the same-file serial rule and the ceiling 5 are
untouched. The seats' Routine prompts are the seats' own to update on
landing (card body).

## Premise readings (all against `origin/main` = `ca7886047`, worktree
created 2026-09-14T03:50:37Z)

- **P1 holds** (read 2026-09-14T03:51Z): SKILL.md line 60 read 默认 `2`;
`git grep -n` for the literal 默认 followed by a backticked 2, over
`.claude AGENTS.md CLAUDE.md`, returned exactly one hit,
`.claude/skills/pm-dispatch/SKILL.md:60`.
- **P2 holds** (read 2026-09-14T03:51Z): `references/core-rules.md` is
151 lines and states no batch DEFAULT. `grep -n batch` hits: line 11
「并行度以 `batch` 封顶,验证锁到达深度 ≥ `LOCK_DEPTH_HOLD`(2)即等;同批按构造文件面不相交。」 (the
verify-lock depth, a different number) and line 37 「插队标签可超 `batch`
立即派发,⛔ 不豁免同文件串行、深度等待与认领协议。」. `grep -n 默认` hits lines 7, 17, 18, 52, 77,
139, 140 — none is about `batch`. So there is no mirror line, and
core-rules.md is untouched (151/151); the card body's "core-rules mirror
line likewise" is superseded by the claim comment's reading, which this
grep confirms.
- **P3 holds** (read 2026-09-14T03:51Z): SKILL.md is 812 lines at
ceiling 812; `sed -n '733,754p' SKILL.md | md5sum` =
`3327d02c56f8a0eca88569dad2270f32`. Same two readings on HEAD
`06c10152b` after the edit: 812 lines, md5
`3327d02c56f8a0eca88569dad2270f32`.
- **P4 holds** (read 2026-09-14T03:52:44Z–03:53:16Z): `git ls-remote
--heads origin` listed 1106 heads; the 6 `claude/issue-18xxx` heads were
compared to `main` through REST `GET .../compare/main...BRANCH` and all
9 open PRs through REST `GET .../pulls/N/files` (page count 9, fewer
than 100, so the listing is complete): zero hits on
`pm-dispatch/SKILL.md` or `core-rules.md` in any of them. Scope
declared: open PRs plus this wave's `issue-18xxx` heads; the 62 older
keyword-matched heads (seat session branches `pm-dispatch-*`, old issue
branches) were listed but not compared.

## Gates (run on the working tree at HEAD `06c10152b`,
2026-09-14T03:55Z–04:01Z; every exit captured by redirect-then-`$?`)

Derived with `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` (no paths; change set from the merge base
`ca7886047`; the `--repo` assertion held): 16 commands — 9 matched by
path, 7 whole-tree. All 16 exit 0:

- `pnpm check:pm-skill-ratchet` — exit 0 (ratchet green;
widest-table-row pins and ceilings unchanged)
- `pnpm check:skill-frame-sync` — exit 0 (「the one declared copy of the
decision frame is internally coherent … 74 markdown files scanned for
undeclared copies」)
- `pnpm check:pm-governed-prose` — exit 0 (「2 instruction surface(s)
name all 5 registered governed surfaces … and claim no others」)
- `pnpm check:pm-skill-id-lint` — exit 0 (27 files clean)
- `pnpm check:pm-governed-merges` — exit 0
- `pnpm check:nul-bytes` — exit 0 (8643 text files, no raw control
bytes)
- `node scripts/pm/check-governed-queue-guard.mjs --self-test` — exit 0
(233 cases)
- `pnpm check:agent-test-spelling` — exit 0
- `node scripts/check-closing-keyword-parity.mjs` and its `--self-test`
— exit 0 / 0
- `node scripts/check-comment-mask-corpus.mjs` — exit 0 (6747 files, 0
disagree)
- `pnpm --filter @objectstack/lint run check:doc-formula-expressions` —
first run exit 3 「PREREQUISITE NOT MET — `@objectstack/formula` is not
built」 (not a measurement); after `pnpm exec turbo run build
--filter=@objectstack/formula --filter=@objectstack/lint` under
`os-verify-lock.sh` (「VERDICT command-exit 0 · held the lock 167s」),
rerun exit 0 (「22 record-scoped formula example(s) across 438 files …
judged clean」)
- `pnpm check:doc-authoring`, `pnpm check:driver-memory-census`, `pnpm
check:refd-timer-probe`, `pnpm check:watch-hint-literal` — exit 0 each

Reconciliation (`--ran` with `COMMAND :: exit CODE` lines,
2026-09-14T04:01:47Z): 「✓ 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).」

Tied family not on the derived list, run anyway: `node
scripts/check-skill-frame-freshness.mjs` (`--self-test` exit 0, scan
exit 0: 「the decision frame in this tree is current with origin/main」).
CI-measured only, not runnable here: `check-governed-queue-guard.mjs` on
the event payload.

Lint, narrowed and declared: `eslint --no-inline-config --format json
.claude/skills/pm-dispatch/SKILL.md` — exit 0, 1 file, 0 errors, 1
warning 「File ignored because no matching configuration was supplied」.
Population read from `eslint.config.mjs`: every `files:` block matches
only `**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}` (six blocks;
`COMMENT_SWALLOW_FILES` is the same glob), so a `.md` file is outside
the linted population. Invariance: the config never enables type-aware
linting (its own line 328: no `parserOptions.project`, no typed rules),
so a one-line edit to an unlinted file cannot move the verdict of any
linted file; the repo-wide `pnpm lint` is CI's run.

`dispatch-gates --tier` was read for the claim; its model line is not
reproduced here (model-free rule).

## Changeset

None owed. The `Check Changeset` job
(`.github/workflows/pr-automation.yml`, job `changeset-check`) reads
exactly two exemptions — the `skip-changeset` label and the changesets
release PR pinned by branch and author — and no path exemption, so the
label is the declaration. The diff publishes nothing: `.claude/**` is in
no released package's `files[]` (fast-lane class per the dev
definition). The label is applied with the additive `POST
.../issues/N/labels` and read back; the read-back is recorded in the
report comment on objectstack-ai#18125.

## 维护者速读(草稿)

**改了什么**:PM 派发技能 `SKILL.md` 的 `batch` 参数默认值从 2 改回 3;上限 5 不变;只此一行。

**为什么改**:维护者裁决「并发2 还是太慢了,默认恢复3吧」;lock 3 落地的默认 2 由此被取代。`core-rules.md`
本就没写默认值,故无需同改。

**风险与代价(含回滚)**:并发默认回到 3 意味着同一时刻多一个 dev
在飞,共享容器的验证锁排队会略长;其余四把写身份锁、验证锁深度等待、同文件串行规则均不动。回滚 = 把该行的 `3` 改回
`2`,一行、零副作用。

**席位意见**:(留空,席位定稿成评论)

**你要做的**:确认后由授权账号 approve,席位按裁决 C 落地;各席位自行更新自己的 Routine 提示词。

## Acceptance notes

- Dispatch vs dev definition: the dispatch asked for a two-line 维护者速读;
the dev definition sets five paragraphs, and the definition wins on
conflict, so the five-paragraph form is used with the two-line business
content inside it. Noted, no card.
- noted, not filed: the card body says the core-rules mirror line
changes "likewise"; the tree says there is no such line (P2 above). The
claim comment already carries the correction. 承接者:无 (nothing to land).
- noted, not filed: the first `check:doc-formula-expressions` run exited
3 on a fresh worktree because `@objectstack/formula` and
`@objectstack/lint` are not built by `pnpm install`; the gate's own text
says so and prescribes the build. Behaviour by design (Absence must be
loud), not a defect. 承接者:无.
- Out-of-scope findings of the three filing classes: none.
- Clause-②: no

---
_Generated by [Claude
Code](https://claude.ai/code/session_01DAcomhvR9kKizeYgg89Vo8)_

Co-authored-by: claude[bot] <claude[bot]@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 17, 2026
…— REST-only content writes, the ACCEPT refuses MCP writes, a stale shared checkout re-seats (objectstack-ai#18205) (objectstack-ai#18216)

Fixes objectstack-ai#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 objectstack-ai#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
(objectstack-ai#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.

| file | line | after | B |
|---|---|---|---|
| `.claude/agents/os-dev.md` | :51 | GitHub 写一律走 REST 代理(`curl` 带
`GITHUB_TOKEN`);归属 = 文本里的 session ID,非 `user.login`。 | 116 |
| `.claude/agents/os-dev.md` | :53 | ⛔ 不用 MCP GitHub
写工具;令牌按会话定:installation ⇒ `claude[bot]`,user-to-server ⇒ 用户。 | 115 |
| `.claude/agents/os-dev.md` | :369 | `"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) | 109 |
| `SKILL.md` | :91 (new) | 同读 harness 载入面
`.claude/{settings.json,agents/*.md,hooks/*}` 的最新触碰是否已在共享检出 HEAD。 | 120
|
| `SKILL.md` | :92 (new) | 否 ⇒ 收班、换新会话再派,⛔ 不推进共享检出;读数走
`scripts/pm/check-harness-current.mjs`。 | 115 |
| `SKILL.md` | :97 | 用户账号仅三用:assignee、授权批准、维护者亲手;批准账号永不跑席位或作其关联用户。 | 117
|
| `SKILL.md` | :98 | 内容写只走 REST 代理,⛔ 无 MCP 写;`user.login` 记令牌不记席位,归属 =
文本里的 session ID。 | 116 |
| `SKILL.md` | :195 | 新仓登记是一张清单:座位贴、标签、类别归属、门禁盘点、写身份锁移植(deny + hooks)。 |
116 |
| `SKILL.md` | :537 (merged) | 终报要求随派发词带一句:只收机器可核字段(gates / line_budget
/ deviations / files_changed)。 | 113 |
| `SKILL.md` | :538 (new) | 派发令恒带 `Writes:` 行:只走 REST
代理、写预算(端点清单)、`mcp_calls` 计数,dev 两数都报。 | 117 |
| `SKILL.md` | :556 (merged) | `mode:cloud` 只保留给 L/XL、活过 PM
会话的工作、浏览器/dogfood 验证;build 重的 M 卡逐卡判。 | 118 |
| `SKILL.md` | :567 (merged) | 标记两种拼写等效(HTML 注释形、首行 `os-dev-report`);⛔
永不把没收到失败通知读作还在跑。 | 120 |
| `SKILL.md` | :602 (new) | `mcp_calls` 点名写工具(`settings.json` deny 清单 +
`update_pull_request`)⇒ 拒收,⛔ 不带注放行。 | 115 |
| `SKILL.md` | :775 (merged) | 终报 JSON 的权威形状住 `.claude/agents/os-dev.md`
终报消息节,⛔ 本文不抄第二份。 | 104 |
| `references/core-rules.md` | :25 | 用户账号仅三用:assignee、授权批准、维护者亲手;写只走
REST 代理,署名随令牌非席位。 | 115 |
| `references/platform-readings.md` | :129 | 容器 curl 的 REST
通道令牌按会话定:installation(`claude[bot]`)或 user-to-server(用户),core 15,000/时。
| 120 |
| `references/rest-channel.md` | :54 | 直合仓 `PUT
.../pulls/{n}/merge`;actor 记通道令牌:REST 按会话为 `claude[bot]` 或用户,MCP 恒用户。 |
118 |

`SKILL.md` and `references/` are `.claude/skills/pm-dispatch/`. The two
os-dev.md rule lines carry their 3-space list indent inside the count.

- (a) facts and invariants: content writes go only through the REST
proxy; ⛔ no MCP content write; `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. os-dev.md :51/:53,
SKILL.md :97–:98, core-rules :25, platform-readings :129, rest-channel
:54.
- (b) dispatch order and acceptance: SKILL.md :538 — every dispatch
order carries a `Writes:` line (REST proxy only, the write budget as an
endpoint list, `mcp_calls` counted, the dev reports both numbers);
SKILL.md :602 — a report whose `mcp_calls` names a write tool (the
`settings.json` deny list plus `update_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.
- (c) propagation: SKILL.md :91–:92 beside the three-charter-file
reading — at fire time the seat also reads the latest `origin/main`
touch 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 is
`scripts/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).
- (d) fleet: SKILL.md :195 — the new-repo registration checklist gains
the write-identity locks port (deny + hooks). The four repos without a
port today: `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 objectstack-ai#7623 until then).
- (e) the managed-settings fact row: not landed in platform-readings
(454/454, no payable pair in that file without deleting a ruled clause);
recorded under Acceptance notes below with the doc sentences verbatim.

## Premise readings (falsified against the tree before writing; all UTC)

- P1 (16:12Z, base `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.
- P2 (16:11:37Z): `git -C /home/user/objectstack rev-parse HEAD` =
`84e6b05b6d295f1c744d236921300f447cf7791e`, `log -1 --format=%cI` =
`2026-09-13T06:14:23+00:00`; `merge-base --is-ancestor 7ef05f9 HEAD`
exit 1. Control legs for the negative (shallow checkout, `rev-list
--count HEAD` = 4024): `is-ancestor 84e6b05 HEAD` exit 0 and, twelve
commits deep, `is-ancestor d88a47d HEAD` (committed
2026-09-12T22:39:41Z) exit 0 at 16:12:17Z; the negative is also
date-decided — `7ef05f9973` was 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 no `permissions.deny` key at all (`grep -c
'"deny"'` = 0 against 1 on `origin/main`), so in this session no deny
list was ever loaded, not a pre-lock-1 one.
- P3 (16:12:09Z, `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 (frontmatter `model:` 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. Control `git grep -c '收班简报'` on SKILL.md = 5. Holds.
- P4 (16:12:09Z): `派发令` 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 the `Writes:` mandate lands as a rule line (SKILL.md
:538). Holds.
- P5: os-dev.md :369–:370 are the `mcp_calls` / `api_writes` report
fields; :57 already orders both counts. Holds; :369 rewritten, :370
untouched.
- P6 (16:17:26Z on the base): `check-skill-line-ratchet` exit 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-sync` exit 0; `git ls-remote --heads origin` matched
only this branch for issue-17497/18205/18181/18158. Holds.
- P7: `scripts/pm/dispatch-gates.mjs` is untouched; objectstack-ai#14290's
`Restart-touch` surface is left alone; the seat-side check is the
sibling file `scripts/pm/check-harness-current.mjs`.

## The mechanism, measured in this session (16:25Z)

- The os-dev.md this dev runs under is the shared checkout's copy: its
line 「通道先探后选…」 is in `git show 84e6b05:.claude/agents/os-dev.md` (1
hit) and absent on `origin/main` (0); `api_writes` is the inverse (0 in
the old copy, 2 on `origin/main`); control `Worktree-first` 1 / 1.
- `node scripts/pm/check-harness-current.mjs` from this worktree (shared
checkout resolved through `--git-common-dir`): exit 1 —
`.claude/settings.json` and `.claude/agents/*.md` latest touch
`7ef05f9973` NOT in shared HEAD `84e6b05b6d`, `.claude/hooks/*` latest
touch `d79f249915` (2026-09-12T09:41:28Z) in HEAD. `--shared
/home/user/objectstack-issue-18205`: exit 0 CURRENT at `af3add1601`.
`--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 as `CMD :: exit N`:

- 39 exit 0 on the first pass, including `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-expressions`
first read exit 3 = PREREQUISITE NOT MET (compiled
`@objectstack/formula` and `@objectstack/lint` absent 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."
- Reconciliation: `dispatch-gates --ran ran.list --repo
objectstack-ai/objectstack` at 16:37:21Z on `7103d0b09f`: "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).
- Ratchet on the final head: every one of the five files at its ceiling,
headroom 0, pins unchanged (SKILL.md widest table row 342). First pass
on the working tree had caught os-dev.md :51 at 122 B (the list indent
was outside the draft measurement); fixed in the second commit to 116 B.
- Lint, narrowed and measured: the checked population is eslint's own
config (`files: ['**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}']`, which covers
`scripts/pm/*.mjs`); the only non-markdown file in the diff is
`scripts/pm/check-harness-current.mjs`; `eslint --no-inline-config
--format json` on it: 1 file, 0 errors, 0 warnings (exit 0); invariance:
`eslint.config.mjs` states it "never enables type-aware linting (no
`parserOptions.project`, no typed `@typescript-eslint` rules) for ANY
file", so a one-file addition cannot move any untouched file's verdict.
The repo-wide `pnpm lint` is CI's run.
- Not run here, declared to CI: nothing else — the diff touches no
package, so there is no ① build closure or ② package test suite;
`.claude/**` and `scripts/pm/**` publish nothing, so `skip-changeset`
applies (fast lane: `.claude/**` · `scripts/pm/**`). The seat writes the
label; this container does not.

## Density paid inside each file

- SKILL.md (four new lines, four merges): 终报要求 + 机器可核字段 → one line
(drops only the implied 「⛔ 复述 PR body 叙事」); `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
「字段与拼写以那里为准」).
- os-dev.md :53 drops 「⛔ 不枚举板面、不宽词搜」 — :50 (「⛔ 不扫 open issues、不拉板」) and
:54 (single-card reads only) already carry it.
- core-rules :25 (the compressed mirror) now carries the channel +
identity reading; the approval-account clause could not fit beside it in
120 B and stays where it is authoritative, SKILL.md :97.

## 维护者速读(草稿)

- 改了什么:① 署名跟令牌走、不跟账号走 —— GitHub 上写回读到的 `user.login` 只说明这条会话的令牌是 App
的还是用户的,不说明是谁在写;身份看文本里的 session ID。② dev 报告里出现任何 MCP 写工具即拒收,不带注放行。③
harness 读的文件(`settings.json`、agents、hooks)在 main
上动了而共享检出没跟上时,席位收班、换新会话再派,永不原地推进共享检出。
- 为什么改:锁 1 落地后 objectui 仍出现一条经 MCP 建的
PR,原因是运行中的会话只在克隆那一刻读一次这些文件;同时章程里「署名恒 `claude[bot]`」被四个会话的实测证伪。
- 风险与代价(含回滚):纯规则文本 + 一个只读 git 的席位脚本,零 CI 接线;回滚即 revert 这一个 PR。代价是每次开轮多一次
git 读数,与一次可能的换会话。
- 席位意见:(留空)
- 你要做的:一个动作 —— 批准这份草稿,席位落地。

## Acceptance notes

- Item (e), recorded here instead of a fact row:
code.claude.com/docs/en/settings 「Settings in cloud sessions」 states,
verbatim: "**Shared project settings** (`.claude/settings.json`): read,
because the file is part of the clone." / "**User and project local
settings** (`~/.claude/settings.json` and
`.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; a `managed-settings.json`
file 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).
- The card's "1 `mcp__github__*` entry (pre-lock-1)" in the shared
checkout's settings is a hook matcher, not a deny entry; the shared file
has no `permissions.deny` at 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 after `7ef05f9973` measures it by tool-table absence.
Bearer: the skills seat's next fresh session.
- `mcp__github__update_pull_request` edits 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.json` is
outside this card).
- The script has no `--self-test` on purpose: it is not CI-wired
(`check-self-test-wired` populates from workflows), it exports nothing
(`check:entry-guard` rule 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.
- `objectstack-ai#18181 remains open` (os-dev.md :287 label write is not addressed
here); `objectstack-ai#18158 remains open` (the identity reading itself); the objectui
port (PR objectstack-ai#9448) is untouched.

Clause-②: no

---
_Generated by [Claude
Code](https://claude.ai/code/session_01DAcomhvR9kKizeYgg89Vo8)_

---------

Co-authored-by: Claude <noreply@anthropic.com>
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/s skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

3 participants