Skip to content

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

Merged
os-zhuang merged 6 commits into
mainfrom
claude/issue-19496-instrument-discipline-charter
Sep 21, 2026
Merged

os-zhuang merged 6 commits into
mainfrom
claude/issue-19496-instrument-discipline-charter

Conversation

@os-project-manager

@os-project-manager os-project-manager commented Sep 21, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #19496

Clause-②: no

Tier H — the maintainer merges this by hand. The diff touches AGENTS.md and .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):

「19491 接受你的建议,并立刻派发处理相关任务。」

What lands — five charter edits, text only

Nothing under scripts/, .github/ or packages/ is touched. The C5 code demotion, the patrol schedule and the CI self-test scoping are their own cards.

R1 — a non-zero --pair blocks landing only on rows that answer a definite question. SKILL.md 〈入队与落地〉 and references/contract-review.md 落地前检三条:

- `Clause-②: yes` 认领同笔卡上挂标;开 PR 跑 `--pair N`:只确定性行红才挡请审,C5 只印读数。
- 0 = 确定性行全清;4 = 任一不成立,只确定性行红才挡落地;3 = 环境答不了 ⛔ 不作干净。
- 确定性行 = 记录在案、`Served-tier:`、双载体一致、认领形;C5 放宽 tell 只报告,归复核裁。

AGENTS.md Prime Directive #14 is where the conjunct lives — git grep -n -- '--pair' AGENTS.md .claude is the census (1 hit in AGENTS.md, 5 in .claude). Tier S now reads reads 0 on its definite rows (⛔ never C5) and every check is green; the paragraph was re-wrapped from that sentence onward and AGENTS.md stays at 1109 lines.

R2 — the rule line, next to the tooling rules ruling #202 B landed, in 〈分诊座位职责〉's filing classes:

- 只报告的仪器,报错不配 dev:猜意图的只印读数,误报席位一句推翻,⛔ 不立卡不派 dev。

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:

SKILL.md      - 半状态巡查按需跑(分诊席每日对账可调),H 行是读数不是前提;⛔ 不因锚行停派发。
core-rules.md - 半状态巡查按需跑,其 H 行是读数不是前提,⛔ 不因锚行停派发。

R5 — at-tier review: scope and shape, in references/contract-review.md 〈复核归属与资格〉:

- 复核面 = 出货给用户或 agent 的:`content/docs/**`、`apps/docs/**`、CHANGELOG/`.changeset` 散文。
- 同含已发布 schema 与 governed 规则文本;三面皆不碰 ⇒ CI 加席位自读,⛔ 不起第二个 agent。
- 复核形状:只读 diff 与卡片,check 结论取 head 的 check-runs,⛔ 永不本地重跑派生门禁族。

Served-tier: and the record shape are unchanged. check-clause2-carriers --template prints 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:

- 仪器纪律(硬门禁面、只报告面、新增授权、工具位)见 `references/instrument-discipline.md`。

The section itself:

- 硬门禁只答有确定答案的问题:受管登记表、队列守卫、CI 测试、复核记录在不在。
- 判意图的仪器(放宽 tell、半状态巡查)只印读数 ⛔ 不挡落地,误报由席位一句话推翻。
- 放宽 tell(C5)由 `scripts/pm/check-widening-tells.mjs` 印 file:line,归达档复核裁。
- 只报告的仪器不配 dev:⛔ 不立卡、不派 dev、不开 PR;它的在途工作只有删除。
- 新增门禁、巡查行或棘轮须在卡上引维护者原话,⛔ 无原话不新增;首行四件恒硬。
- 工具位只有一个,先花在删除上;`dispatch-gates.mjs` 冻结,只在它喂的 workflow 坏了时碰。
- 出处:维护者 2026-09-21 逐字「19491 接受你的建议,并立刻派发处理相关任务。」

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.md 69/69, true-green.md 32/32, review-checklist.md 77/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 no CEILINGS row, because that row is an edit under scripts/** (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-ratchet exit 0. Per-file counts on head fec2177089, after merging origin/main:

file lines ceiling headroom net this PR
.claude/skills/pm-dispatch/SKILL.md 819 819 0 0
.claude/skills/pm-dispatch/references/contract-review.md 60 60 0 0
.claude/skills/pm-dispatch/references/core-rules.md 151 151 0 0
AGENTS.md 1109 1109 0 0
.claude/skills/pm-dispatch/references/instrument-discipline.md 11 none — new file

What paid for the added lines — de-duplication only, no rule deleted, each surviving carrier named:

  • SKILL.md 〈复核〉's two 受管面两层 lines became one. The Tier H enumeration it dropped is the 〈复核〉 line three above it (governed 面统一定义), and 四件套 is stated at 路径面命中规则层 ⇒ ACCEPT 换终局四件套.
  • contract-review.md's 双载体同笔挂 line is stated in SKILL.md 〈入队与落地〉 (needs:contract-review(恒英文)由席位同笔挂:PR 一现即挂 PR;报告先到则先挂卡); its unique tail, ACCEPT 补齐 PR 侧, rides the next line.
  • contract-review.md's two 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.
  • contract-review.md's standalone Tier H/S landing line left; the tier outcome now rides the 落地前检三条 line itself, so 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

Acceptance

grep result
git grep -n '0 才请审' .claude 0 hits (exit 1)
control git grep -n -- '--pair' .claude 5 hits (os-dev.md 1, SKILL.md 1, contract-review.md 2, platform-readings.md 1)
git grep -n '锚行未处置' .claude 0 hits (exit 1)
control git grep -n '半状态' .claude 24 hits across 8 files
git grep -n 'C5' …/references/contract-review.md 1 hit, naming it 只报告

Gates

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.

node scripts/check-closing-keyword-parity.mjs :: exit 0
node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0
node scripts/check-comment-mask-corpus.mjs :: exit 0
node scripts/pm/check-governed-queue-guard.mjs --self-test :: exit 0
node scripts/pm/check-harness-current.mjs --self-test :: exit 0
pnpm --filter @objectstack/lint run check:doc-formula-expressions :: exit 0
pnpm check:agent-test-spelling :: exit 0
pnpm check:cross-package-test-inputs :: exit 0
pnpm check:doc-authoring :: exit 0
pnpm check:docs-audit-scope :: exit 0
pnpm check:driver-memory-census :: exit 0
pnpm check:gitlink-declared :: exit 0
pnpm check:nul-bytes :: exit 0
pnpm check:pm-expected-skips :: exit 0
pnpm check:pm-governed-merges :: exit 0
pnpm check:pm-governed-prose :: exit 0
pnpm check:pm-half-states :: exit 0
pnpm check:pm-skill-id-lint :: exit 0
pnpm check:pm-skill-ratchet :: exit 0
pnpm check:refd-timer-probe :: exit 0
pnpm check:required-contexts :: exit 0
pnpm check:skill-frame-sync :: exit 0
pnpm check:watch-hint-literal :: exit 0

Three more, run because this card names them:

pnpm check:pm-widening-tells :: exit 0      (self-test only — 525 cases; checker health, not a verdict on this diff)
node scripts/pm/check-widening-tells.mjs --declaration no --diff pr.diff :: exit 0   (a real verdict: no tell)
pnpm check:pm-label-desc-cap :: exit 0
pnpm check:pm-settings-deny-roster :: exit 0   (its roster lives under .claude, which this diff is in)

Two gates first answered exit 3 (PREREQUISITE NOT MET) in a fresh worktree and were re-run after pnpm install, and check:doc-formula-expressions after pnpm exec turbo run build --filter=@objectstack/formula --filter=@objectstack/lint under the shared verify lock (VERDICT command-exit 0). Those 3s are recorded as what they are — nothing measured, not a finding.

skip-changeset applies: the diff is .claude/** plus AGENTS.md, nothing under packages/**, and no published files[] content moves.

Acceptance notes — out of scope, not filed by the dev

  • SKILL.md 〈状态模型〉 still says 派发与折叠检查时读半状态巡查锚的 H17 触发文件索引. With the patrol's schedule retired on its own card, that index can go stale; this card's R3 scope was the two 〈执行座位职责〉 lines only. Dedupe words: 半状态巡查锚, H17, 触发文件索引, 折叠检查.
  • references/instrument-discipline.md carries no CEILINGS row, so it is the one un-ratcheted file on the pm-dispatch surface. Adding the row is an edit under scripts/**, 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 --template prints 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 path packages/spec/src/** non-test); origin/main (ea64bbc6e8) merged with the SKILL.md 180/181 conflict resolved keep-both; instrument-discipline.md cites ruling 208 / card 19491 / comment 5755284987 with bare ids (the # spelling is what check:pm-skill-id-lint refuses). 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/** plus AGENTS.md diff matches no PR-level workflow paths; the merge queue's merge_group run 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

… 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>
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>

Copy link
Copy Markdown
Collaborator Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: fec2177089b0628cb9692739b69aae4514f25ed0

Tier H (.claude/** + AGENTS.md): this is the at-tier contract review, not the landing record — the maintainer merges by hand. Shape = ruling #208 R5 applied to the PR that writes it: the diff and card #19496 read in a fresh worktree at origin/main, ③ taken from the head's check-runs via REST, no derived gate family re-run; the only local commands were check-skill-line-ratchet.mjs, git grep probes and git merge-tree. Inputs: card #19496, the ruling record on #19491 (comment 5755284987, 2026-09-21T04:12Z), the dev report on #19496 (comment 5755602587, 2026-09-21T04:57Z), the PR body.

① Derived judgments

  1. Scope — RIGHT. git diff origin/main...fec2177089 --name-status = M SKILL.md, M references/contract-review.md, M references/core-rules.md, A references/instrument-discipline.md, M AGENTS.md; nothing under scripts/, .github/, packages/. The mid-round merge commit 28f852b7a9 (parents f706aca132, 48c39e0023) has tree 5271eefc04, identical to git merge-tree --write-tree f706aca132 48c39e0023 — a clean merge carrying only main's own content, no hand edits; the three-dot diff against merge-base 48c39e00 is the PR's own change only.

  2. R1 — RIGHT. The new sentences: SKILL.md:657 「Clause-②: yes 认领同笔卡上挂标;开 PR 跑 --pair N:只确定性行红才挡请审,C5 只印读数。」; contract-review.md:43 「0 = 确定性行全清;4 = 任一不成立,只确定性行红才挡落地;3 = 环境答不了 ⛔ 不作干净。」; :44 「确定性行 = 记录在案、Served-tier:、双载体一致、认领形;C5 放宽 tell 只报告,归复核裁。」; AGENTS.md:274-275 (Prime Directive feat: Comprehensive CRM example demonstrating all ObjectStack protocol features #14, Tier S conjunct) 「check-clause2-carriers.mjs --pair N reads 0 on its definite rows (⛔ never C5) and every check is green」 — the word-diff of AGENTS.md is that one insertion; the rest is re-wrap. A seat can tell C5 from the definite rows: the four definite rows are enumerated and C5 is named report-only, so 「只确定性行红才挡」 can only be evaluated by reading which rows are red; no sentence says in words 「read the ✗ rows」, but the exit code alone is not offered as the verdict anywhere. git grep -n -- '--pair' AGENTS.md .claude at the head = 6 hits: os-dev.md:303 (exit code 「作读数」), SKILL.md:657, contract-review.md:42 (the invocation) and :55 (Served-tier: ≠ constant ⇒ exit 4 — a definite row), platform-readings.md:120 (the 403 note), AGENTS.md:274. No remaining sentence makes a bare exit≠0 a block. Acceptance: git grep -n '0 才请审' .claude = 0 with control --pair 5 hits in .claude.

  3. R2 — RIGHT, one note. Present once, SKILL.md:359 「只报告的仪器,报错不配 dev:猜意图的只印读数,误报席位一句推翻,⛔ 不立卡不派 dev。」, inside 〈分诊座位职责〉's finding-class block, seven lines below docs(pm-dispatch): the dev queue is product-only — tooling cards close at first grading, broken gates are deleted, ≤1 tooling dev in flight, tooling is a first-touch label (ruling #202 B) #19462's 「门禁头注、self-test 文案与 check-* 处方句 ⛔ 非已声明契约」 (:352) and fourteen below its tooling 入 pm:queue line (:345) — beside the filing rules, where a triage seat decides whether to file. Reads as a rule. Note: the card's sentence is 166 bytes; the line is 119, dropping the parenthetical (放宽 tell、巡查行) and 「不开 PR」 — both survive in instrument-discipline.md:6 and :8.

  4. R3 — RIGHT. git grep -n '锚行未处置' .claude = 0 with control 半状态 = 24 hits; the two 〈执行座位职责〉 lines are gone; SKILL.md:432 「半状态巡查按需跑(分诊席每日对账可调),H 行是读数不是前提;⛔ 不因锚行停派发。」 replaces them; mirrored at core-rules.md:102 「半状态巡查按需跑,其 H 行是读数不是前提,⛔ 不因锚行停派发。」. The dev's flag, 〈状态模型〉 SKILL.md:134 「派发与折叠检查时读半状态巡查锚的 H17 触发文件索引,与本次派发文件面求交。」: it prescribes a read-and-intersect and the next line a per-hold handling; neither says stop dispatching, so it does not contradict the on-demand line — it goes stale once ci(pm): retire the half-state patrol's schedule — on demand only; every scheduled invocation of the patrol action stops (ruling #208 on #19491) #19497 retires the schedule and the index stops refreshing. Reported, not failed; carrier ci(pm): retire the half-state patrol's schedule — on demand only; every scheduled invocation of the patrol action stops (ruling #208 on #19491) #19497.

  5. R5 — WRONG. The scope and shape sentences are in contract-review.md 〈复核归属与资格〉 and match the card: :26 「复核面 = 出货给用户或 agent 的:content/docs/**、apps/docs/**、CHANGELOG/.changeset 散文。」, :27 「同含已发布 schema 与 governed 规则文本;三面皆不碰 ⇒ CI 加席位自读,⛔ 不起第二个 agent。」, :33 「复核形状:只读 diff 与卡片,check 结论取 head 的 check-runs,⛔ 永不本地重跑派生门禁族。」; Served-tier: (:30) and the record shape (:28-:32) are unchanged. Two defects:

    • The older sentences were not replaced and now contradict the scope. :24 「归属派发席,交付后收集复核当轮完成;只 spec 与 skills 车道欠,新 spec 工作恒归 spec 席。」, :25 「按车道:spec 与 skills 席审契约增量;达档席内审,未达档 ⛔ 不自审,起达档子代理。」 and SKILL.md:651 「交付后复核只 spec 与 skills 车道欠,每轮达档:席内审或起子代理;双肢命中即 spec 车道。」 all stand. SKILL.md:254 assigns content/docs/** and apps/docs to domain:devx. So a devx-lane docs PR is owed the review by :26 (the card's R5(a): owed on a PR when its diff touches what ships) and not owed by :24 and SKILL.md:651; no line says which wins, and the ruling's evidence for R5 (six catches, all in docs / CHANGELOG / rules text) is exactly the class :24 excludes. Fix: rewrite :24-:25 and SKILL.md:651 so the obligation keys on the face (:26-:27) and the lane only names who runs it.
    • 「三面皆不碰」 miscounts. :26-:27 enumerate five faces (content/docs, apps/docs, CHANGELOG/.changeset prose, published schema, governed rules text). A reader who takes 三面 as :26's three items lands a schema-only or rules-text-only PR with no second agent — the opposite of the ruling. Fix: 「以上皆不碰」 or 「五面」. Minor, same fix: 「已发布 schema」 names no path (card: packages/spec/src/** non-test exports); it resolves only through SKILL.md:649's 路径肢, not in this file.
  6. R6 — RIGHT. references/instrument-discipline.md (11 lines) carries the six clauses: :5 hard gates only for definite questions (受管登记表、队列守卫、CI 测试、复核记录), :6 guessing instruments report-only, :8 report-only earns no dev (不立卡、不派 dev、不开 PR), :9 a new gate / patrol row / ratchet needs the maintainer's sentence on the card, :10 the single tooling slot goes to removals first and dispatch-gates.mjs stays frozen; :7 carries the widening tell's script and file:line (the sentence R1 removed from contract-review.md); :11 quotes the maintainer's sentence as provenance (the ruling number 🔗 Broken links detected in documentation #208 and the comment id are not cited — the reader greps 19491). Exactly one pointer: SKILL.md:180 「仪器纪律(硬门禁面、只报告面、新增授权、工具位)见 references/instrument-discipline.md。」. 120-byte cap: no added non-table line exceeds 120 bytes in any of the five files; the one non-table line over 120 in SKILL.md (:172) is pre-existing on main inside a template code block. The new file has no CEILINGS row (dev reported; scripts/** is out of scope and R6 :9 applies).

  7. Ratchet — RIGHT. node scripts/pm/check-skill-line-ratchet.mjs at the head: exit 0; SKILL.md 819/819, contract-review.md 60/60, core-rules.md 151/151, AGENTS.md 1109/1109, all headroom 0. Every deleted line read: SKILL.md 〈复核〉's two 受管面两层 lines → one (the Tier H enumeration survives at :617-:618 governed 面统一定义 + AGENTS.md/CLAUDE.md; 四件套 at :619; 等人批 kept); SKILL.md:649's invocation spelling survives at contract-review.md:42; contract-review.md's 双载体同笔挂 line survives at SKILL.md:656 with its ACCEPT tail on :15; the Implemented-by:/Reviewed-by: semantics survive verbatim in --template (checked: 「a mode:subagent dev's BRANCH, a mode:remote dev's session id」, 「the session that RENDERS or ADOPTS the verdict」); 「(spec、skills 每轮)」 survives at :24; the standalone Tier H/S line folds into :41 with 「one Tier H path makes the whole PR Tier H」 kept canonical at AGENTS.md:268. The two substantive deletions — the patrol precondition and C5's 「no 撞新键/成员/导出/登记即拒」 — are the ruling's own R3 and R1, not payment. No rule was deleted to pay for a line.

  8. Collision — RIGHT against docs(north-star,pm-dispatch): the feature axis — area:* labels, the 「路」 section as an ordered list of feature points, cross-layer features as parent + per-layer sub-issues, domain:* retreats to file ownership (#19483) #19488, WRONG against main. git merge-tree --write-tree 410c37f7 fec2177089 (PR docs(north-star,pm-dispatch): the feature axis — area:* labels, the 「路」 section as an ordered list of feature points, cross-layer features as parent + per-layer sub-issues, domain:* retreats to file ownership (#19483) #19488's current head) exit 0, tree 06229ec980, zero conflict markers; merged SKILL.md 819 lines against ceiling 819, core-rules.md 151, contract-review.md 60, AGENTS.md 1109. But origin/main moved again after the PR's mid-round merge of 48c39e00: fix(pm): the claim HANDOVER protocol — one comment, four items, provenance instead of a liveness test; the reader accepts it and C9 keeps one red #19502 landed at 9dd93190b6 and git merge-tree --write-tree origin/main fec2177089 exits 1 — CONFLICT (content) in SKILL.md at :180-:181, where the PR's pointer line is inserted directly above the line fix(pm): the claim HANDOVER protocol — one comment, four items, provenance instead of a liveness test; the reader accepts it and C9 keeps one red #19502 rewrote (「走死认领回收」→「走接管(见认领节)」); core-rules.md auto-merges. GitHub reports the PR mergeable_state: dirty. The resolution is keep-both and the merged SKILL.md would be 815 lines under the unchanged ceiling 819, but the maintainer cannot merge by hand until the branch merges main again, which moves the head this record names.

② Semver level

Not applicable: charter text only, no package content; skip-changeset is on the PR and is correct (nothing under packages/**, no published files[] content moves).

③ Boundary flags

  • dev flag 〈状态模型〉 H17 index: answered in ①4 — stale after ci(pm): retire the half-state patrol's schedule — on demand only; every scheduled invocation of the patrol action stops (ruling #208 on #19491) #19497, not contradicting; carrier ci(pm): retire the half-state patrol's schedule — on demand only; every scheduled invocation of the patrol action stops (ruling #208 on #19491) #19497.
  • dev flag no CEILINGS row for the new reference: acknowledged; out of scope here (scripts/**); by R6 :9 the row needs the maintainer's sentence on its own card.
  • dev reading --template contradicts nothing in R5's shape: confirmed — the template's ③ is the boundary-flag heading and it prescribes no local gate run.
  • dev reading lanes/director.md:32 still resolves: confirmed — 「落地前检③」 is contract-review.md:45.
  • open_questions: none.
  • PR checks on head fec2177089: NOT MEASURED — absent, not in progress. The head has zero GitHub Actions check-runs and the branch has zero workflow runs on any of its four commits; the only check-suites are vercel / fly-io / claude / cloudflare-workers-and-pages (queued, 0 runs) and the commit status is Vercel success. All seven required contexts are absent: TypeScript Type Check, Test Core, Dogfood Regression Gate, Build Core, Temporal Conformance (live PG + MySQL), Lint & Repo Gates, Governed Surface Queue Guard. Non-blocking for the content verdict under R5's shape, but the hand merge needs them; the four commits were pushed by the claude app, whose pushes do not fire pull_request workflows, so the next push should be checked for a run. skip-changeset is present, so Check Changeset needs nothing further.

Implemented-by: claude/issue-19496-instrument-discipline-charter
Reviewed-by: session_012GcsUbuqFGBibkEDMRC1eE

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 domain:devx owns, and 「三面皆不碰」 names three faces where five are enumerated. The patch is three lines plus a merge of origin/main for the SKILL.md:180 conflict (①8), which the head needs anyway; R1, R2, R3, R6, the ratchet and the #19488 collision proof all hold and need no change. The next head needs a fresh record.

Isolated at-tier reviewer (mode:subagent of the director seat), reading time 2026-09-21T05:08Z; worktree at origin/main 9dd93190b6, head checked out fec2177089.


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>

Copy link
Copy Markdown
Collaborator Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 96774e44efec6ac4cf9ba63e5cd2392d729bc12e

Tier H (.claude/** + AGENTS.md): the at-tier contract review of round 2, not the landing record — the maintainer merges by hand. Head fec2177089 was reviewed in full at comment 5755678024 (2026-09-21T05:08Z; FAIL on ①5 R5, the origin/main conflict and absent CI); this record judges the round-2 delta git diff fec2177089 96774e44ef under the same R5 shape: diff and card read in a fresh worktree at the head, ③ from the head's check-runs via REST, no derived gate family re-run; local commands were check-skill-line-ratchet.mjs, git grep probes and git merge-tree. Fresh fetch: origin/main = ea64bbc6e8; PR #19488's remote tip is still 410c37f7.

① Derived judgments

  1. Delta scope — RIGHT. Two commits: the merge 4651c28c13 (parents fec2177089, ea64bbc6e8) and the fix 96774e44ef. The three-dot diff against origin/main is still exactly the five files (SKILL.md, references/contract-review.md, references/core-rules.md, new references/instrument-discipline.md, AGENTS.md); nothing under scripts/, .github/, packages/. The merge brings only main's content: the added/removed line set of the merge against its second parent ea64bbc6e8, restricted to the PR's files, is identical to the PR's round-1 diff (diff-of-diffs empty), so the merge carries nothing but the PR's own hunks re-based; against its first parent it touches only what main gained (SKILL.md 24 lines and core-rules.md 8 from fix(pm): the claim HANDOVER protocol — one comment, four items, provenance instead of a liveness test; the reader accepts it and C9 keeps one red #19502 + docs(pm-dispatch): enter the maintainer's 插队 and 契约面卡 tiers into the 取卡全序 (SKILL.md + core-rules twin) #19506, os-dev.md 2). The one keep-both resolution is context, not content — SKILL.md:180-181 now read 「- 仪器纪律(硬门禁面、只报告面、新增授权、工具位)见 references/instrument-discipline.md。」 directly above main's 「- dev 自己死了不等于维护者中止:子代理消失是正常死法,走接管(见认领节)。」 (fix(pm): the claim HANDOVER protocol — one comment, four items, provenance instead of a liveness test; the reader accepts it and C9 keeps one red #19502's rewrite kept, the pointer kept). The fix commit touches exactly the named lines: SKILL.md:533 and :647, contract-review.md:22-27, core-rules.md:113, instrument-discipline.md:+12.

  2. R5 re-judged — RIGHT. contract-review.md:22 「## 复核归属与资格(按面)」; :24 「欠不欠按面判 ⛔ 不按车道:diff 碰下列任一面即欠达档复核,交付后当轮完成。」; :25 「复核面 = 出货给用户或 agent 的五处:content/docs/**、apps/docs/**、CHANGELOG/.changeset 散文。」; :26 「加已发布 schema(packages/spec/src/** 非测试)与 governed 规则文本(统一定义见 SKILL.md)。」; :27 「五面皆不碰 ⇒ CI 加席位自读 ⛔ 不起第二个 agent;谁跑 = 派发席,达档者席内审。」; SKILL.md:647 「交付后复核按面欠 ⛔ 不按车道(五面见 references/contract-review.md);双肢命中即 spec 车道。」; SKILL.md:533 「条款②自审资格按车道:spec 与 skills 席达档席内审;未达档 ⛔ 不自审,起隔离达档子代理。」; core-rules.md:113 「…语义面卡恒契约复审档施工,契约复核按面欠不按车道。」. A devx-lane docs PR (SKILL.md:254 assigns content/docs/** and apps/docs to domain:devx) now reads as owed, unambiguously: :24 keys the obligation on the face, :25 names those paths, and 「不按车道」 is stated three times (:24, SKILL.md:647, core-rules.md:113). The lane sentence that survives (SKILL.md:533) is eligibility only — which seats sit at tier and may self-review, in the 席位档策略 block next to :532 — and :27 says who runs (the dispatching seat, in-seat if at tier); 「双肢命中即 spec 车道」 on :647 assigns ownership of the path/declaration limb, not the obligation. All five faces carry paths or a resolving pointer: 「governed 规则文本(统一定义见 SKILL.md)」 resolves to SKILL.md:613-614 (docs/adr/** + .claude/** + skills/** + docs/NORTH-STAR.md + AGENTS.md + CLAUDE.md), a superset of the card's four rules-text paths and consistent with the ruling's 「rules text」. Greps at the head: 「只 spec 与 skills 车道欠」 0, 「三面皆不碰」 0, 「spec、skills 欠」 0, 「只 spec 与 skills」 0; 「按车道」 remains only on :533 (eligibility), the three 「不按车道」 sentences, and the file's H1. No sentence still contradicts. Residual, not a contradiction: the H1 at contract-review.md:1 still reads 「# 契约复核细则(按车道)」 — a stale title, not a rule; the section heading and :24 override it; worth flipping to (按面) on the next touch at zero ratchet cost.

  3. R6 provenance addition — RIGHT. instrument-discipline.md:12 「裁决记录:裁决 208,卡 19491,评论 5755284987(编号不带 # 前缀,id-lint 拒)。」 cites the ruling, the card and the comment id with bare ids, closing round 1's note. The literal # in 「# 前缀」 is hash-space; check-skill-id-lint.mjs:57 refuses ID_PATTERN = /#[0-9]{3,}/, which this line does not match (read, not run). Still exactly one pointer (SKILL.md:180); the file is 12 lines and still has no CEILINGS row (unchanged, out of scope here). No added non-table line in the round-2 delta or the full three-dot diff exceeds 120 bytes. AGENTS.md's word-diff against origin/main is still the single insertion on its definite rows (⛔ never C5).

  4. Ratchet — RIGHT. node scripts/pm/check-skill-line-ratchet.mjs at the head: exit 0; SKILL.md 815/819 (headroom 4 comes from main's fix(pm): the claim HANDOVER protocol — one comment, four items, provenance instead of a liveness test; the reader accepts it and C9 keeps one red #19502, not from this PR — main is 815 and the PR is net 0 against it), contract-review.md 60/60, core-rules.md 151/151, AGENTS.md 1109/1109. Lines deleted this round, read: contract-review.md's four lines :24-:27 became four; the content dropped is 「归属派发席,交付后收集复核当轮完成」 (survives as :24 「交付后当轮完成」 + :27 「谁跑 = 派发席」), 「新 spec 工作恒归 spec 席」 (survives as SKILL.md:647 「双肢命中即 spec 车道」) and 「达档席内审,未达档 ⛔ 不自审,起达档子代理」 (survives at :27 and in full at SKILL.md:533); SKILL.md:533, :647 and core-rules.md:113 are one-for-one rewrites. The only rule content removed is the lane restriction itself, which is the correction this round exists to make. No rule was deleted to pay for a line.

  5. Collision — RIGHT against main; the docs(north-star,pm-dispatch): the feature axis — area:* labels, the 「路」 section as an ordered list of feature points, cross-layer features as parent + per-layer sub-issues, domain:* retreats to file ownership (#19483) #19488 conflict is not this PR's. git merge-tree --write-tree origin/main 96774e44ef (ea64bbc6e8): exit 0, tree 121a129536, zero markers, merged SKILL.md 815 under ceiling 819; GitHub's mergeable was still being recomputed at read time. Against PR docs(north-star,pm-dispatch): the feature axis — area:* labels, the 「路」 section as an ordered list of feature points, cross-layer features as parent + per-layer sub-issues, domain:* retreats to file ownership (#19483) #19488's tip 410c37f7 (unchanged on the remote): exit 1, one conflict block at SKILL.md's 取卡全序 (merged file :473-478) — 410c37f's 「取卡全序:priority:p0 > pm:blocking > 功能点位次 > 板上项 > p1 > p2 > p3 > 无级。」 against main's two docs(pm-dispatch): enter the maintainer's 插队 and 契约面卡 tiers into the 取卡全序 (SKILL.md + core-rules twin) #19506 lines 「取卡全序:维护者直派插队卡(出处三件)> 契约面卡(判据见 references/lanes/spec.md)> 标签序。」 / 「标签序:…」. Control: git merge-tree --write-tree 410c37f7 origin/main with no PR content exits 1 with the byte-identical block, so this is docs(north-star,pm-dispatch): the feature axis — area:* labels, the 「路」 section as an ordered list of feature points, cross-layer features as parent + per-layer sub-issues, domain:* retreats to file ownership (#19483) #19488 × main (docs(pm-dispatch): enter the maintainer's 插队 and 契约面卡 tiers into the 取卡全序 (SKILL.md + core-rules twin) #19506), carried into this head by the merge of main. This PR's own hunks never touch 取卡全序 (0 hits on the three-dot diff), and docs(north-star,pm-dispatch): the feature axis — area:* labels, the 「路」 section as an ordered list of feature points, cross-layer features as parent + per-layer sub-issues, domain:* retreats to file ownership (#19483) #19488's SKILL.md hunks (:118-133, :211-307, :365, :461) touch none of the lines the fix commit edited (:533, :647) nor the 复核 sections; the round-1 PR-only proof (exit 0 at fec2177089) stands. Merged SKILL.md count with the conflict left in: 819 including the three marker lines, i.e. 815 or 816 once docs(north-star,pm-dispatch): the feature axis — area:* labels, the 「路」 section as an ordered list of feature points, cross-layer features as parent + per-layer sub-issues, domain:* retreats to file ownership (#19483) #19488 resolves against main — under 819 either way.

② Semver level

Not applicable: charter text only; skip-changeset is on the PR and Check Changeset reports skipped accordingly.

③ Boundary flags

  • dev flags from round 1 (〈状态模型〉 H17 index; no CEILINGS row; --template contradicts nothing; lanes/director.md:32 resolves): unchanged and answered at 5755678024; no new flags; open_questions: none.
  • PR checks on head 96774e44ef — measured this time. The expectation that a .claude/** + AGENTS.md diff yields zero PR-level runs did not hold on this head: 20 workflow runs and 40 check-runs fired on pull_request, so round 1's zero was the push-trigger artefact, not the diff shape. Required contexts: TypeScript Type Check success; Test Core success (the aggregate, if: always(); its Test Core (…/6) shards skipped — the rostered expected skip for a diff outside packages/** and scripts/**, check-expected-skips.mjs:292); Dogfood Regression Gate success (aggregate; shards skipped, rostered :278); Build Core skipped (rostered expected skip, :264); Temporal Conformance (live PG + MySQL) skipped (rostered, :271); Governed Surface Queue Guard success; Lint & Repo Gates in_progress = NOT MEASURED (started 2026-09-21T05:16Z, still running at posting time); Check Changeset skipped under skip-changeset. The roster was read, not run. Non-blocking for the content verdict; the maintainer's hand merge sees the final Lint & Repo Gates result on the PR.

Implemented-by: claude/issue-19496-instrument-discipline-charter
Reviewed-by: session_012GcsUbuqFGBibkEDMRC1eE

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 origin/main clean with the one keep-both at SKILL.md:180-181; the ruling is cited on instrument-discipline.md:12. R1, R2, R3, R6, the ratchet and the collision proofs hold. One cosmetic residual (the stale 「(按车道)」 H1 at contract-review.md:1) needs no patch round. Lint & Repo Gates is the one required context not yet measured on this head.

Isolated at-tier reviewer (mode:subagent of the director seat), reading time 2026-09-21T05:26Z; fresh worktree at head 96774e44ef, origin/main ea64bbc6e8.


Generated by Claude Code

Copy link
Copy Markdown
Collaborator Author

Provenance — director seat, summon #25 (session_012GcsUbuqFGBibkEDMRC1eE), 2026-09-21T05:28Z


Generated by Claude Code

@os-zhuang
os-zhuang marked this pull request as ready for review September 21, 2026 08:21
@os-zhuang
os-zhuang enabled auto-merge September 21, 2026 08:21
@os-zhuang
os-zhuang added this pull request to the merge queue Sep 21, 2026
Merged via the queue into main with commit 3e8e2b0 Sep 21, 2026
42 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-19496-instrument-discipline-charter branch September 21, 2026 08:45
os-project-manager pushed a commit that referenced this pull request Sep 21, 2026
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>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 28, 2026
…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>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 28, 2026
…-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>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 28, 2026
…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>
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

3 participants