Skip to content

fix(pm): the clause-② path limb and SUSPECT_TIER_GLOBS except spec test files - #19948

Merged
os-zhuang merged 3 commits into
mainfrom
claude/issue-19936-clause2-path-limb-excludes-tests
Sep 24, 2026
Merged

os-zhuang merged 3 commits into
mainfrom
claude/issue-19936-clause2-path-limb-excludes-tests

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #19936
Clause-②: no

The clause-② enqueue gate's path limb read every file under packages/spec/src, while the contract-review rule owes an at-tier review for non-test files there only. For a test-only spec PR from an off-tier seat, the two rules gave opposite answers, so the PR could never enqueue. The ruling on the card (comment 5805897677, letter A) settles it toward the review rule: the path limb and SUSPECT_TIER_GLOBS except test files, using the repo's own test-file predicate. contract-review.md is unchanged.

What changed

scripts/pm/dispatch-gates.mjs

  • SUSPECT_TIER_GLOBS: the packages/spec/src/** entry gains except: isTestPath and an exceptWhy reason. The glob and its why are byte-identical, so check-widening-tells.mjs, which builds its contract surface from g.glob and g.why, sees no change.
  • deriveTier: a suspect glob's except is applied per path, before the suspicion is recorded. A mixed diff keeps the suspicion on its contract files. MANDATORY_TIER_GLOBS has no exception and its semantics are unchanged.
  • The predicate is imported, not respelled: isTestPath from scripts/check-undeclared-dep-imports.mjs. The docblock above the table says why this one was chosen (see the table below).
  • Docs: a new docblock section on SUSPECT_TIER_GLOBS, one sentence in the clause-② header paragraph, and one on deriveTier.
  • Self-test: 20 new cases, listed under Verification. One existing census row moved from dispatch-gates.mjs:708 to :713, because the import shifted that file's own inherited-population declaration by five lines. That census names each declaration by file and line on purpose.

.claude/skills/pm-dispatch/references/landing-operations.md line 10

The line is rewritten in place and no line is added.

-- 路径肢 = diff 触及契约面 `packages/spec/src/**`,含 error-code-ledger 与 `*.zod.ts` 契约 schema。
+- 路径肢 = diff 触及契约面 `packages/spec/src/**` 非测试,含 error-code-ledger、`*.zod.ts` 契约 schema。

The line goes from 111 to 119 bytes.

非测试 is the exact word contract-review.md line 9 uses. To stay under the 120-byte line cap, 与 became 、, with no change in meaning. The file is still 101 lines, at its ceiling of 101.

Which test-file predicate, and why (measured)

The ruling says to take "the repo's own test-file predicate, not a new spelling". The repo has several. Each was run over five shape probes and over the 1541 tracked files under packages/spec/src on this branch:

predicate shape probes excepted tracked files excepted *.zod.ts excepted
check-undeclared-dep-imports.mjs isTestPath (exported) ⭐ 5 of 5 499 0
check-position-name-fold-loaders.mjs isTestFile (exported) 5 of 5 499 0
check-examples-live-imports.mjs (not exported) 5 of 5 499 0
check-dispatcher-error-vocabulary.mjs isTestFile (not exported; also bare fixtures/ anywhere) 5 of 5 499 0
check-runner-env-posture.mjs isTestFile (also .bench., .pin., bare fixtures/) 5 of 5 500 0
check-adr-0087-registration.mjs TEST_FILE_RE (filename only) 2 of 5 500 0
this file's own isTestFilePath (filename only, deliberately) 2 of 5 499 0
tenant-audit-census.mjs / isystem-census.mjs isTestPath (treats qa/ as a test dir) 4 of 5 (misses test/) 501 1 (qa/testing.zod.ts)

The five shape probes cover the ruling's four shapes: stack.test.ts, type-alias-convention.pin.test.ts, a helper under __tests__/, a fixture under __tests__/, and a fixture under test/.

isTestPath was chosen because its gate asks the ruling's exact question: which files under a package's src/ are published source, and which are its tests. It is exported and it covers all four shapes. Its only test directories are test/, tests/, __tests__/ and __fixtures__/, none of which is a contract domain under packages/spec/src. A predicate that also treats qa/ as a test directory would drop a real contract: packages/spec/src/qa/testing.zod.ts is a real contract schema, and it stays a SUSPECT. That case is pinned.

The lit case (ruling item 3)

node scripts/pm/dispatch-gates.mjs --tier --repo objectstack-ai/objectstack packages/spec/src/type-alias-convention.pin.test.ts (the one file PR #19932 changes):

  • base 43460b95: prints the SUSPECT block, and its path row reads - packages/spec/src/type-alias-convention.pin.test.ts ⇢ 'packages/spec/src/**' — the contract surface (error-code ledger, *.zod.ts contract schemas) — the normal landing zone of a clause-② card.
  • head ef4d57c0: no line contains SUSPECT. It prints Model tier — no path-derived mandate: the surface hits none of the 3 declared glob(s) … and the standing clause-② note (Clause ② is NOT reachable from paths: … This line is a FLOOR, never a clearance.).

Same command on packages/spec/src/ui/view.zod.ts at head ef4d57c0: the SUSPECT block still prints, and its path row reads - packages/spec/src/ui/view.zod.ts ⇢ 'packages/spec/src/**' — the contract surface …. The block's header line names the contract-review tier's model id, so it is not quoted here.

Verification (at HEAD ef4d57c0)

The gates came from node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, run with no paths (36 commands). Five more came from the dispatch-time list. Each exit code was captured before any pipe. The --ran result: ✓ dispatch-gates --ran: 36 derived famil(ies) accounted for — 36 run, 0 NOT-MEASURED.

gate exit verdict line
pnpm check:pm-dispatch-gates 0 ✓ dispatch-gates self-test: 1925 cases pass. (831 s, run detached as its header prescribes)
pnpm check:pm-skill-ratchet 0 landing-operations.md is 101 lines (ceiling 101; headroom 0)
pnpm check:pm-skill-id-lint 0 34 file(s) clean
node scripts/check-scripts-symbol-anchors.mjs 0 3668 anchors across 281 scripts resolve
node scripts/check-self-test-wired.mjs 0 every one of the 229 script(s) CI runs that ship a --self-test has that self-test run by CI
node scripts/pm/check-governed-queue-guard.mjs --self-test 0 279 cases pass
node scripts/check-skills-token-ratchet.mjs 0 54 authored bundle file(s) within their ceilings
pnpm check:pm-widening-tells (not derived; it imports this table) 0 525 cases pass
pnpm --filter @objectstack/lint run check:doc-formula-expressions 0 The first run exited 3 (PREREQUISITE NOT MET: formula and lint were not built). It exited 0 after turbo run build of those two packages under the verify lock.
pnpm check:nul-bytes 0 scanned 9353 text file(s) … no raw ASCII control bytes
the other 30 derived or listed commands 0 green

The 20 new self-test cases:

  • The lit case raises no suspicion. Its rendering has no SUSPECT line and still carries the clause-② note.
  • A *.zod.ts path is still a suspect.
  • Five shape cases are each excepted: a plain test, a pin test, a __tests__/ helper, a fixture under __tests__/, and a fixture under test/.
  • A mixed diff keeps its contract file.
  • qa/testing.zod.ts stays a suspect while qa/testing.test.ts is excepted.
  • A directory surface that contains the contract surface is still a suspect.
  • A test-named file under a mandated root keeps its mandate.
  • The exception is the imported function, and its home is a gate script, so a card editing it derives this gate by gate-script identity.
  • Every excepting entry carries a reason.
  • Live: none of the 203 tracked *.zod.ts files is excepted, and the exception really drops tracked files.
  • End to end on the CLI: --tier on the lit case prints no SUSPECT line, and --tier on view.zod.ts does.

Ablation, from committed state:

  • Mutation: deriveTier's guard if (hintCovers(g.glob, p) && !(typeof g.except === 'function' && g.except(p))) { became if (hintCovers(g.glob, p)) {. It was applied through scripts/ablation-replace.mjs in WRAP mode, and the file hashes show it landed: anchor 1 → 0, replacement 0 → 1, blob caf66526bc7d → f2f8a50e2b5e.
  • Predicted before the run: 11 red. Observed: ✗ dispatch-gates self-test: 11 of 1925 case(s) failed., the predicted 11 exactly. They are the two lit-case cases, the five shape cases, the mixed diff, the qa/testing.test.ts case, the live non-vacuity case, and the CLI lit case.
  • The zod, qa/testing.zod.ts, ancestor, mandate, provenance and live-zod cases stayed green, as they should with no exception applied.
  • Restore: blob after restore caf66526bc7d equals HEAD, git diff HEAD is empty, and git status --porcelain is empty.

eslint, narrowed to the one changed .mjs file:

  • Population: eslint --print-config shows 2 rules (no-restricted-imports, comment-swallow/no-code-inside-block-comment), both per-file, and no parserOptions.project.
  • Count: --format json reports 1 file, 0 errors, 0 warnings.
  • Invariance: no type-aware linting is enabled, so this diff cannot move any untouched file's verdict. The .md file is not linted.

NOT MEASURED: pnpm check:published-readme-exports, exit 3. It needs a full pnpm build of the workspace, which was not run. It is an artifact-roster family printed for every card. This diff touches no package, no package README and no type surface.

Acceptance notes

  • check-widening-tells.mjs, the mechanical half of the declaration limb, imports SUSPECT_TIER_GLOBS and hintCovers from this file. It already excludes tests with its own filename-only predicate (isContractSourceFile). A __tests__/ helper under packages/spec/src would still be read there for tells. There are none on the tree today. That is outside this card's surface and was not changed.
  • A derivation gap that predates this PR: dispatch-gates.mjs --commands scripts/pm/dispatch-gates.mjs does not name pnpm check:pm-widening-tells, although that gate imports from this file. The same holds for its other two imports (scripts/regen-artifacts.mjs, scripts/pm/check-half-states.mjs). The documented remedy is a module constant in the importing gate, the SURFACE_MODULE shape in check-dispatch-gates.mjs. The gate was run here anyway. Carrier: none.
  • packages/spec/src/benchmark.bench.ts stays a SUSPECT. Neither the chosen predicate nor the ruling's four shapes name bench files.
  • The ruling's __tests__/** shape would also except a *.zod.ts placed under a test directory. The package's files[] entry for src zod files would still ship such a file. None exists today, and the new live pin reds the day one does.

维护者速读(草稿)

改了什么:入队闸门的「路径肢」(landing-operations.md 第 10 行)和 dispatch-gates.mjs 的嫌疑表 SUSPECT_TIER_GLOBS,现在都把 packages/spec/src/** 下的测试文件排除在外。排除用的是仓里现成的测试文件判据(check-undeclared-dep-imports.mjs 的 isTestPath),直接 import,没有另写一份。--tier 对只改 spec 测试的 diff 不再打印 SUSPECT 行;改 *.zod.ts 的照旧打印。contract-review.md 未动。

为什么改:按裁决 A(评论 5805897677,维护者「同意」)。两条规则原先对「只改 spec 测试文件的 PR」互相矛盾:一条要求先有达档复核记录,另一条又说不许为它另起复核。结果出自不达档席位的这类 PR 永远进不了队列,眼下卡住的是 PR #19932。测试文件不随包发布,改它不改变任何对外契约。

风险与代价(含回滚):这道门放宽了,但只放宽到「测试文件」。另有两处自测直接对着真实文件树兜底:仓里 203 个 *.zod.ts 一个都不许被排除;qa/testing.zod.ts 这种名字像测试、实为契约的文件必须照旧打嫌疑行。消融实测:去掉排除,恰好预期的 11 条自测变红。强制档表(MANDATORY)没有任何排除。回滚:revert 本 PR 即可,无数据迁移。

席位意见:

你要做的:无需操作。本 PR 属 Tier S(.claude/** + scripts/pm/**),由 skills 席位完成达档复核后入队;PR #19932 在本 PR 落地后入队。


Generated by Claude Code

…st files

The enqueue gate's path limb read every file under packages/spec/src, while
the contract-review rule owes an at-tier record for non-test files only, so a
test-only spec PR from an off-tier seat could never enqueue. Per the ruling
(letter A), SUSPECT_TIER_GLOBS' contract-surface entry now carries an
`except` predicate — isTestPath, imported from check-undeclared-dep-imports.mjs,
never respelled — that deriveTier applies per path before recording a
suspicion; landing-operations.md's path limb reads 非测试 in place.

Claude-Session: https://claude.ai/code/session_01A22sUB3mUWs6M36VgfijBq
Co-authored-by: Claude <noreply@anthropic.com>
The lit case (a test-only spec diff) raises no suspicion and --tier prints no
SUSPECT line; a *.zod.ts diff still does; each of the four ruled test shapes
is excepted; a mixed diff keeps its contract file; qa/testing.zod.ts stays a
suspect; the exception is the imported predicate and never drops a tracked
*.zod.ts on this tree.

Claude-Session: https://claude.ai/code/session_01A22sUB3mUWs6M36VgfijBq
Co-authored-by: Claude <noreply@anthropic.com>
The isTestPath import above it shifted dispatch-gates.mjs's own
inherited-population declaration from line 708 to 713; the census names each
declaration by file:line on purpose, so the row moves with it.

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: ef4d57c09b8207b88f443b62cec923f066a65e5c

① Derived judgments

  • scripts/pm/dispatch-gates.mjs (+107/−5): SUSPECT_TIER_GLOBS' one entry (packages/spec/src/**) gains except: isTestPath + exceptWhy; deriveTier applies except per path before recording a suspicion (hintCovers(g.glob, p) && !(typeof g.except === 'function' && g.except(p))), so a mixed diff keeps every non-excepted path's suspicion and a mandate is never excepted; MANDATORY_TIER_GLOBS untouched. The predicate is imported from scripts/check-undeclared-dep-imports.mjs :412 (exported), not respelled — the ruling's 「the repo's own test-file predicate, ⛔ not a new spelling」 holds; the docblock records why this predicate over the repo's others (5/5 ruling shapes, 0 tracked *.zod.ts excepted, keeps qa/testing.zod.ts). Reproduced on the head in a review worktree: --tier on packages/spec/src/type-alias-convention.pin.test.ts prints 0 SUSPECT lines; packages/spec/src/ui/view.zod.ts and packages/spec/src/qa/testing.zod.ts still print the SUSPECT row; a __tests__/ path prints none. Self-test: 20 new cases pin the lit case, the zod case, the five shapes, the mixed diff, the adversarial qa/testing.zod.ts, the imported-predicate identity, a live no-*.zod.ts-excepted guard and a non-vacuity guard, plus two real-CLI pins; the marker-census row moved 708 → 713 with the import — correct.
  • .claude/skills/pm-dispatch/references/landing-operations.md :10 rewritten in place: 「路径肢 = diff 触及契约面 packages/spec/src/** 非测试,含 error-code-ledger、*.zod.ts 契约 schema。」 — 119 bytes (cap 120), 101 / 101 lines, the word 非测试 is contract-review.md :9's own; no line added, no rule lost (与 → 、 only). contract-review.md unchanged, as ruled.
  • Accept set: the clause-② SUSPICION narrows by exactly the imported predicate; the enqueue gate's declaration limb and the governed register are untouched; check-widening-tells.mjs (a second importer of SUSPECT_TIER_GLOBS) builds from glob and why only — 525 cases still pass. Public surface: none (.claude/**, scripts/pm/** ship in no package) ⇒ skip-changeset is the right form.
  • Closing keywords: Fixes #19936 on line 1 only; no other keyword beside a card number (whole body scanned). Governed: Tier S (check-governed-merges --pr 19948: 1 of 2 paths under .claude/**; dispatch-gates.mjs not on the register; 114 lines).

② Semver level

  • none — no published package touched; skip-changeset label present (timeline 03:22:48Z).

③ Boundary flags

  • open_questions: none (no predicate gap measured). Deviations 1–5 read: the fifth predicate candidate is the measured best and is what the ruling asks for; assumption 6 partly refuted (check-widening-tells.mjs is a second carrier whose verdicts do not move) — recorded, no change owed; the :10 wording fits the cap with the reference's own word.
  • NOT MEASURED, accepted: pnpm check:published-readme-exports (needs a full workspace build; the diff touches no package, README or type surface).
  • Out-of-scope findings (carrier-less, noted on the seat post, ⛔ not this PR's): dispatch-gates' derivation omits check:pm-widening-tells for an edit to a module that gate imports; isContractSourceFile in check-widening-tells.mjs is filename-only (dormant, zero instances).

Implemented-by: claude/issue-19936-clause2-path-limb-excludes-tests
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 03:42
@os-zhuang
os-zhuang added this pull request to the merge queue Sep 24, 2026
Merged via the queue into main with commit 2dc5889 Sep 24, 2026
37 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-19936-clause2-path-limb-excludes-tests branch September 24, 2026 04:04
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/m skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants