Repository navigation
chore(claude): allow-list hotcrm REST rows and the issue-create / issue-transfer tools - #19941
Merged
Merged
Conversation
… issue-transfer tools settings.json permissions.allow: every objectstack-ai/objectui REST row gains an objectstack-ai/hotcrm mirror directly after it (reads, labels, PATCH pulls, the two ccr landing routes, PATCH issues), and issue-create.mjs and issue-transfer.mjs each get the bare and the --use-env-proxy tool row. fleet-write/dispatch.mjs deliberately gets no row. rest-channel.md, paid in place at 82/82: five write tools share the selector; the failed-run exit register as the tools book it today; prefixes (OS_FLEET_SESSION, any assignment, cd … &&) fall to the classifier and a multi-repo cloud seat's first call is a bare cd; batching goes through the per-op tools, never a bare dispatch.mjs. Claude-Session: https://claude.ai/code/session_01A22sUB3mUWs6M36VgfijBq Co-authored-by: Claude <noreply@anthropic.com>
Contributor
Author
Contract reviewServed-tier: ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
os-zhuang
marked this pull request as ready for review
September 24, 2026 01:55
This was referenced Sep 24, 2026
This was referenced Sep 24, 2026
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Sep 28, 2026
…ough the allow-listed ccr pair; a landing denial stops and surfaces (objectstack-ai#19997) Fixes objectstack-ai#19990 Clause-②: no Rule text only, in three `pm-dispatch` references. This PR adds no allow row, no tool and no gate. `.claude/settings.json`, `scripts/pm/**`, `SKILL.md` and `AGENTS.md` are untouched. Line counts are unchanged (183 / 101 / 37), and every edited line is at or under 120 bytes. The maintainer's words, in the `domain:engine#1` seat's session, quoted on the card verbatim and in order: > 「你的pr为什么没有挂在当前session上」 > 「写一个 skills 卡片,更新技能」 > 「包括你刚才为什么不能merge,我当前session设置的是auto」 The same words reached the `domain:skills` seat directly (claim comment 5817962037): 「你的pr应该挂在当前 session上,对应的卡片优先派发」. ## What changed | file · line (after) | bytes | rule | |---|---|---| | `execution-duties.md` :149 (new) | 118 | Case 1. When a report names a PR, a session seat subscribes it at once (`subscribe_pr_activity`) and lists it on the seat post. Reason, stated once: a PR the relay opens is never attached to the session automatically. | | `execution-duties.md` :147 | 87 → 116 | The collection line now covers both modes itself ("(两种模式)", "评论与返回消息皆无"), replacing the deleted report-channel line (see *Line budget*). | | `landing-operations.md` :49 | 82 → 117 | (b). The landing executes the verdict of record (ACCEPT, or the contract-review PASS). It is not a self-approval. | | `landing-operations.md` :51 | 65 → 115 | (a). Ready and auto-merge go only through the two ccr commands that `settings.json` allow-lists (`rest-channel.md` :51 / :55). | | `landing-operations.md` :54 (new) | 120 | (c). A classifier denial during landing means: stop, report to the maintainer, and record the command and the denial reason on the card. ⛔ Never respell the command or switch to the relay to get around it. | | `landing-operations.md` :77 | 106 + 89 → 111 | Case 1, landing side. Every PR in a session seat's window must be subscribed; subscribe any that is missing. The optional 「关键 PR」 wording is gone. The old :77 "not before the report" clause is folded in as 「⛔ 不早于报告」. Routine seats keep polling. | | `reading-discipline.md` :23 | 82 → 120 | (d). A timer text carries no verdict or landing write verb. | Wording choices that differ from the dispatch text: - **`判决`, not `裁决`, at :49.** In this corpus `裁决` is a maintainer ruling, and `判决` is the review verdict (`execution-duties.md` :180–:183, 「判决 ACCEPT / REWORK / ESCALATE」). - **`判决与落地类写动词`, not only `落地类写动词`, at :23.** The timer that was denied `[Self-Approval]` told the seat to post the ACCEPT as well as run the two landing ops. ## Why no new allow row is owed: case 2 (a) The allow-listed landing route already exists. The skill already names it, and this PR only makes §B's landing step name it too. - `.claude/settings.json` :61–:66 allow-lists `curl -sS -X POST …/pulls/*/ccr/ready_for_review` and `curl -sS -X PUT …/pulls/*/ccr/auto_merge` for all three repos. The hotcrm pair was added on 2026-09-24 by `e6a5ecb9`. That commit also deliberately gave `fleet-write/dispatch.mjs` no row. `git grep -n 'with-fleet' .claude/settings.json` gives 0 hits; the control `git grep -n 'label-write' .claude/settings.json` gives 2. - `SKILL.md` :201 says 「ready/draft 走 ccr 路」, and `platform-readings.md` :48 says 「undraft 单通道:席位凭据走 `POST .../pulls/{n}/ccr/ready_for_review`」. - Measured on the timeline (`GET /issues/N/timeline`, 2026-09-24T16:3xZ): - PRs objectstack-ai#19873, objectstack-ai#19895, objectstack-ai#19902, objectstack-ai#19941, objectstack-ai#19948, objectstack-ai#19956, objectstack-ai#19970 and objectstack-ai#19993 were landed by the `domain:skills` seat under auto mode. Each has `ready_for_review` and `added_to_merge_queue` with actor `os-zhuang` (the ccr route, which writes as the seat's linked user). - PRs objectstack-ai#19971, objectstack-ai#19972 and objectstack-ai#19979 have the same two events with actor `objectstack-fleet[bot]` (the relay route). - Both routes work. The ccr pair is the one with an allow row. The relay route has none, so under auto mode the classifier judges it call by call. ## Where the standing authorization is recorded: case 2 (b) It is already recorded in the tree, so this PR adds only the one clause at :49: - `AGENTS.md` Prime Directive objectstack-ai#14: Tier S lands "by the owning seat on a contract-tier review of record". - `AGENTS.md` Multi-agent discipline §7 and Post-Task Checklist step 2: arm auto-merge on a PR that is green and accepted. - `landing-operations.md` :59 (Tier S). Whether that is enough for a seat landing a PR written by its own `mode:subagent` dev is put to the maintainer below. This PR does not rule on it. ## Line budget: what left, and where each fact still lives All three files stand at headroom 0. Each new line is paid for by deleting content, not by re-wrapping or raising a ceiling. - **`execution-duties.md` old :147 deleted.** It read 「报告通道统一:GitHub 是两种模式共用的真相源;dev 终报先落 issue 评论、再作返回消息。」 - The dev-side ordering lives in `.claude/agents/os-dev.md` :17–:18 (「报告交付两次,GitHub 优先:同一段 JSON 先作 issue 评论 … 再作为终报消息」). - "GitHub is authoritative in both modes" lives in `os-dev.md` :324 (「两种派发模式(`mode:subagent` 与 `mode:cloud`)下 GitHub 都是报告的权威源」). It also stays on the collection line as 「(两种模式)」. - **`landing-operations.md` old :77, second clause, deleted.** It read 「订阅是感知补充,⛔ 不替代 flip 定点」. The fact lives on: - :50: the flip timer is set at ACCEPT. - :52: 「CI success webhook 不可靠:⛔ 不坐等」. - `platform-readings.md` :40: 「订阅来的 `check_suite.completed` 是唤醒不是放行读数」. - Its first clause is kept on :77 as 「⛔ 不早于报告」. - **`landing-operations.md` old :76 rewritten in place.** It dates from `42af12fe7` (the 2026-08-07 ruling on subscribing *key* PRs). The newer maintainer words quoted above replace its optional scope. ## Measured risk that stays open - An allow row does not stop a denial based on content. `mcp__Claude_Code_Remote__send_later` is allow-listed (`settings.json` :23, present since before 2026-09-20), yet the engine seat's timer was denied `[Self-Approval]`. `platform-readings.md` :435 records another content-based `[Self-Approval]` denial. - Two explanations are possible: that session did not load this settings file, or the classifier judges content over an allow row. Which one holds was not measured. The eight ccr landings are the positive reading. The new :54 line covers the negative case. - **Write identity, a tension this PR did not create.** The ccr pair writes as the seat's linked user, `os-zhuang`, which is in `GOVERNED_APPROVERS` (`scripts/pm/check-governed-queue-guard.mjs` :576). Three texts point the other way: - `AGENTS.md`: "Every GitHub write leaves through `scripts/pm/`, as `objectstack-fleet[bot]` … ⛔ Never a bare `curl` … write". - `SKILL.md` :92: 「批准账号永不跑席位或作其关联用户」. - `SKILL.md` :94: 「写侧恒为 `objectstack-fleet[bot]`」. `SKILL.md` :201 already routes ready/draft through ccr, so this tension predates this PR. The new :51 states the same route more plainly. The choice is the maintainer's; see the question below. ## Verification At `04357257d`, every command from `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` was run, with the exit code captured before any pipe. All exited 0: 17 derived commands, plus `pnpm check:pm-governed-prose`, `node scripts/check-skills-token-ratchet.mjs` and `pnpm check:pm-settings-deny-roster`. The reconciliation `dispatch-gates --ran` reports: "17 derived famil(ies) accounted for — 17 run, 0 NOT-MEASURED (a DERIVED zero …)". Verdict lines: - `check:pm-skill-ratchet`: `execution-duties.md is 183 lines (ceiling 183; headroom 0)` · `landing-operations.md is 101 lines (ceiling 101; headroom 0)` · `reading-discipline.md is 37 lines (ceiling 37; headroom 0)`. - `check:pm-skill-id-lint`: `34 file(s) clean`. - `check:skill-frame-sync`: `the one declared copy of the decision frame is internally coherent`. - `check:nul-bytes`: `OK … no raw ASCII control bytes`. - `check:doc-formula-expressions` first exited 3 (PREREQUISITE NOT MET, unbuilt `@objectstack/formula` / `@objectstack/lint`). It exited 0 after `turbo run build` for those two packages under the verify lock. That first run measured nothing; it was not a failure. No build, test, reverse check or ablation applies to this change: it is rule text only, with no code path. ## Acceptance notes - `dispatch-runbook.md` :128 has cloud cards subscribe as soon as the draft PR exists. The folded 「⛔ 不早于报告」 agrees with it only because a cloud dev reports at draft-PR time (runbook :135). No change is made. - `origin/main` was not merged before opening. It moved by one commit (a `docs(qa)` change touching none of these files), and the queue rebuilds on current `main`. ## 维护者速读(草稿) **改了什么** - dev 报告里点名了 PR,席位当场订阅这个 PR 的动态,并记进座位贴。规则里写明原因:经中继开出的 PR 永远不会自动挂到会话上。落地说明里原来"给关键 PR 挂订阅"是可选说法,现在改成"落地窗口里每个 PR 都要挂上,缺了就补"。 - 落地(转 ready、挂 auto-merge)只走 `.claude/settings.json` 已放行的两条 ccr 命令。席位落地是在执行已记录的复核判决,不是自己批准自己。 - 落地过程中如果被权限分类器拒绝:停手,报给您,在卡上记下命令和拒绝原因;不换写法,也不改走中继绕过去。 - 定时提醒的文本里不再写"判决/落地"这类写操作,只写"到时重读什么、满足什么条件"。 **为什么改** - 您问了两个问题。 - PR 为什么没挂在当前 session 上:中继开的 PR 不会自动挂上,而技能里只有可选的"关键 PR 挂订阅"。 - auto 模式下为什么不能 merge:engine 席被拒了两次,一次是一条回读命令,一次是一条写着"发 ACCEPT 并落地"的定时器。它随后改走中继落地,而中继没有放行规则。放行的路本来就有:本席今天在 auto 模式下用它落地了 8 个 PR,一次都没被拒。 **风险与代价(含回滚)** - 只改三个技能参考文件的规则文本。行数不变,每行不超过 120 字节。回滚就是 revert 本 PR。 - ccr 两条命令记在 os-zhuang 名下。而 AGENTS.md 写的是"所有 GitHub 写都经 scripts/pm,以 objectstack-fleet[bot] 身份,永不裸 curl 写",SKILL.md 也写"批准账号永不作席位的关联用户"。这个矛盾早就存在(SKILL.md 本来就写"ready/draft 走 ccr 路"),本 PR 没有新造,只是把它写得更明确。 - 放行规则不保证分类器一定放行。`send_later` 在放行清单里,engine 席那条定时器还是按内容被拒了。所以新加了"被拒就停手上报"这一条。 **席位意见** **你要做的** - 回一句话,确认下面两件事,或者指出要改哪一件: - ① AGENTS.md 第 14 条(Tier S 由所属席位在达档复核记录在案后落地)和 Multi-agent discipline 第 7 条(PR 全绿且已验收就挂 auto-merge),就是席位落地自己子代理所写 PR 的常设授权,不用另外记。 - ② 用 ccr 两条命令落地,算 AGENTS.md「写只经 scripts/pm」这条规则的例外。是把这个例外写进 AGENTS.md,还是改走中继并加一条新的放行规则,都由您决定。 --- _Generated by [Claude Code](https://claude.ai/code/session_01A22sUB3mUWs6M36VgfijBq)_ Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #19931
Clause-②: no
A
repo:hotcrmseat in a cloud container had no allow rule for its own lane: every read and write went to the auto-mode classifier. This PR adds the hotcrm allow rows and the two missing tool rows. It also writes the four facts a seat needs intorest-channel.md. The maintainer's ruling on the card, verbatim: 「你的问题开skills卡」. Thedomain:skillsseat set the shape.What changed
.claude/settings.json(permissions.allowonly; the deny roster and hooks are untouched)objectstack-ai/objectuiREST rows is now followed directly by the same verb and path shape forobjectstack-ai/hotcrm. The mirror covers:ready_for_review,auto_merge);objectstack-ai/*. The per-repo shape is the only one tested so far. Whether a wildcard*in a permission rule crosses a/has not been tested. This is the open question below.issue-create.mjsandissue-transfer.mjseach get the bare row and the--use-env-proxyrow, next to the three existing tool pairs:Bash(node scripts/pm/issue-create.mjs *)andBash(node --use-env-proxy scripts/pm/issue-create.mjs *)Bash(node scripts/pm/issue-transfer.mjs *)andBash(node --use-env-proxy scripts/pm/issue-transfer.mjs *)scripts/pm/fleet-write/dispatch.mjs. A bare dispatch skips what the per-op tools do: the stamp, the unread-knock check and the read-back. It also skips the fleet write-pace budget. Batching goes through the per-op tools:close-cards.mjs --plancloses many cards in one run, andlabel-write.mjshandles one card per call.Structural check on the result: 13 objectui rows and 13 hotcrm rows, each hotcrm row directly after its objectui twin; the JSON parses.
.claude/skills/pm-dispatch/references/rest-channel.mdThe four facts are written into existing lines. The file stays at 82 lines, the ceiling. No line was added, no line was re-wrapped, and every line stays at or under 120 bytes.
post-stamped,label-write,close-cards,issue-create,issue-transfer, the five that importresolveRoute)OS_FLEET_SESSION, any variable assignment, andcd … &&all fall to the classifier when used as a command prefix; in a multi-repo cloud session, the seat's first call is a barecd /home/user/objectstackon its owndispatch.mjshas no allow row (the op-table path is shortened tofleet-write/ops.mjsto fit)exitForResultregister used bylabel-write,issue-create,issue-transferanddispatch.mjsitself). Two exceptions:post-stampedbooks 4, andclose-cardsbooks 4 when the board shows a step landed or the card cannot be read back. UNCONFIRMED is 6.Line 14 used to say that allow rules are literal prefixes. That rule is still stated in this file: the 〈写侧〉 heading on line 39 says 允许规则按首个 glob 前的字面前缀匹配. The only text dropped is the consequence tail 席位就此卡死.
Exit codes were checked against each tool's code, not just its header:
post-stampedrelayExitFor({state:'failure'})givesEXIT_NOT_STORED= 4.close-cardsreads the board after a failed run: 5 when nothing landed, 4 (EXIT_HALF_WRITE) when some steps landed or the read-back failed. Its self-test battery "the relay miss" pins this.label-write,issue-createandissue-transferall returnexitForResult(sent), which gives 5 for a failed run.lanes/hotcrm.mdis untouched. The cwd and prefix fact applies to every sister-repo seat in a multi-repo session, not only to hotcrm, so it belongs inrest-channel.md.Verification (at HEAD
6b230972)Gates came from
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackrun in the worktree (18 commands), plus the dispatch'snode scripts/check-skills-token-ratchet.mjs. Every exit code was captured before any pipe.--ranresult:✓ dispatch-gates --ran: 18 derived famil(ies) accounted for — 18 run, 0 NOT-MEASURED.pnpm check:pm-settings-deny-roster17 content-write tool(s) declared = enforced in .claude/settings.jsonpnpm check:pm-skill-ratchetrest-channel.md is 82 lines (ceiling 82; headroom 0)pnpm check:pm-skill-id-lint34 file(s) cleanpnpm check:nul-bytesscanned 9346 text file(s) … no raw ASCII control bytesnode scripts/check-skills-token-ratchet.mjs54 authored bundle file(s) within their ceilingspnpm check:skill-frame-syncpnpm check:doc-authoringpnpm check:pm-governed-mergespnpm --filter @objectstack/lint run check:doc-formula-expressionsdistnot built); afterturbo run build --filter=@objectstack/formula --filter=@objectstack/lintunder the verify lock, it exited 0NOT MEASURED: the auto-mode classifier's verdict on the new rows. The classifier only runs inside a live seat session. The first live use will be the
repo:hotcrmseat running its confirmed group.The deny-roster gate does not check the
allowlist. Its header says so ("Thatallowanddenyagree … is deliberately NOT asserted"), so its green says nothing about the new rows. No gate reads the allow list's membership. That check rests on the structural mirror above, and this PR adds no gate.Open question for the maintainer
Should an org-level wildcard (
objectstack-ai/*) replace the per-repo rows? This PR keeps per-repo rows, the only tested shape. A wildcard would also cover any future sister repo without an edit. Whether*in a Bash permission rule matches across/has not been tested, and the classifier cannot be tested offline.Acceptance notes
.claude/settings.jsonis read from the primary checkout when a session starts, and is not reloaded (scripts/pm/check-harness-current.mjs). The new rows take effect for a seat only in a session that starts after the primary checkout includes this merge.scripts/pm/close-cards.mjsheader, lines 37–38, also suggests "name the script with an absolute path and nothing before it". No allow rule names an absolute path, so that spelling falls to the classifier. This is the card's seat-side bullet 2. It is reported to the seat, not fixed here.rest-channel.mdline 63 still says the transfer recipe lives inplatform-readings.md. The door is nowscripts/pm/issue-transfer.mjs(cross-repo-coordination.mdline 25). Line 12 saysotherwise direct, but an indeterminate relay read is an exit-3 refusal. Both lines are outside this PR's lines.issue-create,label-write,post-stampedandclose-cards, withoutissue-transfer. It is outside this PR's file surface (Tier H).维护者速读(草稿)
改了什么:
.claude/settings.json的放行清单给 hotcrm 仓补上与 objectui 一模一样的 13 条 REST 读写规则。建卡(issue-create)、转仓(issue-transfer)两个写工具各加两种拼法的放行规则。中继的裸dispatch.mjs刻意不放行。rest-channel.md就地改了四处:写工具现为五个;中继失败的退出码按现行工具更正;多仓云会话里席位先单独一条cd /home/user/objectstack,命令前不带任何前缀;批量一律走逐 op 工具。文件行数 82 不变,没涨上限。为什么改:维护者原话「你的问题开skills卡」。hotcrm 席位在云会话里的读写全凭分类器裁量。第一笔写被拒之后,连读都被拒,已确认的十卡批次至今没执行。
风险与代价(含回滚):本 PR 只加放行规则,放行面与 objectui 已有的完全相同,不新增能力。之后 hotcrm 的 REST 写不再经分类器判,包括 PR 转 ready、挂 auto-merge、改卡。分类器离线测不了,首次实测是 hotcrm 席位执行已确认批次。设置在会话启动时从主检出读取,合并后新开的会话才生效。回滚 = revert 本 PR。
席位意见:
你要做的:拍板一个问题:要不要用 org 级通配
objectstack-ai/*代替逐仓行(本 PR 没采用,因为通配是否跨/没实测过)。本 PR 属 Tier S,由席位凭合约复核记录走队列落地,不需要你点批准。Generated by Claude Code