docs(pm-dispatch): instrument discipline — hard gates answer definite questions only; C5 and the patrol anchor stop blocking; at-tier review scoped to what ships and read from CI (#19496) - #19513
Conversation
… questions only R1: a non-zero `--pair` blocks landing only on the rows that answer a definite question; C5, the widening tell, is report-only and the at-tier review rules on its tells. R2: a report-only instrument earns no card and no dev. R3 (charter half): the half-state patrol runs on demand and its H rows are a reading, never a dispatch precondition. R5: the at-tier review is owed on what ships to users or agents, and its shape is a diff read plus the head's check-runs — never a local re-run of the derived gate families. R6: the 仪器纪律 reference, with one pointer line in SKILL.md. Every added line is paid in place; no ratchet ceiling moves. Claude-Session: https://claude.ai/code/session_012GcsUbuqFGBibkEDMRC1eE Co-Authored-By: Claude <noreply@anthropic.com>
The definite-row line names C5 as 只报告 rather than leaving it to inference, and the 仪器纪律 reference binds the name C5 to the widening tell it is. Claude-Session: https://claude.ai/code/session_012GcsUbuqFGBibkEDMRC1eE Co-Authored-By: Claude <noreply@anthropic.com>
…strument-discipline-charter
The tier outcome rides the 落地前检三条 line itself, so the director lane's pointer at this block still resolves after the standalone tier line left. Claude-Session: https://claude.ai/code/session_012GcsUbuqFGBibkEDMRC1eE Co-Authored-By: Claude <noreply@anthropic.com>
Contract reviewServed-tier: Tier H ( ① Derived judgments
② Semver levelNot applicable: charter text only, no package content; ③ Boundary flags
Implemented-by: VERDICT: FAIL Fails on ①5: R5's scope sentence landed but the lane-restriction sentences (contract-review.md:24-25, SKILL.md:651) were left standing and contradict it on the docs faces Isolated at-tier reviewer (mode:subagent of the director seat), reading time 2026-09-21T05:08Z; worktree at Generated by Claude Code |
…strument-discipline-charter # Conflicts: # .claude/skills/pm-dispatch/SKILL.md
…hes, not by lane The lane sentences that survived beside the new scope now say who runs the review, not who owes it: a diff touching any of the five shipping faces owes it, and 五面皆不碰 is the exemption. The published-schema face names its path, the miscount 三面 is gone, and the ruling record joins the provenance line. Claude-Session: https://claude.ai/code/session_012GcsUbuqFGBibkEDMRC1eE Co-Authored-By: Claude <noreply@anthropic.com>
Contract reviewServed-tier: Tier H ( ① Derived judgments
② Semver levelNot applicable: charter text only; ③ Boundary flags
Implemented-by: VERDICT: PASS Round 1's three FAIL items are closed on this head: R5 keys the obligation on the face and every lane-keyed twin is swept; the branch merges Isolated at-tier reviewer (mode:subagent of the director seat), reading time 2026-09-21T05:26Z; fresh worktree at head Generated by Claude Code |
|
Provenance — director seat, summon #25 (
Generated by Claude Code |
Resolves the SKILL.md conflict in the triage-seat filing rules: #19513's report-only-instrument line is kept verbatim, and this branch's compressed Acceptance-notes reader question sits beside it. The filing gate's instrument clause now points at `references/instrument-discipline.md` instead of restating the no-dev half #19513 landed. Claude-Session: https://claude.ai/code/session_012GcsUbuqFGBibkEDMRC1eE Co-authored-by: Claude <noreply@anthropic.com>
…ate beside PATH and SIZE, plus the charter's external-contributions section (objectstack-ai#19526) Fixes objectstack-ai#19470 Clause-②: no ## The ruling this lands Maintainer, batch objectstack-ai#207 item 1, letter 是, verbatim and untranslated: 「fork PR 的处理同意你的意见。」 — recorded by the director on the card (comment 5754996112) as: **a fork PR is a proposal, not a delivery. No agent seat flips it ready, enqueues it, arms auto-merge on it, or approves it — ever.** It enters through the card door (card first; the human decides the problem, not the code; the seat adopts the diff and never lands the fork PR; a fork's CI is never approved to run by a seat, zero check runs read NOT MEASURED, never green), with the minimal mechanism: a second predicate beside the governed one in `scripts/pm/check-governed-merges.mjs`, one sentence in AGENTS.md, one short 〈外部贡献〉 section in the charter, the NOT-MEASURED wording. No new label, no new sweep; the supply-chain clause is deferred until the first such fork PR appears. The live specimen, PR objectstack-ai#19342 (head `jinyitao123/objectstack`, `author_association: FIRST_TIME_CONTRIBUTOR`, 0 check runs on `5fa7b6d`, 0 labels), is read here as evidence only. It stays untouched by this PR: its disposal is the director's and triage's act in the same stroke, and this branch writes nothing on it. ## What changed (five files, all on the claim's surface) | file | before → after | net | what | |:--|:--|--:|:--| | `scripts/pm/check-governed-merges.mjs` | 6146 → 6226 lines | **+80** (98 added, 18 replaced; budget ≤ +80, self-tests included) | the FORK predicate beside PATH and SIZE; the check-run reading; a new self-test battery beside the SIZE cases | | `AGENTS.md` | 1109 → 1109 | **0** (ceiling 1109, headroom 0) | one sentence in Prime Directive objectstack-ai#14's first paragraph, paid by one retired restatement | | `.claude/skills/pm-dispatch/SKILL.md` | 815 → 816 | **+1** (ceiling 819, headroom 3) | one rule line in 〈入队与落地〉 | | `.claude/skills/pm-dispatch/references/core-rules.md` | 151 → 151 | **0** | the landing-rule line rewritten in place to carry the twin | | `.claude/skills/pm-dispatch/references/external-contributions.md` | new, 16 lines | +16 (budget ≤ 20; widest line 120 B) | the ruled 〈外部贡献〉 section, four points + the deferred supply-chain line | ### 1. `check-governed-merges.mjs` — the FORK limb - `forkVerdict(pull)` (pure, exported, beside `sizeVerdict`): reads `head.repo.full_name` and `base.repo.full_name` off the PR object; NOT MEASURED when there is no `base.repo` to compare against; a fork when the head repo differs — **or is `null`** (the fork was deleted): a deleted fork is still a fork, fail closed. - `testVerdict(paths, { size, fork, checks })` carries `fork` and `checks`; `humanMerge` fires on any of the three limbs; `landsByHumanMerge` reads the fork limb too, so a fork exits on the EXISTING GOVERNED code (3) through the Tier H terminal — every caller that already routes 3 to the human hand routes a fork there without learning a new code. `applyGeneratedExceptions` keeps the fork limb across a generated-artifact lift (a lift moves a PATH, never the head repo). - `renderForkLines(fork, checks)` (pure, exported): the fork sentence under the verdict — a PROPOSAL, never a delivery, whatever its paths; no AI seat flips it ready, enqueues it, arms auto-merge on it or approves it; a seat's review is required INPUT, never the permission; the owning seat adopts the diff onto an internal branch (`Co-authored-by:` the contributor) and lands THAT; requests to the contributor go only as review comments on the fork PR, which is closed with thanks and the landing link. When the PATH limb is clear the head line reads `paths: none on the register — the HEAD REPO decides this PR:` (the SIZE limb's shape); on a governed diff the sentence rides under the tier block, Tier S included (a fork head on a `.claude/**` path is still adopted, never landed). - `--pr N` reads the head's check-run count off `GET /repos/{slug}/commits/{sha}/check-runs` — one more GET, on the channel already chosen for the PR read. `total_count: 0` prints `check runs on head SHA: 0 — NOT MEASURED, never green` in the register the size limb already uses; a count that did not read prints `NOT READ (reason) — read as NOT MEASURED, never green`; a non-zero count prints nothing. - `--test` and `--branch` cannot see a head repo and now say so on stdout (`head repo: NOT MEASURED — this verdict cannot tell a fork PR from an internal one; --pr N reads it`), the same discipline as the size limb: a verdict silent about a leg it did not run reports a clearance it never measured. The `--branch` / `--test` byte-identity pin still holds (both print the same line). - `--json` carries `fork` and `checks` beside `size`. - Self-test: battery `⭐ the FORK predicate: head repo ≠ base repo is a proposal, never a delivery` (6 cases, floor 6, roster 30 → 31), placed between the SIZE battery and the audit-half battery, never at the tail: fork head on ordinary paths ⇒ H route with the fork sentence and no `ordinary queue landing applies`; `head.repo === null` ⇒ the same; same-repo head on ordinary paths ⇒ unchanged (NOT governed, no fork line); same-repo head on `AGENTS.md` / `.claude/agents/os-dev.md` ⇒ Tier H / Tier S word for word, while a fork head on the Tier S path still prints the fork sentence; no PR object ⇒ NOT MEASURED said; zero check runs ⇒ the NOT-MEASURED line, 43 ⇒ absent, unread ⇒ `NOT READ`; and `fetchPullFiles` against an injected fetch reads the head repo off its own GET and the count off the head sha. Header docblock gains a "The FORK predicate" section; the summary line names the battery. ### 2. `AGENTS.md` — one sentence, net 0 Prime Directive objectstack-ai#14, first paragraph, :264 before (102 B): ```text paragraph names fewer surfaces than the register — or more. When it reds, name the surface here. ``` :264–:266 after (113 B / 116 B / 106 B): ```text paragraph names fewer surfaces than the register — or more. When it reds, name the surface here. A fork PR (head repo ≠ base repo) is a proposal, never a delivery, whatever its paths: no AI seat readies, queues, arms auto-merge on or approves it; the owning seat adopts the diff onto an internal branch and lands that. ``` "whatever its paths" is the sentence's shape saying the fork rule is a predicate on the PR, not a surface: the paragraph still names exactly the register's six surfaces and no `**`-shaped span was added, so `pnpm check:pm-governed-prose` stays green (quoted below). **Payment (+2 lines bought by one retired restatement, ⛔ not by the ceiling, not by re-wrap):** the three paragraphs of Prime Directive objectstack-ai#14 are greedy-packed already (878 / 1789 / 918 characters joined ⇒ 8 / 16 / 8 lines at the 120-column cap, exactly what they occupy), so re-wrap buys nothing. Retired: the Skills section's line (pre-edit :800, 117 B, plus its trailing blank line): ```text ⛔ **Both roots are governed surfaces** — `skills/` is Tier H, `.claude/skills/` Tier S (**Prime Directive objectstack-ai#14**). ``` It restated the register and its tiers, whose home is the directive it pointed at. Surviving home and grep proof on this head: `grep -n 'skills/\*\*' AGENTS.md` → :6 (the CLAUDE-conflict clause), :260 (the register names `.claude/**` and `skills/**`), :273 (Tier H names `skills/**`); `grep -n 'Tier S' AGENTS.md` → :258, :276 (Tier S = all of `.claude/**`), :292; `grep -n 'Both roots' AGENTS.md` → no hits. The Skills section keeps its two-root catalog lines; only the restatement left. ### 3. `SKILL.md` — one rule line in 〈入队与落地〉 (+1, 108 B) Inserted at :643, directly under the section's pointer line (`- 细则见 references/landing-operations.md,落地窗口查阅。`): ```text - fork PR = 提案,席位永不放行;采纳 diff 内部落地,见 `references/external-contributions.md`。 ``` Placement (four axes): **业务需求** — the acts the rule forbids (ready / 入队 / auto-merge / 批准) are the acts this section governs, so a seat reading its landing checklist meets the fork rule where it would otherwise act; **长远合理性** — one rule line, one pointer, the detail in a references file, the same shape as the section's first line; **防 AI 犯错** — the line sits before the 条款② gate and the PASS ⇒ ready ⇒ auto-merge line, so the fork stop is read before the enqueue reflex; **创业阶段不扩散** — ≤ +1, no new section. ⛔ Not in the 分诊 / 定级 region (:174–:175, :362, :368–:372, :379 are objectstack-ai#19494's) and not on PR objectstack-ai#19488's lines (the `area:*` rows, :119–:134, :215, :243, :259, :294, :310, :371–:372, :470–:471). PR objectstack-ai#19513 edits :632–:633 and :647 / :653 of the same section; :643 sits between its two hunks with unchanged context on both sides, so the later lander merges `origin/main` once with no conflict expected. ### 4. `references/core-rules.md` — the twin, paid in place (151 → 151) :122 before (118 B) → after (112 B): ```text - 验收后取路径面,命中规则层即分叉 ⛔ 不翻正式不入队;Tier S 席内达档复核 PASS 后入队。 - 验收后取路径面:规则层 ⛔ 不翻正式不入队,Tier S 达档 PASS 后入队;fork PR 永不放行。 ``` Every landing-rule line in the core subset sat at 111–118 B of the 120 B cap, so the twin could only ride a compression: 「命中…即分叉」 and 「席内…复核」 are folded to 「规则层」 and 「达档」 (the SKILL.md lines they summarise are unchanged), and the freed bytes carry the rule. No clause dropped. PR objectstack-ai#19513 and PR objectstack-ai#19488 touch :55–:69, :102, :106–:113 of this file, not :122. ### 5. `references/external-contributions.md` — new (16 lines, every line ≤ 120 B) Frame as its siblings (title, a 「见 SKILL.md 〈…〉」 pointer line, one rule per line). Bytes per line: 45 · 0 · 117 · 117 · 0 · 113 · 77 · 97 · 92 · 102 · 110 · 113 · 106 · 113 · 116 · 120. Content, the four points of the ruling in substance: ① card first — triage's fire scans open PRs whose head repo ≠ base repo, files a card from the PR body graded like any card, posts the ONE fixed comment (`this repository works card-first; filed as #N; this PR stays a draft until the card is graded`), a fork PR with no card does not exist on the board; ② the human decides the problem, not the code — a plain reproducible defect routes to a seat, floor items (security / permission boundary, contract, feature) go to the decision box as a business question; ③ the seat adopts the diff, never lands the fork PR — internal branch, cherry-pick or re-implementation, `Co-authored-by:` the contributor, full gate derivation + at-tier review + the queue as for internal work, requests to the contributor only as review comments on the fork PR, closed with thanks and the landing link when the internal PR lands; ④ a fork's CI is never approved to run by a seat, zero check runs read NOT MEASURED, never green; the machine face (`check-governed-merges.mjs --pr N` routes head repo ≠ base repo through the H(人合) exit, `head.repo` null the same); and the deferred supply-chain line. `check-skill-line-ratchet.mjs` does **not** require a CEILINGS row for a new references file (it enumerates only `references/lanes/` for uncovered files; the file is simply outside the ratchet's map, as `references/instrument-discipline.md` on PR objectstack-ai#19513 is). No row was added: the ratchet script is outside this card's file surface. Noted under Acceptance notes for the seat. ## The two `--pr 19342` readings Before (origin/main 8fc6a5f), `node scripts/pm/check-governed-merges.mjs --pr 19342 :: exit 0`: ```text governed-surface predicate: 0 of 4 path(s) hit the register (6 surfaces, repo-agnostic). ✅ NOT governed — ordinary queue landing applies to a PR with exactly this file list. Derived from GOVERNED_SURFACES, not recalled. Re-run on the FINAL file list: the register has grown several times in two days, and a reading taken earlier in the session is recall. size: 56 changed line(s) (+48 / -8) ≤ 5000 — under the human-merge threshold (generated files included in the count). ``` After (this head 0a50588), `node scripts/pm/check-governed-merges.mjs --pr 19342 :: exit 3`: ```text governed-surface predicate: 0 of 4 path(s) hit the register (6 surfaces, repo-agnostic). paths: none on the register — the HEAD REPO decides this PR: ⛔ FORK PR — head jinyitao123/objectstack ≠ base objectstack-ai/objectstack: a PROPOSAL, never a delivery, whatever its paths. The Tier H terminal, with its own reason: no AI seat flips it ready, enqueues it, arms auto-merge on it or approves it — ever; a seat's review is required INPUT, never the permission. The owning seat adopts the diff onto an internal branch (`Co-authored-by:` the contributor) and lands THAT through the ordinary gates; requests to the contributor go only as review comments here, and this PR closes with thanks and the landing link.⚠️ check runs on head 5fa7b6d: 0 — NOT MEASURED, never green: a fork's CI does not run until a maintainer approves it, and a head that never ran looks exactly like one that passed. No seat approves it to run. size: 56 changed line(s) (+48 / -8) ≤ 5000 — under the human-merge threshold (generated files included in the count). ``` `--json` on the same run: `governed: false`, `tier: null`, `humanMerge: true`, `fork: { measured: true, isFork: true, headRepo: "jinyitao123/objectstack", baseRepo: "objectstack-ai/objectstack" }`, `checks: { sha: "5fa7b6dc9d76657a57e30fbcd8010277261254f7", total: 0, reason: null }`. ## Design choices, on the four axes **`head.repo === null` reads as a fork (fail closed).** 实际业务需求: the API returns `head.repo: null` exactly when a contributor deleted the fork after opening the PR — a PR nobody can rebuild the head of is the least reviewable shape a fork PR takes, and the population it comes from is the fork population. 项目长远合理性: the predicate is closed (`head ≠ base`, with an unreadable head on the far side of ≠), so no third state grows beside it. 防 AI 犯错: the alternative — treating an unreadable head as "not a fork" — is a `??` fallback in the consumer that turns a missing fact into a clearance; the fail-closed reading is the loud one, and its words name the deleted repo. 创业阶段不扩散: zero extra code — one `headRepo === null ||` in the predicate, one `(deleted fork repo)` in the words, one self-test case. The one case deliberately left NOT MEASURED rather than forked is a PR object with no `base.repo` at all (a fixture shape, never seen from the API): there is nothing to compare against, and NOT MEASURED is never a clearance either. **Where the SKILL.md line sits** — the four axes are given under §3 above; the 分诊 / 定级 region belongs to objectstack-ai#19494 and the feature-axis lines to PR objectstack-ai#19488, so the landing section was the only region on the claim. ## PM mechanism assumptions — verified 1. ✅ At 8fc6a5f `check-governed-merges.mjs` had 0 hits for `head.repo` / `author_association` / `FIRST_TIME`; the PR object is fetched in `fetchPullFiles` (`fetchJsonOver` on `GET /repos/{slug}/pulls/{n}`, the read that gives `changed_files` and the size pair), and the proxy returns `head.repo` (`jinyitao123/objectstack` on objectstack-ai#19342, `base.repo` `objectstack-ai/objectstack`). The fork limb reads that same object; the check-run count is a second GET on the same channel. 2. ✅ The H route's terminal reads `⚖️ landing tier: H(人合) — the maintainer's hand, or an authorized APPROVED review (GOVERNED_APPROVERS) and then the owning seat lands it`; the SIZE limb's terminal reads `The same terminal as a Tier H governed diff: no seat flips it ready, enqueues it, or arms auto-merge`. The fork sentence keeps the three verbs, adds `approves it`, and adds the adoption step instead of the seat-lands-it clause (a fork PR is never landed by the seat). 3. ✅ AGENTS.md 1109 / 1109; on the unchanged tree `pnpm check:pm-skill-ratchet :: exit 0` and `pnpm check:pm-governed-prose :: exit 0`; on this head both exit 0 again (quoted below). 4. ⏳ `node scripts/pm/check-governed-merges.mjs --pr` on this PR's own number is run after the PR exists; the reading (expected: exit 3, GOVERNED, Tier H — `AGENTS.md` and `skills`-free `.claude/**` on the register, one Tier H path making the whole PR Tier H) goes into the `os-dev-report` comment on the card. This PR stays draft for the maintainer's hand. ## Verification (this head 0a50588) - `node scripts/pm/check-governed-merges.mjs --self-test :: exit 0` — 441 assertions, the new battery registered at its floor; the two `--pr 19342` readings above; `--test README.md :: exit 0` (NOT governed, `head repo: NOT MEASURED` on stdout), `--test AGENTS.md :: exit 3`; `npx eslint scripts/pm/check-governed-merges.mjs :: exit 0`. - The four charter gates, exits captured before any pipe: `pnpm check:pm-skill-ratchet :: exit 0` (AGENTS.md 1109 / 1109 headroom 0; SKILL.md 816 / 819; core-rules.md 151 / 151), `pnpm check:pm-skill-id-lint :: exit 0`, `pnpm check:pm-governed-prose :: exit 0` (2 surfaces name all 6 registered surfaces and claim no others), `pnpm check:nul-bytes :: exit 0`; control-character scan of the five files: 0 hits. - Derived union, `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` with no paths on 0a50588: 44 commands. Every command run in the foreground with its exit captured before any pipe (`cmd > log 2>&1; EXIT=$?`), then reconciled: `node scripts/pm/dispatch-gates.mjs --ran ran-union.txt --repo objectstack-ai/objectstack :: exit 0` — **44 derived, 44 run, 0 NOT-MEASURED, 0 UNRUN**, every exit 0. One family needed its inputs built first: `pnpm --filter @objectstack/lint run check:doc-formula-expressions` answered `PREREQUISITE NOT MET` (exit 3, `@objectstack/formula` then `@objectstack/lint` not built in a fresh worktree) until `pnpm --filter '@objectstack/lint...' build` ran under the shared verification lock (`os-verify-lock.sh`, VERDICT command-exit 0, held 176 s); re-run on the built tree it exits 0 (22 record-scoped examples judged clean). Heavy families in the union (`check-governed-queue-guard.mjs --self-test`, `check:pm-dispatch-gates`, `check-skills-token-ratchet.mjs`) all green. - Local scope per the dispatch: no package's `pnpm test` / `typecheck` is owed — the diff touches no `packages/**` source; `pnpm lint` (the repo-level eslint) is CI's run, the edited script was linted directly. ## 维护者速读(草稿) **改了什么** — 三处一致的规则:fork 上来的 PR(head 仓 ≠ base 仓,含 fork 已删的情况)是提案不是交付。① `check-governed-merges.mjs` 在「路径受管」「改动超 5000 行」之外加了第三个判据「head 是 fork」,命中即走既有的「人合」出口,并打印自己的理由句;`--pr` 同时读 head 上的 check run 数,0 条时打印「NOT MEASURED,不是绿」。② `AGENTS.md` 第 14 条基本原则加一句(行数不变,用 Skills 节里一句重复表述抵付)。③ PM 技能的落地条款加一行,核心条款一行原地改写承接,并新增一页 `external-contributions.md` 写清四步:先立卡、人裁问题、席位采纳 diff 内部落地、fork 的 CI 席位不代批。供应链条款按裁决延后。 **为什么改** — 您 2026-09-21 裁了「fork PR 的处理同意你的意见」。之前合并前防线只看改了哪些文件、只约束我们自己的席位,objectstack-ai#19342 那样的外部 PR(OAuth 放宽到明文 HTTP,0 条 CI)按机器判定可走普通队列;现在它被机器点名为提案,任何席位都不能放行。 **风险与代价(含回滚)** — 代价:每张外部 PR 都要您(或授权批准账户)拍板,今天 100 张里 1 张;`--pr` 多一次 API 读。行为变化:`--test` / `--branch` 的输出多一行「head 仓未测」提示,不改退出码。回滚:revert 本 PR 一个 commit 即可,无数据、无迁移。 **席位意见** — (留空,席位定稿时填) **你要做的** — 点合并(Tier H:本 PR 改了 `AGENTS.md`,只由您的手落地)。objectstack-ai#19342 本身按同一批裁决由总监席 / 分诊席处置,不在本 PR 内。 ## Acceptance notes - `references/external-contributions.md` carries no CEILINGS row in `check-skill-line-ratchet.mjs` (the ratchet does not require one and the script is outside this card's surface), so the new file is invisible to the line ratchet until pinned; a first pin at the landed count (16) is not a raise. carrier: the `domain:skills` seat. - `--pr N` now spends one extra GET per run (`/commits/{sha}/check-runs?per_page=1`) on the chosen channel; a failed read is carried as `NOT READ (reason)` and never a zero. - `--test` / `--branch` stdout gained the `head repo: NOT MEASURED` line under every verdict — deliberate, mirrors the size limb; a grep reader keyed on `NOT MEASURED` alone now matches on every `--test` run without size flags too (it already did for the size line). - The seat's decision analysis left three confidence gaps open (`pull_request_target` in four workflow files, the MCP merge route against a fork head, fork PR history beyond the newest 100); none is on this card's surface and none was measured here. carrier: the `domain:skills` seat. - PR objectstack-ai#19342 is not addressed here; it remains open for the director's / triage's disposal. --- _Generated by [Claude Code](https://claude.ai/code/session_017ETYWqMQD4qMtZzAGovWNi)_ Co-authored-by: Claude <noreply@anthropic.com>
…-echoes per leaf, and derive the population from the type registry (objectstack-ai#19585) Part of objectstack-ai#19403 Clause-②: no Round 7 of the en-echo decision series. The six **bare** metadata types — `seed`, `mapping`, `api`, `doc`, `book`, `capability` — have no form at all: no `fields`, no `sections`. Their registry `label` and `description` are the only strings an author ever sees for them, and every one of those twelve leaves was still its English source in `zh-CN`, `ja-JP` and `es-ES`. They are decided here, one leaf at a time, with the reason recorded for each. ## The three-exit question, answered: **EXIT 1 — taken, with a stated substitute** Rounds 5 and 6 both refused this family on one standing reason: > only a real panel keeps the DERIVED-POPULATION property with a working dark control — six unrelated types degenerate the derivation and the control has nothing to exclude. **That refusal was right about what it refused.** It was reasoning about a population spelled `['seed','mapping','api','doc','book','capability']` — a hand-list, which derives nothing and excludes nothing. That is not the only population available, and this round names the substitute rather than deferring a third time. **What derives the population, if not one panel's `en` subtree.** `DEFAULT_METADATA_TYPE_REGISTRY` — the spec-side list of every metadata type the platform declares — crossed with one predicate read off the catalog's shape: a type is **bare** when its catalog entry has no `fields` and no `sections`, so the registry entry *is* its whole panel. Both halves are derived and neither is the ledger's own opinion: the registry lives in `packages/spec`, a package this round does not touch, and it is the thing that manufactures these leaves — `seed.label` in the catalog *is* the registry entry's own `label`, and every row is pinned to both. Ten types satisfy the predicate; the six decided here are the six that echoed. **What the dark control can exclude — twice, and the second one is the half a panel walk never has.** 1. **Outward** — the 17 types that do carry a form are excluded, and their own type-level pairs are already authored. The sharpest single exclusion is `dataset`: it carries a registry `description` exactly as these six do, so any walk keyed on "registry entries with a description" would sweep it in. The bare predicate is what leaves it out, and it is already authored, so a walk that wrongly included it would not even go red. Asserted by name. 2. **Inward** — **four** members of the population (`job`, `datasource`, `external_catalog`, `translation`) are in the walk and come back **non-echoing**. A hand-list of six can only ever produce positives; this derivation produces a negative on four of its own members, in the same run, from the same walk. **That is the control the refusal said this family could not have**, and ablation B below is its proof: refilling `job.label` — a leaf *no row in this ledger decides* — reds the file. **And the substitute is strictly stronger than a panel walk in one measurable way.** A panel walk is bounded by a type that already exists. This one is bounded by a shape over the registry, so a *new* metadata type — the actual way this defect class reproduces, since every one of these twelve leaves was born the day its registry entry was added — lands in the population automatically. No single-panel ledger in this series can do that. What is not claimed: that the property is preserved unchanged. It is **substituted**, and the controls are real but are not the same controls. ## The phantom-translation trap — structurally out of reach here Round 5 measured it on `action.fields.ai.label`: `Ai` to `AI` differs in bytes, so it passes the echo predicate in all three locales and drops the census by a key while telling a zh-CN author nothing. **An echo that stops matching is not the same thing as a leaf that got translated.** Here the shortcut is not merely refused, it is unreachable, and that is a property of the family nobody chose: the `en` side of every row is the registry entry in `packages/spec/src/kernel/metadata-plugin.zod.ts`, not a string in this package. Touching up the English would be an edit to another package, outside this round's file surface. The only edit reachable from here is the one that actually renders the leaf — and ablation D proves the registry pin is live and independent. ## The schema readings — five near-misses, each asserted, none described Resolved through `getMetadataTypeSchema` (the registry's own schema map) rather than a hand-picked import list. | leaf | the word | verdict | |:--|:--|:--| | `doc.description`, `capability.description` | `package` | **near-miss REAL** — it is a legal `MetadataProvenanceSchema` value and both schemas accept `_provenance: "package"` while refusing a rendered one. **Cleared** three ways: the key is an envelope field the loader sets (the item parses without it, and the whole `en` catalog names no underscore-prefixed key anywhere); these types are bare, so the string labels no input; and where this catalog does render a leaf whose stored value is literally `package`, it renders the display and keeps the value (`sys_metadata.fields.managed_by.options.package` is 包 / パッケージ / Paquete). | | `mapping.description` | `rename` | not a `TransformType` member (none/constant/lookup/split/join/javascript/map) — rendered. **But `map` is**, and "field mapping" contains it: `'field mapping'.includes('map')` is TRUE while the word-boundary predicate says NO. That pair is the token guard's dark control. | | `seed.description` | `publish` | looks like a `SeedMode`, is not one — rendered, with this catalog's own authored 发布 / 公開 / Publicar. | | `api.description` | `pipeline` | looks like an `ApiEndpointSchema.type`, is not one (flow/script/object_operation/proxy) — rendered. | | `book.description` | `groups` | a key that takes an **array**, so no rendered word can land in it — the rule round 6 used to clear `timeoutMs`. | ## "Capability" in three positions, decided two ways - `hook`/`action.fields.body.capabilities.label` — a `HookBodyCapability` **token list**. Round 6 moved both to 能力 / ケイパビリティ. **Untouched**, and asserted so: the cross-panel invariant stays at 30/30. - `object.sections.capabilities.label` — the object's **feature toggles**, a different concept under the same English stem. Still 功能开关 / 機能 / Capacidades, deliberately, and asserted so. - `capability.label` — the **ADR-0066 metadata type**, whose instances are named authorization capability keys (`export_data`, `billing.refund`, from the schema's own name-regex message). Takes 能力 / ケイパビリティ / **Capacidad** — agreeing with the landed pair by **re-deriving from the same objects-catalog evidence** (`sys_user.fields.ai_access.help`, `sys_email.fields.attachments_json.help`) rather than borrowing its decision, and differing from it in Spanish **number** because this leaf names one capability and that one names a list. A precedent answers only the question it contains; round 6's contained the token list, not this type. ## The remainder, in keys AND leaves — two counts, two questions | family | keys | decidable leaves | `.label` leaves | |:--|--:|--:|--:| | `object.fields.enable.*` (+ `validations`) | **9** | 11 | 27 | | `report.fields.drilldown` + `runtimeFilter` + `sections.dataset_binding` | 3 | 6 | 9 | | **remaining total** | **12** | **17** | **36** | Census re-taken on this branch's base `88920d153` and on the merged head `699e2e862`: | reading | base | head | |:--|--:|--:| | `en` string leaves / `.label` leaves | 893 / 538 | 893 / 538 — same population | | `.label` keys echoing in ALL THREE | **18** (54 `.label` leaves) | **12** (36) | | decidable sibling remainder | **29** | **17** | | POSITIVE CONTROL `zh-CN` / `ja-JP` / `es-ES` | 520 / 504 / 504 | **526 / 510 / 510** | The headline falls by **6** and the decidable remainder by **12**, because this round decides six labels *and* six descriptions and only the labels move the headline. State which count you mean. ## No key added, no key removed Full flattened key sets compared base against head across all four bundles: **3572 entries, 0 added, 0 removed**, `en` untouched. Both directions of the comparator proved on a probe key, each mutation landed on disk and restored: - **added** — injecting `seed.__probe__` into `zh-CN`: `base=3572 head=3573 added=1 removed=0`. - **removed** — deleting `zh-CN` `seed.description`: `base=3572 head=3571 added=0 removed=1`. Regenerated with `pnpm i18n:extract`, which rewrote the three bundles byte-identical to the entered values and dropped exactly the **12** provenance rows per locale that recorded these leaves as unauthored extractor fills, adding none. `metadataForms.TYPE.PROP` is now empty in all three tables where the base held exactly these twelve. ## Ablations — 6 runs, **11 distinct assertions**, 5 mutation targets across 4 files Every one through `scripts/ablation-replace.mjs`: the anchor had to hit and fall, the blob had to change, and each restore is proved by blob-equals-HEAD plus an empty `git diff HEAD`. | # | mutation | reds | |:--|:--|:--| | A | `zh-CN` `seed.label` refilled with its `en` source | `zh-CN: every decided leaf matches its verdict`; `no leaf in the bare class reads its en source unless the ledger decided it is an echo` | | B | `zh-CN` `job.label` refilled — **a leaf no row decides** | `DARK, INWARD — four members of the population come back NON-ECHOING`; the same class rule. **This is the proof the substitute property is real.** | | C | `en` catalog `Seed Data` reworded to `Seed data` | `every row is pinned to the live en source`; `the echo predicate can say "echo"`. The **registry** leg stays green — the two legs are independent. | | D | the **registry** entry in `packages/spec` reworded, spec rebuilt | exactly one: `…and to the REGISTRY entry that manufactures it — the third leg`. The catalog pin stays green. | | E | `rename` made a legal `TransformType` in `packages/spec`, spec rebuilt | `rename is NOT a TransformType — but map is, and "field mapping" contains it` | | F | the derived population swapped for the **hand-list rounds 5/6 assumed** | `the BARE predicate splits that population, and is read off the SHAPE not a name list`; `DARK, INWARD`; `this class is now DONE`. | **Two disclosures, volunteered.** 1. The first key-set negative control was **refused by the tool** — "the anchor count moved 1 to 1, a drop of 0, not the declared 1" — because the replacement re-contained the anchor. It was **re-authored** as a true replacement (and the removal direction re-authored again into `--delete` mode when the replacement count would not rise), not re-run until something landed. 2. Ablation D's first dist marker was `Seed data`, which **pre-exists** in `packages/spec` in 12 unrelated places (`'Seed data loader configuration'`, `'Seed data records'`, doc comments). Its `--absent` restore leg read RED as the tool's own header warns it can — a surviving hit that was never mine — and, worse, its `present` leg would have passed even had the mutation never reached `dist`. **That reading was void**, the marker was re-authored to the discriminating `type: "seed", label: "Seed data"`, and D was re-run from scratch. Both spec-side ablations then carried a real dist preflight (`marker present in 4` / `in 20 built files`) and a real restore leg (`marker absent from all 216` / `218 built files`, tree clean). Because this ledger imports `@objectstack/spec/kernel` — unaliased, so it resolves through `spec/dist` — the spec-side ablations **do** need a rebuild, and each carries one. That is a difference from round 6, whose ledger reached only sibling sources; it is stated rather than inherited. ## Merge with `origin/main`, two-legged `origin/main` moved from `88920d153` to `b3615f1a4` while this round ran, so the check was **not vacuous** — and the two commits (objectstack-ai#19538, objectstack-ai#19513) touch **nothing** in `packages/platform-objects/src/apps/translations/`. - **Leg 1 — something really moved:** `git diff --stat 88920d1 origin/main` = 10 files, +262 / -60, including `packages/spec/src/data/object.zod.ts`. - **Leg 2 — it survived:** after the merge, `git diff origin/main HEAD` is empty on **all 10** of those paths. Nothing was reverted. The merged head was then rebuilt and every reading below re-taken on it. ## Gates — 60 derived / 60 run / 0 NOT MEASURED / 0 UNRUN Reconciled by `scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran`, with every family recorded as `COMMAND :: exit CODE` and the exit code captured **before** any pipe, so the zero is derived rather than claimed: > 60 derived famil(ies) accounted for — 60 run, 0 NOT-MEASURED (a DERIVED zero — all 60 recorded an exit code and none of them is 3). Plus, on the merged head `699e2e862`: dependency-closure build exit 0; `pnpm --filter @objectstack/platform-objects test` exit 0 — **51 files / 770 passed** (+1 file, +37 tests vs round 6); `typecheck` exit 0; `pnpm check:i18n` exit 0; repo-wide `pnpm lint` exit 0 over **6970 files, 0 messages** — the full population, so no narrowing is claimed and none is owed. **Three exit 3s, all discharged** by building the closure the gate named, then re-run: `check:dual-build-cjs-loads` and `check:i18n` both wanted a full build (`turbo run build --concurrency=2 --filter='!@objectstack/docs'`, 73 tasks, exit 0); `check:type-check-debt` named `@objectstack/platform-objects` specifically. All three then exit 0, and the final sweep on the merged head produced no exit 3 at all. The `--` forwarding trap was avoided by construction: every build in this round is spelled `pnpm exec turbo run build --concurrency=2 --filter=...`. ## Changeset — measured, not assumed `@objectstack/platform-objects` declares no `private` and ships `files: ["dist", ...]`, so `skip-changeset` is measurably wrong. Probed in `dist` after a real build, with controls: | probe | raw | `\uXXXX` lower | `\uXXXX` UPPER | `\xNN` lower | `\xNN` UPPER | |:--|--:|--:|--:|--:|--:| | 种子数据 (`zh` `seed.label`) | 0 | 0 | **6** | 0 | 0 | | ケイパビリティ (`ja` `capability.label`) | 0 | 0 | **42** | 0 | 0 | | Documentación del paquete (`es`) | 0 | 0 | 0 | 0 | **6** | | `Endpoint API` (pure ASCII) | **6** | 6 | 6 | 6 | 6 | | NEGATIVE CONTROL — non-ASCII never written | 0 | 0 | 0 | 0 | 0 | | NEGATIVE CONTROL — ASCII never written | 0 | 0 | 0 | 0 | 0 | CJK ships as **UPPER-CASE** `\uXXXX`; U+0080–U+00FF as **UPPER-CASE** `\xNN`. Both negative controls read 0 everywhere, so a zero on the raw probe reads **"escaped"**, not "absent". The pure-ASCII probe reads 6 in every column because its escaped spellings are identical to its raw one — the sanity note that the counter is counting. `patch`. ## Acceptance notes **Zero cards filed, and that is a reading, not a silence.** The leaf that could have carried a known trap is `doc.description` / `capability.description`'s opening word `Package`: it *is* a legal `MetadataProvenanceSchema` value, both schemas really do accept `_provenance: "package"` and refuse a rendered one, and the check was performed **at the schema** and then **pinned in the ledger**, together with the derived assertion that the `en` metadata-forms catalog names **no** underscore-prefixed key anywhere. None of the five schema readings produced a defect: four of the words are not enum members at all, and the fifth is one an author never writes on these types because these types have no form. Two observations, neither filed, both prose here because neither carries a class or a carrier: - `permission.fields.systemPermissions.helpText` renders "system capability **keys**" as システム**機能**キー in `ja-JP`, using the word round 6 measured as wrong for a capability token and replaced on the `hook` and `action` panels. It is **not an echo** — it is authored — so it is outside this card's family, and it is not a defect against any declared contract: no runtime reads it. Whether the objects-catalog rendering (ケイパビリティ) should reach the `permission` panel is a wording question for whoever takes that panel. **承接者:无** — no open PR and no queued family touches `packages/platform-objects/src/apps/translations/` on the `permission` entry, so nothing is scheduled to pass through that file. Dedupe words: permission systemPermissions capability 機能 ja-JP. - `es-ES` answers "permission sets" two ways in the same catalog — `conjuntos de permisos` (`permission.sections.identity.description`) and the English `permission sets` (`position.sections.position.description`). This round took the Spanish form, because the leaf it describes is the permission domain's own definition side, and the row records the split rather than harmonising the other leaf. Not a defect; a recorded divergence. Dedupe words: es-ES permission sets conjuntos position panel. One scratch file (`packages/spec/probe-tmp.mts`, an exploratory census probe) was swept into a commit by a `git add -A` and removed in its own commit before the merge; the final diff is 8 files, all in `packages/platform-objects/src/apps/translations/` plus the changeset. --- _Generated by [Claude Code](https://claude.ai/code/session_01NcPSwnmJHczmTu6FG7NMjE)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
…classes, paced writes through claude[bot]; and the grading contract's measured gaps (objectstack-ai#19570) (objectstack-ai#19575) Fixes objectstack-ai#19570 Clause-②: no Tier H (`.claude/**` plus `scripts/pm/`) — the maintainer merges by hand. Draft; no ready, no auto-merge, no labels, no assignee written by this run. ## Provenance — the maintainer's own sentences, verbatim and untranslated > 19546 帮我派发处理 > 刚才 os-sam 又被封号了,也是因为分诊批量处理issue的时候,所以现在 issue 太多对于车队是负担。 19546 我希望在创建issue 的环节就卡死,没必要的issue就不要创建。 > 19546 卡片好像也丢了,要重新写。你创建一个新的吧,而且注意不要和之前的一模一样 > 之前封号的时候讨论过 issue 和 pr 应该使用 claude app 创建,名称应该是 claude(bot) The second sentence is quoted at the head of the new charter section itself (`references/filing-gate.md:10`), the fourth beside it (`:12`), so the door carries its own authority where it is read. ## Part A — the filing door, rule by rule, with the line that carries it | card rule | carried at | |:--|:--| | 1 · four classes or no card; the body's first line names the letter | `references/filing-gate.md:14` (headline), `:15`–`:18` (a/b/c/d), `SKILL.md:351`–`:352` (the door and its pointer, inside 〈分诊座位职责〉) | | 2 · named non-cards | `references/filing-gate.md:19`–`:24` — instrument misfire (pointing at `references/instrument-discipline.md`, which objectstack-ai#19513 landed this morning), seat mistakes and lessons, a `finding` outside the three classes, a lost-card rebuild, anything a comment can carry, a sweep-derived pin/bump reminder | | 3 · three answers before POST | `references/filing-gate.md:25`–`:28`; mirrored in `references/core-rules.md:80` | | 4 · quota (three per fire; groups of ten for stock cleanup) | `references/filing-gate.md:29`–`:30` | | 5 · paced writes | `references/filing-gate.md:31`–`:33`; the invariant every seat reads is `SKILL.md:99` | | 6 · writes go through the App identity `claude[bot]` | `references/filing-gate.md:34`–`:36`; `SKILL.md:99` carries the prohibition beside the pacing numbers | | the sweep reconciliation (first-touch closes only clear what predates the door) | `SKILL.md:391` | Rule 6's consequence is stated where it bites: a user-class write is bound to its author and 404s with the account (`references/platform-readings.md:135` is the standing reading, and `:130`–`:134` record that the class is set by the Claude account's GitHub connection rather than by the session and can flip mid-session). So each seat reads back `user.type` on its first write of a fire, records a `User` reading on the seat post and files nothing new that fire. The claim carrier stays the assignee and attribution stays the session ID in the text — nothing else in the protocol moves. ## Part B — the grading contract's measured gaps, with the line that carries each | card item | carried at | |:--|:--| | false green counts one human catch as one measured cost | `references/filing-gate.md:43` | | 「仪器为车队服务」 has four answers, never two; a false red's standard cost is a seat complying | `references/filing-gate.md:44`–`:45` | | chain inheritance is an upper bound, the failure shape decides the grade inside it | `references/filing-gate.md:46` | | the three-state checklist test, state (2) inheriting the item's priority | `references/filing-gate.md:40`–`:42`; `SKILL.md:373` names the three states in the principal text | | North Star item 4: shipped faces only; a wrong sentence, not a missing one | `references/filing-gate.md:47`–`:48` | | the decision box re-reads the premise in the same act | `SKILL.md:365` | | `pm:retriage` judged by content, not shape | `SKILL.md:400` | | a zero carries a control, and the control proves reach, not that the probe is right | already on `main` at `SKILL.md:168` and `references/core-rules.md:45` — nothing added, this PR only extends it | | exact-name matching, not prefix; hit counts lie too | `references/filing-gate.md:60` | | the reach radius, two arms, and the two corollaries | `references/filing-gate.md:52`–`:56` | | a git-ignored directory is a convention; the three-part zero | `references/filing-gate.md:57`–`:59` | | one board enumeration per fire, statistics offline, patrol daily | `references/filing-gate.md:61` | `SKILL.md:170` is the pointer that puts all of the reading criteria in the reader's path, immediately under the 零命中须配同主体必中词 line the card names. ## Why the reading criteria are in a new reference file rather than `platform-readings.md` The card asks for them in `references/platform-readings.md`. Measured against that file as it stands, they cannot land there without either a ceiling raise or a deleted rule, both of which the card forbids: - `platform-readings.md` is 469 lines at ceiling 469 (headroom 0), and **zero** of its adjacent bullet pairs merge under the 120-byte cap (the smallest pair is 135 bytes). - `dispatch-runbook.md` is the same shape: 241/241, zero mergeable pairs, smallest 122 bytes. So the charter text lands in a new `references/filing-gate.md`, following the pattern `references/instrument-discipline.md` set on `main` this morning (PR objectstack-ai#19513, merged as `3e8e2b0d`), and `SKILL.md` carries the pointers. One difference from that precedent, deliberately: this PR **adds the new file to the ratchet map** (`CEILINGS` and the table-row pin), at its landed count with headroom 0, because the map is an enumeration and a file merely absent from it is silently unratcheted. ## Ratchet — per-file counts, every added line paid in place | file | before | after | ceiling | how it was paid | |:--|--:|--:|--:|:--| | `.claude/skills/pm-dispatch/SKILL.md` | 815 | 819 | 819 | headroom 4 spent, plus three lines paid back: the three Acceptance-notes lines compress to one (their content is the door's rule 3), the two-level-inventory cadence lines merge into one, and the paced-writes invariant absorbs the identity prohibition without a new line | | `.claude/skills/pm-dispatch/references/core-rules.md` | 151 | 151 | 151 | net zero, two in-place amendments only (`:80` the three answers, `:84` the door) | | `.claude/skills/pm-dispatch/references/filing-gate.md` | — | 61 | 61 | new file, pinned at its landed count | | `.claude/agents/os-dev.md`, `AGENTS.md`, every other map entry | unchanged | unchanged | unchanged | not touched | No ceiling was raised. No rule was deleted to pay: every limb removed from `SKILL.md` is present in the new file (`git diff origin/main -- .claude/skills/pm-dispatch/SKILL.md` removes seven lines, and each one's content is at a line named above). ## Gates Derived on the final tree with `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` at `34a97c4` — 40 commands, identical to the list derived before the last two commits. All 40 were run with the exit code captured before any pipe; all exit 0. Named families: ``` check:pm-skill-ratchet exit 0 SKILL.md 819/819 · core-rules.md 151/151 · filing-gate.md 61/61 (+ self-test, 157 cases) check:pm-widening-tells exit 0 self-test, 525 cases (report-only family) check:pm-label-desc-cap exit 0 39 label descriptions, all at or under 100 characters check:pm-skill-id-lint exit 0 30 file(s) clean — no issue-number citation in the new charter text check:skill-frame-sync exit 0 77 markdown files scanned; the one declared copy of the decision frame is coherent check:nul-bytes exit 0 9134 text files, no raw control bytes check:pm-governed-prose exit 0 check:pm-governed-merges exit 0 check:pm-dispatch-gates exit 0 check:ratchet-remedy-authority exit 0 265 scripts swept check:self-test-wired exit 0 (the self-test-wired family, both arms) ``` Nine of the forty first returned exit 3, PREREQUISITE NOT MET, in a fresh worktree with no `node_modules`; they were re-run after `pnpm install` and, for `check:doc-formula-expressions`, after building `@objectstack/formula` and `@objectstack/lint` under the shared verify lock. Exit 3 is not a finding and was not read as one. ## Acceptance greps ``` $ git grep -n '三态\|会 fail' .claude .claude/skills/pm-dispatch/SKILL.md:373:- 清单项三态定级(会 fail / 步骤够不到 / 无项断言)与定级判据见 `references/filing-gate.md`。 .claude/skills/pm-dispatch/references/dispatch-runbook.md:149:…(pre-existing) .claude/skills/pm-dispatch/references/platform-readings.md:371:…(pre-existing) $ git grep -n '半径' .claude .claude/skills/pm-dispatch/SKILL.md:168, :169 (pre-existing, the line this PR extends) .claude/skills/pm-dispatch/references/core-rules.md:45 (pre-existing twin) .claude/skills/pm-dispatch/references/filing-gate.md:46, :48, :49, :50, :52 (the two arms and the corollaries) .claude/agents/os-dev.md:240, .claude/skills/pm-dispatch/references/platform-readings.md:280, .claude/skills/spec-property-retirement/SKILL.md:278, :281, :301 (pre-existing, unrelated) ``` The three states are at `references/filing-gate.md:40`–`:42`, which `SKILL.md:373` names. ## Collision with PR objectstack-ai#19513 It merged while this branch was in flight (`3e8e2b0d`, then `origin/main` moved on to `b3615f1a4c`). `origin/main` is merged into this branch rather than proved clean against it. The one conflict was in the triage seat's filing rules and is resolved in favour of both: objectstack-ai#19513's report-only-instrument line is kept verbatim and this branch's compressed Acceptance-notes question sits beside it. The door's instrument clause points at `references/instrument-discipline.md` instead of restating the no-dev half objectstack-ai#19513 landed. ## Not in this PR — pending the maintainer's word Each needs the maintainer's own sentence on its own card (ruling objectstack-ai#208 R6 — a new gate is not a maintenance edit): - a mechanical throttle inside `scripts/pm/post-stamped.mjs` and `scripts/pm/label-write.mjs` — this PR states the pacing numbers as charter only; - a refusal in `scripts/pm/label-write.mjs` when `domain:*` or `priority:*` is written with no pm-state label in the same write; - a `filed.log` receipt for newly filed cards; - the backup status written into the board snapshot itself. ## Changeset `skip-changeset` applies: nothing under `packages/**` is touched. The four paths are `.claude/skills/pm-dispatch/**` and `scripts/pm/check-skill-line-ratchet.mjs`, all on the fast track (internal protocol and PM tooling, not shipped by any package's `files[]`). ## 维护者速读(草稿) **改了什么**:给「立什么卡」装了一道门。今天起,一张 issue 只为四类事存在:有落点或能复现的产品缺陷、只有你能做的决定、你直派的任务、跨仓跨层必须的协调父单。其余一律不立卡 —— 仪器误报、席位自己的失误、三类外的 finding、丢卡重建、一条评论能承载的知会、sweep 每次都能重新推出来的提醒。立卡前必须在正文答三件:类别字母、谁会动手、查重跑了什么命中几条;缺一件分诊首触就关。配额:一席一轮最多三张。写节奏进章程:每笔间隔 3 秒、每账号每小时 40 笔封顶,429 就停写。写入身份恒走 App 的 `claude[bot]`,不走席位用户令牌。第二半是定级契约的十处实测缺口(假绿、四种答案、沿链继承是上界、清单三态、北极星第 4 条的两个限定、可达半径、零的三件判据等)。 **为什么改**:24 小时内两仓新立 183 张卡,其中 64 张当天就关、41 张是 finding;今天有两个席位账号因批量 issue 操作被封,它们立的卡跟着账号一起消失(本卡自己就是第二次重建)。成本已经不在开发队列,而在板面本身:每张开着的卡每轮都被每个席位扫一遍,每次写都是账号信誉的一笔支出。 **风险与代价(含回滚)**:门收紧后,真缺陷有被判成「不是卡」的可能 —— 对冲是类别 (a) 的定义没有变,仍是原来的 `pm:queue` 判据。识别口径变严会让分诊首触关掉一些格式不全的卡,这些卡重开免费。回滚是一次 `git revert`:本 PR 只改三份协议文本和一份门禁脚本的两行台账,不动任何运行时代码,回滚零迁移。 **席位意见**:(留空,待席位复审填写) **你要做的**:读一遍 `references/filing-gate.md` 的〈立卡门〉十六条,确认四类和两个数字(每轮三张、每小时四十笔、每笔三秒)是你要的口径;然后人工合并本 PR。另有四件被本 PR 明确挡在门外、等你一句话:label-write 的两道闸、filed.log、备份状态写进备份。 --- _Generated by [Claude Code](https://claude.ai/code/session_012GcsUbuqFGBibkEDMRC1eE)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
Fixes #19496
Clause-②: no
Tier H — the maintainer merges this by hand. The diff touches
AGENTS.mdand.claude/**, so one Tier H path makes the whole PR Tier H: no seat flips it ready, queues it, or arms auto-merge, and no agent account approves it.Authorization, verbatim and untranslated — maintainer, 2026-09-21 (ruling #208 on #19491):
What lands — five charter edits, text only
Nothing under
scripts/,.github/orpackages/is touched. The C5 code demotion, the patrol schedule and the CI self-test scoping are their own cards.R1 — a non-zero
--pairblocks landing only on rows that answer a definite question.SKILL.md〈入队与落地〉 andreferences/contract-review.md落地前检三条:AGENTS.mdPrime Directive #14 is where the conjunct lives —git grep -n -- '--pair' AGENTS.md .claudeis the census (1 hit inAGENTS.md, 5 in.claude). Tier S now readsreads 0 on its definite rows (⛔ never C5) and every check is green; the paragraph was re-wrapped from that sentence onward andAGENTS.mdstays at 1109 lines.R2 — the rule line, next to the tooling rules ruling #202 B landed, in 〈分诊座位职责〉's filing classes:
R3 (charter half) — the patrol anchor stops being a precondition. The two 〈执行座位职责〉 lines are deleted; one line replaces them, mirrored in
references/core-rules.md:R5 — at-tier review: scope and shape, in
references/contract-review.md〈复核归属与资格〉:Served-tier:and the record shape are unchanged.check-clause2-carriers --templateprints nothing that contradicts the shape: its ③ is the record's boundary-flag heading and it prescribes no local gate run, so no script edit was needed (checked; reported below as a reading, not a finding).R6 — 〈仪器纪律〉 is a new reference,
references/instrument-discipline.md(11 lines), with exactly one pointer line in SKILL.md 〈平台读数纪律〉, placed one line below the advisory-red rule:The section itself:
Why a new file rather than
landing-operations.md. Every file an at-tier reader already opens for landing or gates stands at its ceiling with zero headroom —landing-operations.md69/69,true-green.md32/32,review-checklist.md77/77 — and the section costs 11 lines, so hosting it in one of them means deleting live rules, which this card forbids. The pointer instead sits where the reader deciding what a gate reading means already is: 〈平台读数纪律〉, right after the two lines on a gate job's conclusion and an advisory red. The new file carries noCEILINGSrow, because that row is an edit underscripts/**(out of scope here) and R6's own rule says a new ratchet needs the maintainer's sentence on its own card — reported below.The line ratchet — no ceiling raised, every added line paid in place
pnpm check:pm-skill-ratchetexit 0. Per-file counts on headfec2177089, after mergingorigin/main:.claude/skills/pm-dispatch/SKILL.md.claude/skills/pm-dispatch/references/contract-review.md.claude/skills/pm-dispatch/references/core-rules.mdAGENTS.md.claude/skills/pm-dispatch/references/instrument-discipline.mdWhat paid for the added lines — de-duplication only, no rule deleted, each surviving carrier named:
needs:contract-review(恒英文)由席位同笔挂:PR 一现即挂 PR;报告先到则先挂卡); its unique tail, ACCEPT 补齐 PR 侧, rides the next line.Implemented-by:/Reviewed-by:spelling lines became one pointer at--template, which prints both fields verbatim — and the record-shape line three above already cites that template.references/lanes/director.md's pointer at this block still resolves, and SKILL.md 〈复核〉 carries the full two-tier rule.AGENTS.md: 43 added bytes absorbed into the paragraph's own slack by re-wrapping from the edited sentence onward; no line was bought.Collision — both charter PRs merge clean
claude/issue-19457-charter-product-only-queue, head36ab00aa) merged intomainduring this round.git merge-tree --write-tree 36ab00aa HEADwas exit 0 with zero conflict markers before that, and this branch then mergedorigin/main(48c39e00) with no conflict. Itstoolinglabel rules, its two gate-false-positive lines and its ratchet bump (SKILL.md 813 → 819) are all present on this head.claude/issue-19483-feature-axis-charter): the card named head420bd091; the branch tip is now16418377.git merge-tree --write-tree 16418377 fec2177089→ exit 0, tree02a7323a2d779974bd035082ad954eaf4cd15a21, zero conflict markers. Every line that PR touches is untouched here.Acceptance
git grep -n '0 才请审' .claudegit grep -n -- '--pair' .claudegit grep -n '锚行未处置' .claudegit grep -n '半状态' .claudegit grep -n 'C5' …/references/contract-review.mdGates
Derived on this diff with
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack— 23 commands; the derivation also names 11 wide-population, 53 artifact-roster, 14 pending-changeset and 2 CI-valued families as outside that list, so this is not a complete account of CI. Every exit code was captured before any pipe (cmd > log 2>&1; e=$?). Reconciled with--ran: 23 derived, 23 run, 0 NOT-MEASURED, 0 UNRUN.Three more, run because this card names them:
Two gates first answered
exit 3(PREREQUISITE NOT MET) in a fresh worktree and were re-run afterpnpm install, andcheck:doc-formula-expressionsafterpnpm exec turbo run build --filter=@objectstack/formula --filter=@objectstack/lintunder the shared verify lock (VERDICT command-exit 0). Those 3s are recorded as what they are — nothing measured, not a finding.skip-changesetapplies: the diff is.claude/**plusAGENTS.md, nothing underpackages/**, and no publishedfiles[]content moves.Acceptance notes — out of scope, not filed by the dev
references/instrument-discipline.mdcarries noCEILINGSrow, so it is the one un-ratcheted file on the pm-dispatch surface. Adding the row is an edit underscripts/**, and by R6's own rule a new ratchet needs the maintainer's sentence on its card. Dedupe words: CEILINGS, instrument-discipline, ratchet row, un-ratcheted reference.check-clause2-carriers --templateprints no line contradicting R5's shape (checked; no script edit).维护者速读(草稿)
改了什么 — 把「仪器」这件事写成纪律:
--pair这类硬门禁只在能给出确定答案的行上挡落地(复核记录在不在、档位行在不在、两个标签载体一不一致、认领形对不对);猜意图的那一行(C5 放宽 tell)从此只印读数,由达档复核的人判。半状态巡查从「每轮派发的前置条件」降为「按需跑的读数」。达档复核的范围收到「会出货给用户或 agent 的东西」,形状收到「读 diff 加读 CI 的 check-runs」,不再本地重跑门禁。新增一页〈仪器纪律〉,把这几条连同「只报告的仪器不配 dev」「新增门禁要维护者一句话」「工具位先花在删除上」写在一起。为什么改 — 裁决 #208 的实测:工具链自己占了三到四成的合并量、每个 PR 三分之二的 CI 关键路径;把 #19314 挡住的那道门,本体只是两句章程话,不是 CI 门禁。一次达档复核 24 万 token、29 分钟,其中最大一块是在本地重跑 CI 已经跑过的 37 个门禁族。这三件都不是删代码能解决的,是纪律写错了地方。
风险与代价(含回滚) — 代价是硬门变软:C5 不再挡人,漏网要靠复核的人看见。回滚是一次 revert,因为全是文本。棘轮一行没抬,新增的行全部用去重付账,幸存载体逐条点名在上面;唯一的新面是那一页新文件,它暂时没有棘轮行。
席位意见 —
你要做的 — 读这五处改动,同意就人工合并(Tier H,队列与 auto-merge 都不适用)。
Round 2 (seat's note)
Head
96774e44ef. The at-tier review (5755678024, FAIL) named three items; all fixed in one push: R5's obligation now keys on the face, not the lane (references/contract-review.md〈复核归属与资格(按面)〉 lines 24–27, SKILL.md line 647, plus the two lane-keyed twins at SKILL.md 533 and core-rules.md 113 — 「三面」 is now 「五面」 and the published schema names its pathpackages/spec/src/**non-test);origin/main(ea64bbc6e8) merged with the SKILL.md 180/181 conflict resolved keep-both;instrument-discipline.mdcites ruling 208 / card 19491 / comment 5755284987 with bare ids (the#spelling is whatcheck:pm-skill-id-lintrefuses). Ratchet: SKILL.md 815/819, contract-review.md 60/60, core-rules.md 151/151, AGENTS.md 1109/1109.CI: this head has zero GitHub-Actions runs — a
.claude/**plusAGENTS.mddiff matches no PR-level workflow paths; the merge queue'smerge_grouprun supplies the required contexts, so the empty check list is the known shape and not a stall.Generated by Claude Code
Generated by Claude Code