Skip to content

docs(agents): 一次派发跑出来的 PR 停在 draft —— 给自行合并条款加第二条例外 - #10213

Merged
hotlong merged 1 commit into
mainfrom
claude/issue-9992-agents-non-governed-pr-lands-by-the-seat
Sep 23, 2026
Merged

hotlong merged 1 commit into
mainfrom
claude/issue-9992-agents-non-governed-pr-lands-by-the-seat

Conversation

@huangyiirene

@huangyiirene huangyiirene commented Sep 22, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #9992

维护者速读(草稿)

改了什么
AGENTS.md 只改一行(第 326 行,「多 agent 协作纪律」节里那条「CI 全绿即自行合并」)。原来它只留了一条例外(diff 命中受管面);现在留两条,新增的第二条是:由一次派发跑出来的 PR(卡上挂着 Claim: 评论、由被派发的 os-dev 一类 agent 实施的那种)不论受管面判定如何都停在 draft,等 CI 收敛、翻 ready、入队、事后重挂 auto-merge 全是派发席的动作。同时把「没有派发、卡上没有 Claim: 的自发改动仍按本条自行入队」写明,免得这条例外被读成把自主改动也一并冻住。净增 0 行,文件仍是 589 行。

为什么改
同一个 agent 在两份规程里收到相反的指令:objectui 的 AGENTS.md 让绿了的非受管 PR 作者自己落地,而每个 os-dev 运行都读的派发契约(兄弟仓 .claude/agents/os-dev.md)说它的终态交付物就是「draft PR + 报告」、等 CI 不是它的事。受管面守卫解不开这个结:它答 NOT GOVERNED 只解除批准要求,而派发契约管的是谁在动手,不是改了哪些文件。这不是假想 —— objectui#9475 → PR #9962 上一个被派发的 dev 已经在这里停住并把它当治理问题上报。派发循环的冲突规则是「两条细则冲突 ⇒ 按更严的一条行动并立卡;⛔ 不当场改文本了结」,更严的一条是派发契约(停在 draft),所以移动的是 AGENTS.md 这句自行合并。

风险与代价(含回滚)
代价:被派发的 PR 从此多等一个人,落地延迟由派发席的巡检节奏决定。风险面很小 —— 这条规则没有任何机械执行者(受管面守卫不读 Claim:),它的全部效力在于读到的人照做;所以写错的后果是「有人照旧自己合并」,不是构建红。回滚就是 git revert 这一个 commit,文件回到原来的一行,不触及任何脚本、工作流或标签。另一个方向的代价请一并权衡:如果维护者更想要方案 2(改派发契约,让 dev 在非受管 diff 上自行落地),那就得把 PM 循环的复核步骤一起重写,因为复核人现在就是落地人 —— 本 PR ⛔ 没有走这条路,原卡把它记为「会删掉循环赖以存在的复核步骤」。

席位意见 —— 技能席 1(session_01Wnstp2kTth7sGXfr8fXypc)已按 GitHub 与拉取的 head 46680d1 复核:只改 :326 一行,方向按卡引的冲突规则「按更严的一条」取 os-dev 契约(dev 停在 draft、派发席落地),例外 ② 以「谁在动手」为键、不被受管面判定解除,无派发的自发改动仍按原句自行入队;跨仓契约按内容锚引用而非 path:line(本仓禁令,引用门禁读 0 新增)。objectui 自带的五个门禁本席复跑均 exit 0,check-governed-queue-guard --test AGENTS.md 读 GOVERNED。复核记录 PASS 5773828944(席内 fable)。与 PR #10212 同文件不同区域,后落者合并。建议合并;「受管面」一节尚未回指例外 ②,留给下一次动该节的 PR。

你要做的
读上面「两条条款」一节的两段原文,确认第二条例外的措辞是你要的治理语义;认可就合并这个 PR(本仓 AGENTS.md 是受管面,本 PR 停在 draft,由你落地)。如果你更倾向方案 2,请直接说,这个 PR 撤掉、改去兄弟仓动派发契约。

两条条款,原文与出处

A —— objectui AGENTS.md 第 326 行(改动前,0cf2d66)

  • CI 全绿即自行合并,不必等维护者确认(授权语义没变,变的只是动作;⛔ 例外:diff 命中受管面的 PR 不适用本条 —— 见下方「受管面」,那类 PR 停在 draft 等一条获授权的批准,再由认领席落地)—— 修改完成后只提交你任务改动的文件(逐路径 git add PATH,绝不 git add -A 扫入无关 diff),开 draft PR;等远端 CI 全绿后:

紧随其后的代码块(第 328–332 行)就是那两行入队命令:gh pr ready 与 gh pr merge --squash --auto --delete-branch。

⚠️ 行号漂移,已复核:原卡把这条记在 :330(树在 c255b38,当时 AGENTS.md 593 行)。在本 PR 的基点 0cf2d66 上文件是 589 行,同一条款在 第 326 行。原卡的 :330 在今天的树上指向的是那两行入队命令里的 gh pr merge 那一行。

B —— objectstack .claude/agents/os-dev.md(只读,⛔ 本 PR 不改)

在 372931e 上,os-dev.md 共 403 行。

第 16 行:

交付物两件:该 issue 实现完毕并推成 draft PR,加下方的 JSON 报告。

第 311 行:

报告在本地验证走完时交付,CI 收敛等待归 PM 不归你:⛔ 不为等 CI 结论推迟报告。

第 312 行(同族,一并引以说明「等 CI」到底归谁):

门禁状态如实记录,in_progress 是诚实值;PR 开出后 ⛔ 永不 sleep、定时等待或空转轮询 CI。

⚠️ 行号漂移,已复核:原卡把第二条记在 :310。在 372931e 上 :310 是那条 objectui skip-changeset 的规则,被引的句子在 第 311 行,差一行。

它们为什么真的相撞

A 只挖了一个洞:diff 命中受管面。一个被派发的 os-dev 在非受管文件面上开的 PR 因此整个落在 A 的适用范围里 —— A 让它在 CI 绿后翻 ready 并挂 auto-merge,而 B 告诉同一个 agent 等 CI 根本不是它的活、它的终态交付物是 draft PR 加一份报告。受管面守卫解不开:node scripts/check-governed-queue-guard.mjs --test 答 NOT GOVERNED 解除的是 A 自己那条例外要的批准,解除不了 B,因为 B 问的是谁在动手,A 问的是改了哪些文件。

改了哪一行

  • 文件:AGENTS.md;改动行:第 326 行(改动前后都是这一行);净增 0 行(589 → 589);git diff --numstat 读数 1 1 AGENTS.md。
  • 改动前(原文见上面 A 节的引文)。
  • 改动后:
  • CI 全绿即自行合并,不必等维护者确认(授权语义没变,变的只是动作;⛔ 两条例外,命中任一就只走到 draft 为止 —— 下面那两行入队命令都不归你跑:① diff 命中受管面的 PR —— 见下方「受管面」,那类 PR 停在 draft 等一条获授权的批准,再由认领席落地;② 一次派发跑出来的 PR —— 卡上挂着 Claim: 评论、由被派发的 os-dev 一类 agent 实施的那种,不论受管面判定如何都停在 draft:被派发 dev 的终态交付物就是「一个 draft PR + 一份报告」,等 CI 收敛、翻 ready、入队、以及事后重挂 auto-merge 全都是派发席的动作 —— 因为 PM loop 的复核步骤要求落地的是第二个 actor;兄弟仓的派发契约 ../objectstack/.claude/agents/os-dev.md 也正是这么写的,逐字照录:「交付物两件:该 issue 实现完毕并推成 draft PR,加下方的 JSON 报告。」「报告在本地验证走完时交付,CI 收敛等待归 PM 不归你」。⚠️ ② 不是 ① 的推论,也不被受管面判定解除:node scripts/check-governed-queue-guard.mjs --test 答 NOT GOVERNED 只解除 ① 要的那条批准 —— ① 问的是改了哪些文件,② 问的是谁在动手。没有派发、卡上没有 Claim: 的自发改动不在 ② 内,仍按本条自行入队)—— 修改完成后只提交你任务改动的文件(逐路径 git add PATH,绝不 git add -A 扫入无关 diff),开 draft PR;等远端 CI 全绿后:

两处刻意的写法,请一并复核:

  1. 引兄弟仓那份契约用的是内容锚,不是行号。 本仓对跨文件 路径:行号 引用的约定(AGENTS.md 的「相信任何『不存在』」一族与 scripts/check-new-cross-file-line-citations.mjs)把跨文件行址列为禁写形态,所以落进 AGENTS.md 的是两句原话,行号只写在本 PR 正文里(正文不在那道门的受检总体里)。门禁读数:本分支新增跨文件行址 0 条。
  2. 例外 ② 的判据是「谁在动手」,不是路径。 措辞刻意不去引用任何脚本的判定,因为没有脚本读得出 Claim: —— 这条的执行力就在于读到的人照做,和本仓受管面那节自己承认的情形同类。

门禁(本地,全部在最终 commit 46680d1 上重跑,退出码在任何管道之前捕获)

门禁 命令 退出码 判定行
Control Bytes node scripts/check-control-bytes.mjs 0 check-control-bytes: OK (scanned 8216 tracked text file(s); skipped 85 binary).
Changeset Declaration node scripts/check-changeset-presence.mjs 0 No source or published contract of a released package changed in this range, so no changeset is owed.
Cross-file line citations node scripts/check-new-cross-file-line-citations.mjs 0 VERDICT new-cross-file-line-citations: 0 new citation(s), enforcement report-only -> exit 0(6/6 synthetic controls PASS)
Doc links node scripts/check-doc-links.mjs 0 Links are valid across 17 scan roots.
Shell escape residue node scripts/check-shell-escape-residue.mjs 0 check-shell-escape-residue: OK (5/5 root(s) resolved -- AGENTS.md: 1 file(s), 15 fence(s); ...)
Governed Surface Queue Guard node scripts/check-governed-queue-guard.mjs --test AGENTS.md 3 ⛔ GOVERNED — 1 of 1 path(s) are on a governed surface —— 这是期望值,不是失败:它就是本 PR 停在 draft、由维护者落地的依据。

本地收窄,已申报并测量。 这是一个只改 markdown 的 diff。ci.yml 的 Decide whether this change needs a full run 步骤把 :(exclude,glob)**/*.md 列在排除集里(lint.yml 有一个孪生步骤),所以在这样的 PR 上两条工作流都会启动、报告、并跳过每一个昂贵步骤;pnpm lint / pnpm type-check / pnpm test 的受检总体里没有任何一个被本 diff 触动的文件(改动的是 AGENTS.md,不是任何包的源码,changeset presence 门禁独立测到「0 个已发布包的源码或已发布契约发生变化」)。上表是本地欠的那一半,全农场归 CI。

changeset:零写,按门禁读数而非标签。 check-changeset-presence.mjs 自己印的判定是 nothing owed,所以既不加空 frontmatter changeset、也不碰 skip-changeset 标签(本仓该标签零读者)。本仓路径标签归 labeler.yml,本 PR ⛔ 未写任何标签。

并发与后来的落地人

本分支从 0cf2d66(= 认领时的 origin/main)切出,开 PR 前重新 fetch,origin/main 未动,无需 merge。触到的行只有 AGENTS.md:326 这一行。objectui#9563 在同一批认领里改的是 AGENTS.md 的 GitHub 正文改写条款(:470–:480 一带),区域不同;两者落地顺序任意,后落地的那个合一次即可。认领时读过的 5 个 open PR 的文件清单里没有一个触到 AGENTS.md。

Acceptance notes

  • 本仓 AGENTS.md 里不存在「人类贡献者自己的 PR」这一单独情形 —— 全文的收件人是 agent(grep -n '人类贡献者|human contributor|非 agent' 零命中)。所以派发词里「若 AGENTS.md 也处理人类贡献者的 PR 就保留该情形」这一条不适用;取而代之,被保留并写明的是「没有派发、没有 Claim: 的自发改动」这一残余情形。
  • 第 335 行那条「auto-merge 会在合并冲突和 draft 窗口里被静默丢弃 —— 事后必须复查并重挂」未改,也不需要改:它的收件人是挂上 auto-merge 的那个人,在例外 ② 之下那就是派发席,而新措辞已经把「事后重挂 auto-merge」明确划给派发席。
  • 第 494 行那节「受管面」未改(认领申报的文件面只含这条落地流程条款)。若维护者希望那一节也回指例外 ②,那是另一个 PR 的事;⛔ 本 PR 不扩面。

Generated by Claude Code

…e self-land clause

objectui AGENTS.md's landing-flow clause told the author of a green,
non-governed PR to flip ready and arm auto-merge, with exactly one
carve-out (diff hits a governed surface). A PR opened by a dispatched
os-dev over a non-governed file set therefore sat squarely inside it,
while the dispatch contract in the sibling repo tells that same agent
its terminal deliverable is a draft PR plus a report and that waiting
for CI is not its job. Same actor, opposite instructions.

Per the dispatch loop's conflict rule — act on the stricter clause and
file a card, never settle it by editing the text on the spot — the
stricter side is the dispatch contract, so the self-land sentence is
the one that moves. It now carries a second carve-out, stated as a
question about WHO is acting rather than about which files changed:
a PR carrying a Claim comment, implemented by a dispatched agent,
stops at draft whatever the governed-surface verdict says, and the
dispatching seat converges CI, flips ready and enqueues.

The residual case is kept explicit rather than deleted: an unsolicited
change with no dispatch and no Claim comment still self-enqueues under
the main sentence.

Net zero lines: one line rewritten in place, AGENTS.md stays 589 lines.
The os-dev contract is quoted by content anchor, not by line address —
this repository's cross-file line-citation convention.

Claude-Session: https://claude.ai/code/session_01Wnstp2kTth7sGXfr8fXypc
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added the documentation Improvements or additions to documentation label Sep 22, 2026
@github-actions

Copy link
Copy Markdown
Contributor

changeset-claim-re-read

⚠️ 1 address(es) in this pull request's own prose name a tree it replaced

Each was read from a tree this change itself moves, so a reader who follows it lands somewhere else. ⛔ Nothing here blocks and nothing here says the sentence is false — the question asked is arithmetic: does this diff move the line that number points at?

  • in this body, AGENTS.md:326 — this change rewrites AGENTS.md:326

    触到的行只有 AGENTS.md:326 这一行。

⛔ The repair is not to correct the number. Changing :246 to :274 is true today and born false again on the next insertion — objectui#9509 states that before anything else. Bind the number to the tree it was read from (`:246` at `b8a006883d`, `:274` at this head), which cannot re-stale because each number names its own tree; or state a rule instead of a coordinate, the way objectui#9495 replaced a file count with "every file in git diff --name-only against the merge base".

⚠️ 5 pending changeset(s) describe a file this change touches

Their bodies publish verbatim into the CHANGELOG at the next release, so this is a request to re-read them against your diff — addressed here because you are the one seat that can answer it without re-deriving anything.

⛔ Nothing here blocks, and nothing here is a verdict on your change. This gate exits 0, is not a required context, and judges name resolution, never meaning: it asked whether a pending body names a file you touched. "Is this sentence still true?" is the one question it will not answer, and the one you are being asked to answer.

.changeset/10106-settled-schema-equal-payload.md

  • names AGENTS.md → AGENTS.md — edited by this change

    The hook refetches getObjectSchema whenever the adapter identity changes, and every consumer its doc comment instructs — ObjectGantt, ObjectCalendar, ObjectView, ObjectTimeline, ObjectGallery — keys its record fetch on the returned def, which is what AGENTS.md §5 commandment [WIP] Enhance every detail of the designer #10 tells a consumer to do (key on the payload, never on a memo identity). The producer half of that same sentence is what this fixes: a byte-identical answer arriving as a fresh object re-fired every one of those fetches, and the duplicate query carried the same $expand set as the one before it. Measured first-hand on ObjectGantt with an instrumented adapter, with a control swap in the same command; the counts, and the control that makes them a discrimination rather than a counting artefact, are asserted by packages/react/src/hooks/__tests__/useSettledSchema.equalPayload-10106.test.tsx (objectui#10106).

.changeset/6357-basechema-bind-declaration.md

  • names AGENTS.md → AGENTS.md — edited by this change

    bind was read by ten production sites and declared by no schema shape. It resolved as any through BaseSchema's index signature and rode .passthrough() on the validator, while three separate documents taught it as an authorable key of every node: this repo's own AGENTS.md §4 ("Every node in the UI tree follows this shape (@object-ui/types)"), the published agent-facing skills/objectui/rules/protocol.md ("Every UI component node MUST follow this shape"), and content/docs/fields/grid.mdx. So the agent-facing protocol told authors to write a key the published types did not know existed.

.changeset/6505-predicate-valued-gate-rules.md

  • names AGENTS.md → AGENTS.md — edited by this change

    The accept set widens to what the protocol already declares and the runtime already accepts, not beyond it. AGENTS.md §4 declares both keys as expressions, SchemaRenderer evaluates them through hasDeclaredPredicate + evaluateCondition, @objectstack/spec normalizes every authored predicate into a { dialect, source } envelope, and the objectui#3862 / objectui#3955 rulings are entirely about which expression spellings count as declared. This table was the one place in the repo that disagreed, so this restores declared = enforced rather than changing a contract.

.changeset/7122-spec-17-3-0-parity-reconciliation.md

  • names AGENTS.md → AGENTS.md — edited by this change

    Two breaking consequences for a consumer that names the type directly. (1) It no longer declares editMode; the key is now carried by the spec's ServiceObject, so ObjectSchemaMetadata still has it, but code written against the extension type ALONE loses it. (2) interface → type alias also ends declaration merging: a consumer that reopened declare module '@object-ui/types' { interface ObjectSchemaClientExtensions { … } } to add its own client-side member no longer compiles, because an alias cannot be reopened. minor rather than major per AGENTS.md's version-alignment rule — objectui's own breaking changes are graded minor with the semantics stated in the body, since any major in the fixed group would push all 39 packages off @objectstack's major.

.changeset/7714-lookup-draft-stays-client-side.md

  • names AGENTS.md → AGENTS.md — edited by this change

    minor rather than major per AGENTS.md: objectui's major tracks @objectstack's, so objectui's own breaking changes ship as minor with the breaking semantics stated.

Read the paragraph, not the line: both false halves of the objectui#8617 claim sat in one paragraph, and correcting either alone would have left it asserting the same wrong thing.

If a claim did go false, correct the body. That is precedented and prose-only, frontmatter untouched; check-changeset-overwrite.mjs will report the correction as its own case 2 ("correcting a declaration on purpose … legitimate"), which is the intended shape — one gate asks for the read, the other records the write.

Not covered, stated so nobody reads this as more: a born-false claim that spells no line address at all (objectui#9495 coordinated one by ORDINAL — "a grep finds that member first" — and deciding that means reading what the sentence means), a claim spelled as a symbol or a package rather than a backticked file name, and a file named ambiguously.

Compared the checked-out tree with 0cf2d6644 (merge-base with origin/main): 1 file(s) changed outside .changeset/, read against 1262 pending declaration(s) that publish a body (1829 pending in total). · run

Copy link
Copy Markdown
Collaborator Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 46680d1fb6b4db76b8a5edf747710662b516bb26

① Derived judgments

Rendered in-seat by the skills seat 1 session at the served tier the maintainer named on this date (the fable tier word; ruling record objectstack#7623 comment 5771798588). The contract is the pm-dispatch conflict rule the card cites — 「两条细则冲突 ⇒ 按更严的一条行动并立卡;⛔ 不当场改文本了结」 — with the os-dev dispatch contract as the stricter clause.

  • (a) One line moves — right. git diff origin/main 46680d1 -- AGENTS.md is +1 / −1 at :326, the landing-flow clause under 「多 agent 协作纪律」; 589 lines before and after. Nothing else in the file changes.
  • (b) The direction is the stricter clause — right. The old sentence carved out one class from self-landing (a diff hitting a governed surface), so a dispatched os-dev's draft over a NON-governed file set was told to flip ready and arm auto-merge, against objectstack .claude/agents/os-dev.md :16 「交付物两件:该 issue 实现完毕并推成 draft PR,加下方的 JSON 报告。」 and :311 「报告在本地验证走完时交付,CI 收敛等待归 PM 不归你」 (read by this seat at 372931e). The rewrite adds exception ②, keyed on WHO acts rather than which files changed: a PR carrying a Claim: comment, implemented by a dispatched os-dev-class agent, stops at draft whatever the governed-surface verdict says; ready, enqueue and auto-merge are the dispatching seat's acts, and 「PM loop 的复核步骤要求落地的是第二个 actor」 states why. That is the os-dev contract, and references/landing-operations.md:64 「auto-merge 由 PM 挂,dev 永不碰」 agrees.
  • (c) The residual case is explicit — right. 「没有派发、卡上没有 Claim: 的自发改动不在 ② 内,仍按本条自行入队」 — the main sentence keeps its subject; ② is stated as not a corollary of ① and not lifted by a NOT GOVERNED verdict (「① 问的是改了哪些文件,② 问的是谁在动手」). No human-contributor case exists in the file (the dev grepped; the whole document addresses an agent), so the dispatch's conditional did not apply — correctly reported rather than invented.
  • (d) The citation form — right. The os-dev contract is quoted INSIDE AGENTS.md by content anchor with the sibling path, never by path:line (objectui's ban at AGENTS.md:108; check-new-cross-file-line-citations.mjs reads 0 new). Line numbers live in the PR body only. The card's own line addresses had drifted (:330 → :326; os-dev.md :310 → :311) and were re-measured, not trusted.
  • (e) Gates — re-run by this seat on its own worktree at this head: check-control-bytes OK, check-changeset-presence (no changeset owed), check-new-cross-file-line-citations 0, check-doc-links valid, check-shell-escape-residue OK; check-governed-queue-guard.mjs --test AGENTS.md exit 3 — GOVERNED, the Tier H basis. No objectstack edit was made (read-only quotes).

② Semver level

None — a governed instruction file; no package source; no changeset owed; no label written (documentation is the labeler's).

③ Boundary flags

  • pnpm install not run — declared narrowing; the applicable gates are dependency-free. Accepted.
  • The 受管面 section (:494 onward) still describes only the governed-surface reason for stopping at draft and does not point back at exception ② — noted by the dev as polish for the next PR on that section; ⛔ not widened here. Accepted.
  • objectui carries no os-dev.md of its own, so a reader of this clause needs the sibling checkout to open the contract it quotes — a stated content-anchor trade-off, and the reason the card was re-homed here. Accepted.
  • CI at this head: objectui's test shards were in_progress at the seat's read (markdown-only diff); Tier H — the maintainer merges; the seat reports the checks green before that hand-off. Self-referentially, this PR is itself a dispatched draft and stays one.
  • Concurrency: objectui#9563 (PR docs(agents): state the attribution-footer append as conditional on absence #10212) rewrites the body-mutation clauses at :470–:480 — a different region of the same file; the later lander merges once.

Implemented-by: claude/issue-9992-agents-non-governed-pr-lands-by-the-seat
Reviewed-by: session_01Wnstp2kTth7sGXfr8fXypc

VERDICT: PASS


Generated by Claude Code

@hotlong
hotlong marked this pull request as ready for review September 23, 2026 02:42
@hotlong
hotlong added this pull request to the merge queue Sep 23, 2026
Merged via the queue into main with commit 87a0ed8 Sep 23, 2026
41 checks passed
@hotlong
hotlong deleted the claude/issue-9992-agents-non-governed-pr-lands-by-the-seat branch September 23, 2026 02:53
akarma-synetal pushed a commit to akarma-synetal/objectui that referenced this pull request Sep 28, 2026
…ude/settings.json (objectui#9586) (objectstack-ai#10214)

Fixes objectstack-ai#9586

⛔ **GOVERNED — this pull request is a DRAFT and stays one.** The changed
instruction-tree path is governed surface in this repository. Asked of
the guard itself, in this worktree:

```
$ node scripts/check-governed-queue-guard.mjs --test .claude/settings.json .changeset/issue-9586-enqueue-class-deny-mirror.md
⛔ GOVERNED — 1 of 2 path(s) are on a governed surface:
   .claude/** x1 — the agent instruction tree (skills, hooks, settings)
     - .claude/settings.json

   One governed path governs the WHOLE pull request — proportion is not a question.
   ⛔ Do not flip it ready, enqueue it, or arm auto-merge. Park it as a DRAFT and leave the merge
      to the maintainer; a human merge IS the review record for a governed surface.
   The merge-queue run of "Governed Surface Queue Guard" refuses this diff unless an APPROVED review by an
   authorized approver (GOVERNED_APPROVERS: os-zhuang, hotlong) is on the pull request — on
   whichever commit it was left (maintainer ruling 2026-09-04).
(exit 3)
```

⇒ ⛔ never flipped ready, ⛔ never enqueued, ⛔ never armed with
auto-merge, ⛔ never approved by a seat under any account. Under ruling C
the claiming seat lands it only once an authorized APPROVED review
exists.

## 维护者速读(草稿)

**改了什么**

一个文件、三行:`.claude/settings.json` 的 `permissions.deny` 从 14 条加到 17
条,新增点名三个 MCP GitHub 工具 ——
`update_pull_request`、`enable_pr_auto_merge`、`disable_pr_auto_merge`。改完之后,本仓的
deny 数组与 objectstack 的**逐元素相同、顺序也相同**。`allow` 与 `hooks` 两块字节未动。另附一个空
frontmatter 的 changeset(声明「不发版」,本仓惯例)。

**为什么改**

这是 objectstack 已裁决条款的跨仓镜像,不是本仓的新主张。objectstack#18282 裁决 A(批次 objectstack-ai#135
追认)把「声明即强制」扩到**入队类**:席位在两个板上真正用的入队通路只有 REST 的 `PUT
.../pulls/{n}/ccr/auto_merge`(它就在同一个文件的 `allow` 里),所以把 MCP
的那两个入队工具关掉,**不损失任何能力**,只是把一条没人走的旁路封死。

第三条 `update_pull_request` 是本仓自己的事故教训:objectui#6183 里,一次只传 `reviewers` 的
MCP 调用**顺带**把 PR 的 `draft: false` 设了,于是一个受管草稿进了合并队列、零人工批准就落了地。本仓当时只镜像了
14 条内容写入类,这一条(objectstack 在 PR #18276 落地)和入队那两条(PR #18317)都没跟上 ——
本次一并补齐。

**风险与代价(含回滚)**

- 代价:任何还想用 MCP 去翻 ready、挂/摘 auto-merge 的流程会被运行时拒绝。这是有意的。`gh pr
ready`、`gh pr merge --auto` 与 REST 的 ccr 路径都不受影响。
- ⚠️ 已知代价,需要你知道:AGENTS.md 里有两处**配方文本**仍然点名这两个工具(自行合并那条的「MCP
等价物」一行,以及受管面段「发现自己已经挂上了怎么办」那条补救 —— 后者写的是「只调 `disable_pr_auto_merge` ……
两个都要做」)。本卡的文件面只有 `.claude/settings.json`,且 AGENTS.md 当前被两个在途 PR
持有(objectstack-ai#10212 / objectstack-ai#10213),所以本 PR ⛔
不碰它,改由席位另立卡。方向是安全的一侧:配方失效会**响亮地被拒**,不会静默做错事。
- 回滚:`git revert` 单个 commit 即可,无数据迁移、无构建产物、无已发布包字节变化。

**席位意见**

(留空,交席位定稿)

**你要做的**

确认「入队类 MCP 工具一并关掉」这个方向是你要的(objectstack 那边已经这么定了),然后在本 PR 上留一条 APPROVED
review。批准之后由认领席翻 ready、入队落地,你不需要自己合并。上面那条 ⚠️ 只需要你知道,不需要你在这张卡里处理。

## 前提核验(动手前,在 `origin/main` `0cf2d6644` 上实测)

| # | 前提 | 读数 | 结论 |
|:--|:--|:--|:--|
| P1 | 卡片说本仓 deny 有 14 条 `mcp__github__*`,且三条都不在 | 实测 deny 总数 14,全部是
`mcp__github__*`;三条各命中 0 次,正控 `issue_write` 命中 1 次 | 成立 |
| P2 | 本仓有没有 deny 名册门禁(objectstack 的
`scripts/pm/check-settings-deny-roster.mjs`) | 不存在;`scripts/pm/` 全部内容是
`board-snapshot.mjs` 与 `check-half-states.mjs`;全仓 grep
`settings.json`,`scripts/` 与 `.github/` 内零个读取者 | 无 ⇒ ⛔ 不移植该门禁 |
| P3 | 本仓有没有通道对照表(objectstack 的 `references/rest-channel.md`) |
`.claude/` 下不存在任何 `references` 目录;`.claude/` 全部文件共 14 个(4 个 hook + 4 个
selftest + `launch.json` + `settings.json` + 2 个 skill 的 4 个 md) | 无 ⇒
那一行事实无处可加 |
| P4 | `.claude/settings.json` 是不是被 pin 的上游移植文件(改它会红
`check:upstream-port-parity`) | 不是。`scripts/upstream-port-pin.json` 13
条,`.claude/` 下只 pin 了 8 个 hook/selftest 文件 | 可以改;该门禁仍已跑,绿 |
| P5 | 并发:有没有别的在途 PR 碰这个文件 | 7 个 open PR 的文件清单逐一读过,零个碰
`.claude/settings.json`;`objectstack-ai#10212` / `objectstack-ai#10213` 只有 `AGENTS.md`(hunk 分别在 468
与 323 行区) | 无冲突 |

## 执行判据(卡片的那一条)

推送之后,`GET
/repos/objectstack-ai/objectui/contents/.claude/settings.json?ref=claude/issue-9586-settings-deny-auto-merge-mirror`
解码后的 `permissions.deny`(HTTP 200,blob sha
`30c3367037de7ba000673e4da8caa2913a9c704c`,size 2255):

```
 1 mcp__github__issue_write
 2 mcp__github__create_pull_request
 3 mcp__github__update_pull_request          ← 新增(objectstack PR #18276 的镜像)
 4 mcp__github__add_issue_comment
 5 mcp__github__add_comment_to_pending_review
 6 mcp__github__add_reply_to_pull_request_comment
 7 mcp__github__pull_request_review_write
 8 mcp__github__push_files
 9 mcp__github__create_or_update_file
10 mcp__github__delete_file
11 mcp__github__create_branch
12 mcp__github__sub_issue_write
13 mcp__github__merge_pull_request
14 mcp__github__create_repository
15 mcp__github__fork_repository
16 mcp__github__enable_pr_auto_merge         ← 新增(本卡的判据)
17 mcp__github__disable_pr_auto_merge        ← 新增(本卡的判据)
```

两条判据条目都在 ⇒ 判据满足。

## 改动的形状,实测而非声称

⛔ 没有手打重排。两处锚点文本替换,锚点各自唯一,其余字节不动:

```
deny  base: 14  branch: 17  objectstack: 17
branch deny === objectstack deny (order included): true
allow identical to origin/main: true   sha 46560076ce2be885 = 46560076ce2be885
hooks identical to origin/main: true   sha 10e6c3a3ae991607 = 10e6c3a3ae991607
top-level keys / permissions keys: 与 origin/main 相同,无增删
added:   ["mcp__github__update_pull_request","mcp__github__enable_pr_auto_merge","mcp__github__disable_pr_auto_merge"]
removed: []
duplicate entries: 0
git diff --numstat: 4 1 .claude/settings.json
```

顺序惯例是**按类分组、不是字母序**(实测:objectstack 把 `update_pull_request` 紧挨
`create_pull_request`、把入队对追加在末尾),本 PR 照同样的位置插入,结果两仓数组完全一致。

两个读者都能解析:`node JSON.parse OK` · `python json.load OK`。`settings.json` 里
wire 的 4 个 hook 命令全部落在磁盘上(4/4 OK)—— 这个文件正是点名它们的那一个。

## 门禁

门禁族由本仓自己推导:objectui 没有 `scripts/pm/dispatch-gates.mjs`(不存在),所以逐个读
`.github/workflows/*.yml` 的触发条件,取「会判到这两个路径」的全集。

| 门禁 | exit | 判定行 |
|:--|:--|:--|
| JSON 有效性 | 0 | `node JSON.parse OK` · `python json.load OK` |
| `check-control-bytes`(commit 后重跑) | 0 | `✅ check-control-bytes: OK
(scanned 8217 tracked text file(s); skipped 85 binary).` |
| `check-governed-queue-guard --self-test` | 0 | `OK
check-governed-queue-guard self-test: 185 cases pass` |
| `check-governed-queue-guard --test` | 3 | 本文顶部那段(受管,exit 3 是本 PR
的健康读数) |
| `check-changeset-presence` | 0 | `✅ No source or published contract of
a released package changed in this range, so no changeset is owed.` |
| `check-changeset-no-major` | 0 | `✅ No changeset declares a major
bump.` |
| `check-changeset-overwrite` | 0 | `✅ No pre-existing changeset was
modified or deleted.` |
| `check-lint-coverage` | 0 | `✅ lint coverage: 46/46 packages linted, 0
with outstanding errors (0 total).` |
| `check-entry-guard --self-test` / 全跑 | 0 / 0 | `63 cases pass` · `108
scripts/ file(s) — no entry guard outside the baseline` |
| `check-upstream-port-parity --self-test` / 全跑 | 0 / 0 | `66 cases
pass` · `13 ported file(s) match objectstack-ai/objectstack modulo their
declared divergences` |
| `check-bash32-floor --self-test` / 全跑 | 0 / 0 | `160 cases pass` · `13
tracked shell file(s) … name no bash 4+ construct` |
| `check-skills-paths` | 0 | `✅ check-skills-paths: OK (88/89 stated
path(s) resolve across 20 guide file(s); 1 baselined).` |
| `check-new-cross-file-line-citations` | 0 | `VERDICT
new-cross-file-line-citations: 0 new citation(s), enforcement
report-only -> exit 0` |

退出码一律在任何管道之前捕获(先重定向到文件,再读 `$?`)。

**不欠的两族,连同不欠的理由:** `hook-selftests.yml` 的 paths 是 `.claude/hooks/**`
与它自己 —— 本 diff 不碰 hook 文件(hook 命令仍被 `settings.json` 点名,所以单独验了 4/4
落盘)。`check-shell-escape-residue` 的 `SCAN_ROOTS` 是
`AGENTS.md`、`CLAUDE.md`、`skills`、`.claude/skills`、`content/docs`
五条,`.changeset/**` 不在其中 ⇒ 新增的 changeset 不在它的总体里。

**lint 与 type-check:一次申报过的收窄,不是一次漏跑。** 三条读数里的两条来自仪器,第二条被替换,如实写明:

1. **总体读自 eslint 自己的配置**:`eslint.config.js` 里每一个 `files` 块都限定在
TS/TSX(`**/*.{ts,tsx}`、`scripts/**/*.{ts,tsx}`、`**/*.test.{ts,tsx}`、`**/__tests__/**/*.{ts,tsx}`、`**/examples/**/*.{ts,tsx}`、`**/*.tsx`
及三个具名单文件),没有 JSON 块,也没有 markdown 块。
2. **文件数**:⚠️ 本次**不是**读自 `--format json`,而是读自 diff 的扩展名普查 —— 本分支 `git
diff --name-only` 两个文件,`.json` 与 `.md` 各一,`.ts/.tsx/.js/.mjs/.cjs` 命中
**0** ⇒ 落在 eslint 总体里的文件数必然为 0。这个容器里 objectui 没有 `node_modules`(共享检出与本
worktree 均无),而为一个 JSON + markdown 的 diff 做一次全仓 `pnpm install`
是这台共享机器上最贵的动作、且买不到任何判定。⇒ 这一格的仪器读数记 NOT MEASURED,理由如上;推翻它只需要一个 `.ts`
文件,而本 diff 没有。
3. **对未触碰文件的不变性**:type-aware linting 全仓未启用(`eslint.config.js` 里没有任何块设
`parserOptions.project` 或 `projectService`)⇒ 本 diff
移不动任何它没触碰的文件的判定。同一读数覆盖 `type-check`:tsc 的输入字节未变。

仓级 `pnpm lint` / `pnpm type-check` 农场是 CI 的运行。⚠️ 并且它**会**在本 PR
上跑满:`lint.yml` 的相关性判断只排除
`**/*.md`、`content/**`、`docs/**`、`.changeset/**`,而
`.claude/settings.json` 不在排除列里 ⇒ `should_run=true`,安装之后的那半边门禁由 CI 如常判定。

上述读数取自本分支最终 commit `45f22049`。

## Acceptance notes

顺路看到、⛔ 本 PR 一律没动的:

- **AGENTS.md 有两处配方点名了本 PR 刚关掉的工具。** 一处在「CI 全绿即自行合并」那条的代码块里(`# MCP
等价物:pull request update(draft: false) +
enable_pr_auto_merge`),一处在受管面段的补救条(「只调 `disable_pr_auto_merge` 会摘掉
auto-merge 但不取消队列成员资格,两个都要做」)。本 PR 落地之后,照这两处文本行事的席位会撞上运行时拒绝。objectstack
那边的对应修复是改它的通道对照表那一行(「auto-merge 备用 MCP 未拒」→「auto-merge MCP 锁 1
同拒」);本仓的等价文本恰好落在 AGENTS.md 里,而 AGENTS.md **不在本卡的文件面内**,且当前被 objectstack-ai#10212 /
objectstack-ai#10213 两个在途 PR 持有。⇒ 交席位另立卡,本 PR 不碰。方向安全:失效的配方会被响亮拒绝,不会静默做错。
- **`.changeset/issue-9418-write-identity-locks-port.md` 里有一句已经过时的话。**
那份(空 frontmatter、永不消费的)历史声明写着四个「状态形」工具 —— 含 `update_pull_request` 与
`enable_pr_auto_merge` —— 「stay allowed」。本 PR 之后其中两个不再
allowed。它是一份历史记录而非规程,读者为零,⛔ 不改、也不建议为它单独立卡;记在这里只是为了下一个读到那句话的人不被它误导。
- **`check-governed-queue-guard.mjs` 的拒绝文案仍写着「a human merge IS the
review record」**,而 AGENTS.md 已按裁决 C 改成「获授权批准后由认领席落地」。这条在 objectui PR
objectstack-ai#9448 的 Acceptance notes 里已经报过一次,至今仍在;本 PR 只是再次撞见,不重复立卡。

`Clause-②: no`

本次实现所在的会话,写成散文以便在正文被改写后仍可追溯:`session_01Wnstp2kTth7sGXfr8fXypc`。

## 席位意见(`domain:skills` seat 1,session
`session_01Wnstp2kTth7sGXfr8fXypc`)

**建议:批准并由席位落地。** 一个文件三行,把 objectui 的 `permissions.deny` 补成与 objectstack
逐元素、同顺序相同(席位在复核工作树上解析两边数组比对为真);`allow` / `hooks`
未动;三条都在卡的裁决范围内(objectstack#18282 裁决 A、批次 objectstack-ai#135 追认;`update_pull_request`
是卡正文点名的「本仓一并缺失」项)。不损失能力:两个板上席位真正用的入队通路只有 REST `PUT
…/ccr/auto_merge`,就在同一文件的 `allow` 里。卡留给取卡者的两个可选项 —— 移植 deny 计数门禁、频道表加一行
—— 本树都不存在对应物,均未做(新增门禁默认否)。

**你需要知道的一件事(不阻塞本 PR):** 落地后 objectui `AGENTS.md` :331 与 :531 两处配方文字仍写着
`enable_pr_auto_merge` / `disable_pr_auto_merge` 作为 MCP
等价物,届时会被响亮拒绝而非静默执行错;两个在途 AGENTS.md PR(objectstack-ai#10212 :468、objectstack-ai#10213
:323)不在那两段,不能顺带。席位身份过不了立卡门,已记到席位帖的维护者事项里,由你决定是否立卡。

复核记录:PR 评论 5778587633(PASS,在席按服务档渲染);ACCEPT 在卡 objectui#9586。CI 读数:42
latest-per-name check runs — 39 success, 3 skipped, 0 in progress, 0
other。

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

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

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 needs-user-decision

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Rule conflict: objectui AGENTS.md tells a green non-governed PR author to self-land; the os-dev dispatch contract stops the dev at draft

3 participants