Skip to content

pm-dispatch: restore needs:contract-review as a marker for a PR awaiting its at-tier review - #19993

Merged
os-zhuang merged 2 commits into
mainfrom
claude/issue-19973-contract-review-marker
Sep 24, 2026
Merged

os-zhuang merged 2 commits into
mainfrom
claude/issue-19973-contract-review-marker

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #19973
Clause-②: no

维护者速读(草稿)

改了什么:把 needs:contract-review 恢复为「PR 在等达档契约复核」的可见标记。规则落在 contract-review.md 新增的一节(何时挂、何时摘、谁读,并写明没有任何检查读取它);landing-operations.md 里「子代理起不来」那一行原地改写,点名这个标记(行数不变);ensure-pm-labels.sh 加回这个标签的定义(新颜色,说明写明「只是标记,没有检查读它」)。

为什么改:您的原话「恢复 needs:contract-review,把这句原话写进一张 skills 车道的卡」,回答的是「只作等复核标记,不作闸门」那一问。上一班达档复核子代理连续被限流,12 个 CI 全绿的 PR 在等达档复核记录,只能靠翻座位贴才看得到;现在一个过滤 is:pr is:open label:needs:contract-review 就能列出来,交接也不必先读座位贴。

风险与代价(含回滚):不新增任何门禁:没有 check、workflow、队列守卫或巡查脚本读它,落地仍只认 ## Contract review 记录,标记丢了或多挂了都不会放行或拦下任何 PR。代价是派发席每个条款② PR 多两次标签写(ACCEPT 时挂,PASS 时摘)。已逐个核对本仓会写 PR 标签的 workflow:今天没有任何自动机制会摘掉手挂的 PR 标签。回滚 = revert 本 PR;GitHub 上的标签对象不受影响。

席位意见:(留空,待席位填写)

你要做的:合并后请持有 gh 的人跑一次 bash scripts/pm/ensure-pm-labels.sh --reconcile,把现存标签对象的颜色与说明对齐(它现在是 GitHub 自动建的灰色、说明为空)。本 PR 的受管路径全在 .claude/**(Tier S),由席位在达档复核 PASS 后落地,不等您点合并。

Summary

The maintainer's instruction recorded on #19973 — 「恢复 needs:contract-review,把这句原话写进一张 skills 车道的卡」, answering a question that proposed 「只作等复核标记,不作闸门」 — brings needs:contract-review back as a visibility marker, never a gate. Every layer that ruling record 5770886272 (letter B) retired stays retired: no queue-guard refusal, no --pair, no double carrier, no independence pair. The enqueue gate still decides on the ## Contract review record alone, and nothing in this diff reads the label.

What changed — 3 files, +28 / -2

path change
.claude/skills/pm-dispatch/references/contract-review.md a new section, heading plus 5 rule lines (:30-:36); the :3 pointer now lists it. 28 → 36 lines, ceiling 60
.claude/skills/pm-dispatch/references/landing-operations.md :13 rewritten in place to name the marker. 101 / 101 lines; that line goes 113 → 119 bytes
scripts/pm/ensure-pm-labels.sh one main-repo row after needs:pack-smoke: colour bfdadc, a 95-character -d, and a comment block naming the label's readers

The rule as landed (contract-review.md :32-:36):

  • it is only a marker, not a gate. The PR is the single carrier; a copy on the card is outside the rule and not required.
  • hang: at ACCEPT, if either clause-② limb hits and no same-form PASS is on file for the current head, the dispatching seat hangs it on the PR in the same stroke.
  • clear: when a same-form PASS is on file for the current head, the seat that posts it clears the marker in the same stroke. When the PR merges or closes, the dispatching seat clears it. A FAIL does not clear it.
  • readers: the maintainer's filter is:pr is:open label:needs:contract-review, and each seat's patrol and handover.
  • ⛔ no check, workflow, queue guard or patrol script reads it. Enqueue recognises only the same-form record, and the marker being present or absent changes no verdict.

landing-operations.md :13, before and after:

- 子代理起不来 ⇒ 复核缺席,PR 留 draft 队列外等档;唯一旁路是维护者亲审,逐次为准。
- 子代理起不来 ⇒ 复核缺席,PR 带 `needs:contract-review` 留 draft 队列外;旁路仅维护者逐次亲审

The line keeps three facts: the review is absent; the PR stays draft, outside the queue; and the maintainer's own review is the only bypass, per instance (唯一 … 逐次为准 → 仅 … 逐次). 「等档」 is carried by the marker itself, which already says the PR awaits its at-tier review. Keeping 「等档」 as well measured 125 bytes, over the 120-byte cap.

Durability — what removes a PR label on this repo today (read at base ba77509eee)

  • pr-automation.yml job pr-size → scripts/pr-labels.mjs --size. It POSTs the computed size/* label, then sends a targeted DELETE only for stale size/* labels (planSizeWrites loops over the size family and nothing else). The job is skipped on labeled / unlabeled / edited.
  • pr-automation.yml job auto-label → scripts/pr-labels.mjs --paths. It only POSTs: 「Path labels are ADD-ONLY … So this half issues POST and has no DELETE at all」 (:225-:227). The keys in .github/labeler.yml are documentation, protocol:*, ci/cd, dependencies, tests and tooling; none is a needs:* label.
  • lint.yml runs node scripts/pr-labels.mjs --self-test, which pins that no write plan emits a PUT. It also runs node scripts/check-whole-set-label-write.mjs, which reds on a whole-set PUT /issues/{n}/labels in any spelling anywhere in the repo. That verb (third-party labelers, and a labels field written through MCP) is what removed this label in the gate era.
  • stale.yml (actions/stale) removes only its own stale label. It closes a PR after 37 idle days, and a close is already a clear trigger in the rule.
  • half-state-patrol.yml runs sweep-closed-cards.mjs --write, which strips PM_RESIDUE_LABELS (the pm:* state labels) from closed cards only.
  • merge-queue-triage.yml adds labels to its anchor issues only. fleet-write.yml runs only the ops a seat names.
  • objectui's labeler runs with sync-labels: true, but that is objectui's. This label is created in this repo only.

⇒ Today no mechanism on this repo removes a PR label that a seat hung by hand. The live carriers' event history agrees. Every labeled or unlabeled event for needs:contract-review on PR #19962, PR #19968, #19955 and #19953 is by objectstack-fleet[bot], that is, by a seat. The only removal pair (PR #19962 at 11:27:07Z, #19953 at 11:27:41Z) was the spec seat's own stroke after an at-tier FAIL (comment 5813182458, 「Carriers stripped on the PR and on this card」), and both were hung again at 12:14Z. Losing a marker is also the safe failure: a waiting PR drops out of the filter, but nothing is released, because the queue guard reads the record.

Live carriers at dispatch (read 2026-09-24T14:46Z) — ⛔ this PR changes no label on any of them

carrier kind what the rule says
#19962 PR, draft, head 22c9473c86 path limb hits (packages/spec/src/security/rls.zod.ts). The marker stays until a same-form PASS is on file for its current head; whoever posts that PASS clears it. Under the rule, the 11:27Z clear after the FAIL would not happen: a FAIL leaves the marker on.
#19968 PR, draft, head b05a88136d path limb hits (packages/spec/src/ui/view.form.ts). Same as above.
#19955 card a card copy is outside the rule and not required. What happens to it is for the spec seat that hung it.
#19953 card same as #19955.

Aligning these four carriers is the dispatching seat's closeout step once the rule is on main, as the claim amendment on the card says. It is not part of this PR.

Four scripts that still name the label as retired — unchanged, on purpose

scripts/pm/check-half-states.mjs :11927 and :18308, scripts/pm/check-skill-line-ratchet.mjs :448 and :830, scripts/pm/check-widening-tells.mjs :673, and scripts/pm/clause2-line.mjs :11 and :306. Each one describes the gate role (a half-state row that patrolled it, a raise provenance, a dated census line, the ruling's summary, a measured incident). That role is still retired, so every sentence stays true. None of them reads the label, and this PR does not make any of them a reader. AGENTS.md, SKILL.md, state-machine.md and .claude/agents/os-dev.md are untouched too; the claim excluded them.

Acceptance notes

Pending after merge — the seat's, not this PR's

  • Someone holding gh runs bash scripts/pm/ensure-pm-labels.sh --reconcile once. The live object is ededed with an empty description (read 2026-09-24T14:46Z), and create-if-missing never changes an object that already exists.

Tests — on 84f4580e

  • node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths; three-dot vs merge base ba77509ee) derived 32 commands. The dispatch named four more: node scripts/check-skills-token-ratchet.mjs, node scripts/pm/check-governed-queue-guard.mjs --self-test, pnpm check:pm-expected-skips and pnpm check:pm-governed-prose. All 36 exit 0, each exit code captured before any pipe. --ran reconciliation: 「32 derived, 32 run, 0 NOT-MEASURED, 0 UNRUN」.
    • pnpm check:pm-label-desc-cap: 「39 label descriptions … all ≤100 characters (longest: 100, tooling)」 (38 on base).
    • pnpm check:pm-skill-ratchet: 「contract-review.md is 36 lines (ceiling 60; headroom 24)」 and 「landing-operations.md is 101 lines (ceiling 101; headroom 0)」. All lines are ≤120 bytes; :3 is at exactly 120.
    • pnpm check:pm-skill-id-lint: 「34 file(s) clean」. pnpm check:skill-frame-sync, pnpm check:doc-authoring, pnpm check:pm-governed-prose and pnpm check:nul-bytes are green.
    • pnpm --filter @objectstack/lint run check:doc-formula-expressions first exited 3 (PREREQUISITE NOT MET: @objectstack/formula / @objectstack/lint not built). That run measured nothing. After pnpm exec turbo run build --filter=@objectstack/formula --filter=@objectstack/lint under scripts/pm/os-verify-lock.sh (VERDICT command-exit 0), the rerun exited 0.
  • bash -n scripts/pm/ensure-pm-labels.sh exits 0. A fake gh on PATH ran ensure-pm-labels.sh --reconcile, exit 0: the new row issued label create needs:contract-review -R objectstack-ai/objectstack -c bfdadc -d … and the matching label edit … --color bfdadc --description … with the same string.
  • node scripts/pm/check-governed-merges.mjs --test on the three paths returns GOVERNED, Tier S (.claude/** ×2); scripts/pm/ensure-pm-labels.sh is not on the register.
  • A self-scan for control bytes on the three files finds none.
  • Not run locally: no package is touched, so there is no build closure and no package test or typecheck. pnpm lint and the CI-only families (the shard attestation, the test-completeness reader and the type-check lanes) are left to CI.

Generated by Claude Code

…ing its at-tier review

The label comes back as a visibility marker only, never a gate:

- references/contract-review.md gains a 〈等复核标记〉 section: hang on
  the PR at ACCEPT when either clause-② limb hits and no same-form PASS
  is on the current head; clear when that PASS is on file or the PR
  merges or closes; single carrier = the PR; named readers = the
  maintainer's PR-list filter and each seat's patrol and handover; no
  check, workflow, queue guard or patrol script reads it.
- references/landing-operations.md: the subagent-down line names the
  marker in place (line count unchanged, 119 bytes).
- scripts/pm/ensure-pm-labels.sh: one main-repo row, colour bfdadc,
  description under the 100-character cap.

Every layer ruling record 5770886272 retired (gate role, --pair,
double carrier, independence pair) stays retired.

Claude-Session: https://claude.ai/code/session_01A22sUB3mUWs6M36VgfijBq
Co-authored-by: Claude <noreply@anthropic.com>
The marker tracks "landing still owes an at-tier PASS on the current
head"; a FAIL starts a patch round and the PR still owes that PASS, so
only a PASS on file, a merge or a close clears it. Stated in the rule
and in the roster comment so a seat does not clear on FAIL and re-hang
at the next ACCEPT.

Claude-Session: https://claude.ai/code/session_01A22sUB3mUWs6M36VgfijBq
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 84f4580e8a674f868d70a95c045f11ac4f4d34d6

Rendered in-seat by the domain:skills#1 seat at 2026-09-24T15:18Z on the diff of head 84f4580e (3 files, +28/−2; check-governed-merges --pr 19993: GOVERNED, Tier S, .claude/** ×2, scripts/pm/ensure-pm-labels.sh not on the register), the card #19973 with the maintainer's verbatim instruction, ruling record 5770886272 on #19061, and the dev report 5816859484 (2026-09-24T15:14Z). Adversarial brief: does any line of this diff make the label a gate again, restore a layer ruling B retired, or say something a seat cannot act on?

① Derived judgments

  1. Scope against the maintainer's words 「恢复 needs:contract-review,把这句原话写进一张 skills 车道的卡」, answering 「只作等复核标记,不作闸门」. The diff restores the label as a marker and nothing else: the contract-review.md section :30–:36 says 「只作等复核标记,⛔ 不是闸门」 and 「⛔ 无 check、workflow、队列守卫或巡查脚本读它;入队只认同形记录,标记有无不改判」; the roster comment repeats it in English. No script, workflow or gate changes; the diff's only non-prose line is a gh label create row. Correct.
  2. Ruling B layers (gate role and double-carrier discipline, --pair, independence pair): grep of the diff for --pair, 双载体, 独立性 finds them only in the roster comment's sentence naming them as staying retired. 「单载体 = PR,卡上副本在规则外、不是要求」 is the opposite of the retired double-carrier discipline. Correct, none returns.
  3. Hang rule :33 「ACCEPT 时条款②任一肢命中而现 head 无同形 PASS 在案 ⇒ 派发席同笔挂于 PR」: keys on the two limbs landing-operations.md :10–:11 already define (path limb, declaration limb), so the limbs are not restated in a second file — the dev's deviation 4, accepted. One hang point (ACCEPT), one actor (the dispatching seat), one carrier (the PR). Actionable by a seat.
  4. Clear rule :34 「现 head 同形 PASS 在案 ⇒ 写记录的席同笔摘;PR 合并或关闭 ⇒ 派发席摘;FAIL 不摘」: the seat that posts (or adopts) the PASS clears, the dispatching seat clears on merge/close, a FAIL leaves it on. 「FAIL 不摘」 is the dev's deviation 3 — it makes the implied answer explicit and is grounded in a measured event (the spec seat cleared on FAIL at 11:27Z and re-hung at 12:14Z). Consistent with the marker's meaning (still awaiting a PASS). Correct.
  5. Readers :35: the maintainer's filter is:pr is:open label:needs:contract-review and each seat's patrol and handover — the named readers the card asked for; ensure-pm-labels.sh :37–:38 (every label has a named query) is satisfied by the roster comment. Correct.
  6. landing-operations.md :13 in place, 101 / 101 lines, 119 bytes: keeps 复核缺席 · 留 draft 队列外 · 旁路仅维护者逐次亲审, names the marker; drops 「等档」 (carried by the marker) and the trailing 。 to stay under the 120-byte cap (125 with both). The missing full stop is the one stylistic cost in the diff; the ratchet's own rule (a wrap it cannot produce is never demanded) makes it acceptable, and no gate pins it. Accepted; noted.
  7. Roster row: -c bfdadc (outside the state and red families), -d 97 characters ≤ 100 (check:pm-label-desc-cap reports 39 descriptions all ≤ 100), 2>/dev/null || true in the file's shape, placed after needs:pack-smoke, main repo only. The comment block (16 lines, deviation 1) names the rule's home, hang/clear, readers, no-reader-in-any-check, the verbatim words, the retired layers and the colour rationale — longer than suggested, all of it load-bearing for the file's reader. Correct.
  8. contract-review.md :3 pointer rewritten to 「…复核归属、资格与等复核标记」, exactly 120 bytes. 28 → 36 lines under a ceiling of 60. Correct.
  9. Population (the dev's open question): A — the two clause-② limbs, the population the retired label had. 「恢复」 restores that label; governed Tier S PRs get their in-seat record in the same seat's ACCEPT stroke and show no waiting state to mark; B would key the rule on the governed register with no measured stall. A, as landed.
  10. Durability: the PR body's per-workflow reading (pr-labels.mjs --size DELETEs only stale size/*; --paths POST-only; stale.yml removes only stale; sweep-closed-cards.mjs strips pm:* residue from closed cards only) agrees with this seat's own read of pr-automation.yml :225 at dispatch; the live carriers' events are all seat strokes. Accepted as reported; a lost marker releases nothing, since the queue reads the record.
  11. Untouched on purpose: SKILL.md (fable-mandated path, PR docs(pm-dispatch): name objectstack-fleet[bot] as the write identity #19890 in flight), AGENTS.md (Tier H), state-machine.md, os-dev.md, and the four scripts whose comments describe the retired gate role — every one of those sentences stays true. Correct.

② Semver level

No published package touched (.claude/** and scripts/pm/** ship in no files[]); skip-changeset is the correct form on this repo and is on the PR; no content/docs/releases/ change. None owed; consistent.

③ Boundary flags

Implemented-by: claude/issue-19973-contract-review-marker
Reviewed-by: session_01A22sUB3mUWs6M36VgfijBq

VERDICT: PASS


Generated by Claude Code

@os-zhuang
os-zhuang marked this pull request as ready for review September 24, 2026 15:27
@os-zhuang
os-zhuang added this pull request to the merge queue Sep 24, 2026
Merged via the queue into main with commit 19af43d Sep 24, 2026
37 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-19973-contract-review-marker branch September 24, 2026 16:19
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>
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

2 participants