Skip to content

feat(pm): gate the clause-② carrier strip on the served tier the verdict declares - #17990

Merged
os-zhuang merged 4 commits into
mainfrom
claude/issue-17915-served-tier-gates-the-strip
Sep 13, 2026
Merged

os-zhuang merged 4 commits into
mainfrom
claude/issue-17915-served-tier-gates-the-strip

Conversation

@claude

@claude claude Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor

Fixes #17915

CONTRACT_REVIEW_TIER's own docblock declares the comparison against the served tier EXACT — "never a family or prefix floor" — and nothing in this tree performed it. The dispatching seat passes a model as a dispatch parameter, and a passed parameter is configuration rather than a reading; the docblock's other half said the re-review sub-round's "opening self-check reads this", but a self-check is prose to the reviewer, so a round that simply did not run it produced a verdict indistinguishable from one that did. The census on the card measured 11 rounds served below the declared tier across four days and eleven PRs, five of them the only clearance a merged Clause-②: yes pair ever had.

This lands items 1–3 of the director ruling: the verdict carries the reading, and the strip is gated on it.

What changed

.claude/skills/pm-dispatch/references/contract-review.md (2 lines of new rule, net 0 at the ratchet):

  • 〈复核归属与资格(席内)〉 gains, beside the 同形 definition:

    同形含首行 Served-tier:,值取复核者转录的 harness model 盖章;无此行不成裁决。

  • 〈降档保险丝(机读)〉 turns the prose instruction into the mechanism it described:

    清标前 --pair:裁决 Served-tier:CONTRACT_REVIEW_TIER ⇒ exit 4,点名 PR、评论、读数。

scripts/pm/check-clause2-carriers.mjs — a new finding row, C7:

  • readServedTier reads a Served-tier: key line with Reviewed-by:'s own discipline (case-sensitive key, the markdown decoration a seat writes without meaning it). Three-valued: read / unreadable / missing — a carrier never started and one started and left unreadable are different facts.
  • ⭐ The value grammar is [N/M ]tier, corrected from a fixture against the live board before shipping. Every record the ruling's own remediation rounds are posting right now spells it control-first — Served-tier: 75/75 \…`on PR #17877,138/138on #17498 — and the ruling's specimen is written the same way. A reader that demanded the tier token immediately after the colon would have refused every verdict produced under the rule it enforces, on day one. TheN/M` is judged rather than skipped: it is the zero-hit control the discipline already requires, and a count that is not total is the 「回退证据」 whose own rule text voids a verdict entire. Absent, it is vacuous — the tier alone decides, which is the ruling's minimum, so nothing the ruling permits is refused.
  • reviewOfRecord carries the reading on the record it already chose, so C6 and C7 can never disagree about which verdict a clear stands on.
  • c7ServedTierBelow fires on C6's population and no otherneedsRecordRead's completed state, i.e. a clearance judgment of a hung carrier: declared yes, the gate bound and cleared on both carriers, head unmoved. The refusal names the PR, the verdict comment and the served value, at exit 4 (a limb not standing), never 3.
  • The comparison is EXACT against CONTRACT_REVIEW_TIER, imported from dispatch-gates.mjs so the model id keeps exactly one value site across scripts/pm/** and .claude/skills/pm-dispatch/**. No model identifier appears anywhere in this diff outside that import.
  • 42 self-test cases in a new battery: the ruling's three (at tier green, below tier red, line missing red), the live value shape and its bulleted/bolded spelling, the control pins (0/0 void, 12/133 refused as fallback evidence, absent vacuous, a perfect control never rescuing a below-tier value), the exactness pins (a family prefix and an extended value both refused), and the four populations the row must never reach.

Deliberately NOT in scope

A Clause-②: no pair that never carried the label is never refused for lacking the line — it is not in the candidate shape, so the row cannot reach it. A pair still carrying the gate owes nothing yet. An absent or unsigned record stays C6's row alone. No PASS/FAIL token is read to reach any of it: what produced a verdict is measurable, what it concluded stays human.

One deviation from the dispatched file surface — and why it is inside it

The dispatch scoped the diff to those two files. It is those two files — but one edit inside the checker was not foreseen and is worth reading before approving.

scripts/pm/check-clause2-carriers.mjs carried a dispatch-gates: no-path-population marker: "this gate reads no file in the tree at all … so no card's file surface can predict it". The input half is still exactly true. The other half stopped being true the moment C7's import landed: a card editing CONTRACT_REVIEW_TIER moves the value every clearance is judged against, so it does predict this gate. pnpm check:pm-dispatch-gates catches this directly — its live-half case no family both DECLARES no path population and names paths anyway went red on check:pm-clause2-carriers, measured by ablation (base tree: hints []; with the import: hints [".github/workflows"]).

Keeping a declaration that stopped being true is the exact shape C7 itself exists against, so the marker is retired, and the comment left in its place states the trade rather than hiding it: the import channel contributes a followed module's own literals, so this family now also inherits a .github/workflows lead it never opens, on a gate whose CI step runs the self-test only. The designed narrowing (inherited-population, declared by the followed module) cannot express this case — it is per-module, and the same module's globs are a real population for check:pm-widening-tells, which reads them. Filed separately as #17991 rather than worked around here.

⛔ The alternative — restating the tier in this file — is the thing that let the declared tier and the served one drift apart in the first place, and is refused.

Acceptance measurements

All taken at fed29ced, against base 9ccc4179.

reading before after
Served-tier in references/contract-review.md 0 2
Served-tier in check-clause2-carriers.mjs 0 26
Reviewed-by in references/contract-review.md (lit control) 1 1
references/contract-review.md lines 60 60
check-clause2-carriers.mjs longest line (bytes) 390 390
  • Every line of references/contract-review.md is ≤ 120 bytes (LC_ALL=C awk 'length($0)>120' prints nothing); the four edited lines measure 105 / 113 / 111 / 108 B. The 120-byte register is the reference file's; the checker keeps its own line style unchanged (471 → 502 lines over 120 B, max unmoved at 390).
  • git diff --stat origin/main...HEAD → exactly .claude/skills/pm-dispatch/references/contract-review.md and scripts/pm/check-clause2-carriers.mjs.
  • Line budget, paid by density and not by re-wrap: +1 the 同形 line, +1 the mechanism line replacing the 转录档位核验 prose, -1 by folding 「⛔ 自述档位不是读数」 and 「传参只是配置 ⛔ 不作达档读数」 into one clause (they are one rule: a tier claim that is not a harness stamp is not a reading), -1 by dropping the 转录核验 grep recipe, whose method survives at :49 (「每场前必读服役档,读法见 platform-readings.md」) and in the new 同形 line. node scripts/pm/check-skill-line-ratchet.mjs green at 60/60, headroom 0.

Checks

  • pnpm check:pm-clause2-carriers588 cases pass (546 before this PR; the C7 battery is 42 and is registered with its own floor, SELF_TEST_BATTERY_FLOOR 19 → 20, preserving the roster's existing slack).
  • Gate families derived with node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths) on the merged head: 41 families, 41 run, 41 exit 0. Reconciled: --ran reports 41 derived famil(ies) accounted for — 41 run, 0 NOT-MEASURED (a DERIVED zero — all 41 recorded an exit code and none of them is 3).
    • pnpm --filter @objectstack/lint run check:doc-formula-expressions first answered PREREQUISITE NOT MET (exit 3, nothing measured). Built its two declared prerequisites under the shared verify lock (VERDICT command-exit 0 · held the lock 141s) and re-ran it: exit 0.
  • Reverse verification (both legs on the committed tree, restored and verified by blob hash): adding the Served-tier: requirement turned the reference record fixture red — ⭐ the #14155 specimen WITH its record still reads CLEAN overall failed with ["C7"] — before the fixture gained the line. The row can fail.
  • Live control, node scripts/pm/check-clause2-carriers.mjs --pair 17956: exit 2 on this head and exit 2 on the base script for the same PR, same sentence (PR #17956 is not open, or names no card this file can derive). The pair could not be formed, so nothing about it was judged — the dispatch expected 0 for a Clause-②: no pair, and that PR has since left the open set. ⛔ Not a C7 refusal, and unmoved by this diff.
  • Narrowed lint, with its three readings: universe is eslint . over one eslint.config.mjs; npx eslint --no-inline-config --format json scripts/pm/check-clause2-carriers.mjs → 1 file, 0 errors, 0 warnings; the narrowing excludes nothing because that config "never enables type-aware linting (no parserOptions.project, no typed @typescript-eslint rules) for ANY file" (its own line 328), so this diff cannot move any untouched file's verdict. The .md is not an eslint input.
  • grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]' over both edited files: no hits; pnpm check:nul-bytes green.

Changeset

skip-changesetscripts/pm/** and .claude/** publish nothing: neither path is in any package's files[], and both are on the fast track (.claude/** and PM tooling). Label applied and read back.

Acceptance notes

维护者速读(草稿)

改了什么 — 契约复核裁决从此必须带一行 Served-tier:,值取复核者转录里 harness 逐消息盖的 model 字段;check-clause2-carriers.mjs --pair 在判定「双载体已清」时读这一行,不等于 CONTRACT_REVIEW_TIER 就拒(exit 4),并点名 PR、裁决评论和读到的档位。规则文本同步落在 references/contract-review.md,行数 60 → 60。

为什么改 — 常量自己的 docblock 写着「与服役档的比较是 EXACT」,而树上没有任何东西在比。派进去的 model 是配置不是读数,「开场自检」是写给复核者的散文:一轮不跑它,产出的裁决与跑了的长得一模一样。卡上实测 11 轮在档下产出裁决,其中 5 轮是已合并 Clause-②: yes PR 唯一的清标依据。这是本仓在别处一律拒绝的 declared ≠ enforced,落在「一次公共契约加宽到底有没有被复核过」那道门本身。

风险与代价(含回滚) — 失败方向是响亮的:清标被拒,不是被静默放行。代价一:规则落地前写的历史裁决没有这一行,再被判定时会红,补救是复核席把自己转录里已经盖好的读数补写成一条新记录(最新的记录优先,不动载体)。代价二:本 PR 让这个门禁第一次有了树内依赖(CONTRACT_REVIEW_TIER 所在文件),因此退掉了它「无路径面」的旧声明;派生因此多送一条 .github/workflows 的线索,是噪音、已在文件里写明,并已记入验收备注。回滚 = revert 本 PR,一次 revert 即可,门禁回到今天的状态。

席位意见 — (留空,待席位定稿)

你要做的 — 受管面(.claude/**),本 PR 恒为 draft,⛔ 不由任何 AI 席位合并、入队或挂 auto-merge。请人工确认两件事:① 规则文本那两行的措辞;② 退掉 no-path-population 声明这一步是否接受(替代方案是把常量在本文件再写一遍,那正是让档位漂移的那个形状,已拒)。

Provenance

Authored by the domain:skills seat's dispatched executor, session session_01DAcomhvR9kKizeYgg89Vo8 (https://claude.ai/code/session_01DAcomhvR9kKizeYgg89Vo8), on branch claude/issue-17915-served-tier-gates-the-strip. Attribution is stated here in prose because a REST edit of a PR body appends its own footer block: the first edit of this body left two, and this revision sends none so the appended one is the only one.


Generated by Claude Code

`CONTRACT_REVIEW_TIER`'s own docblock declares the comparison against the
SERVED tier EXACT, and nothing performed it: the dispatching seat passes a
model as a parameter, a parameter is configuration rather than a reading,
and the reviewer's "opening self-check" was prose a round could skip while
producing a verdict indistinguishable from one that did not.

A contract-review verdict now carries a `Served-tier:` line whose value is
the harness-stamped served-model field of the reviewer's own transcript,
and `check-clause2-carriers.mjs --pair` refuses to treat a
`needs:contract-review` pair as cleared unless the newest verdict on the
current head declares exactly the constant — naming the PR, the verdict
comment and the served value, at exit 4.

The row (C7) rides C6's population and C6's chosen comment, so a clearance
judgment of a hung carrier is the whole of its scope: a `Clause-②: no` pair
that never carried the label is never refused for lacking the line, and an
absent or unsigned record stays C6's row alone. The constant is imported
from `dispatch-gates.mjs`, which keeps its single value site.

Claude-Session: https://claude.ai/code/session_01DAcomhvR9kKizeYgg89Vo8
Co-authored-by: Claude <noreply@anthropic.com>
C7 compares against `CONTRACT_REVIEW_TIER`, which is declared in
`dispatch-gates.mjs` and imported here, so a card editing that constant
now predicts this gate — and the marker claiming no card's file surface
can predict it stopped being true the moment the import landed. A false
declaration is the shape the new row itself exists against, so it is
retired rather than kept.

The note left in its place states the input half that is still true, the
half that is not, and the imprecision the import channel adds on the way:
a followed module contributes its own literals, so this family inherits a
`.github/workflows` lead it never opens, and the designed narrowing is
per-module and cannot express one caller's case while the sibling really
does read those globs.

Claude-Session: https://claude.ai/code/session_01DAcomhvR9kKizeYgg89Vo8
Co-authored-by: Claude <noreply@anthropic.com>
@claude claude Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 13, 2026
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation labels Sep 13, 2026
Measured against the live corpus rather than the fixture: every record the
remediation rounds post spells the value as the stamp control first, then
the tier — `Served-tier: 75/75 \`<tier>\`` — and the ruling's own specimen
is written the same way. A reader that demanded the tier token immediately
after the colon would have refused every verdict produced under the rule it
enforces, on its first day.

So the grammar is `[N/M ]<tier>`, and the count is judged rather than
skipped: it is the zero-hit control the discipline already requires, and a
count that is not total is the fallback evidence whose own rule text voids
the verdict entire. Absent, it is vacuous — the tier alone decides, which
is the ruling's minimum, so nothing the ruling permits is refused.

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

claude Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor Author
  • Served-tier: 621/621 claude-fable-5-1 — harness model stamp counted over this seat's own transcript (non-sidechain assistant messages) at 2026-09-13T10:25Z; get_session external_metadata.last_served_model read claude-fable-5-1 at 2026-09-13T10:21Z.

Contract review

Head: fed29ced (PR #17990, card #17915) — read at 2026-09-13T10:25Z by the skills seat at the contract-review tier.

Files: .claude/skills/pm-dispatch/references/contract-review.md (net 0 lines, 60/60, no line over 120 bytes — measured on the head tree) · scripts/pm/check-clause2-carriers.mjs (+401 −23). Both inside the dispatched surface; scripts/pm/dispatch-gates.mjs untouched, as the dispatch's zone rule required (its :49 self-check survives, so the mechanism sits beside it).

① derived judgments — four changes to what the gate accepts or reports, each read against ruling 5651924370 items 1–3:

  1. C7 row (c7ServedTierBelow): on C6's completed population only (declared yes, gate bound and cleared on both carriers, head unmoved) it reads Served-tier: off the SAME comment reviewOfRecord chose for C6, and refuses at exit 4 (EXIT_PAIR_ADVERSE, never 3) unless the value equals CONTRACT_REVIEW_TIER exactly — no family, no prefix; a missing or unreadable line is refused beside a below-tier value; the refusal names the PR, the comment id and the served value, and names the required tier by its constant. Item 2 as ruled. The strip's accept set NARROWS; nothing widens.
  2. Value grammar [N/M ]tier: the stamp control is read and judged — absent is vacuous (the ruling's minimum), present must be non-zero and total; the not-total case is the 回退证据 the file's own :57 rule already voids a verdict for. Corrected against the live corpus (remediation records spell it control-first: 75/75, 138/138); a tight-after-colon reader would have refused every record written under the rule on its first day. Right direction.
  3. no-path-population marker retired because CONTRACT_REVIEW_TIER is now IMPORTED (one value site kept): a card editing dispatch-gates.mjs now predicts this family. Ablation measured base hints [] → head ['.github/workflows']. Effect: such cards run MORE gate families, never fewer; the report-only sweep is unchanged. In-surface, stated in the file, over-reach filed as [finding] a constant-only import inherits the exporter's whole watch surface — inherited-population is per-module, so no caller can decline a fabricated lead #17991. Accepted.
  4. keyLineRegex factoring: AUTHORSHIP_KEY_LINES builds the byte-identical pattern; reviewOfRecord gains a served field (additive); c6RecordNote gains one sentence; SELF_TEST_BATTERY_FLOOR 19 → 20 by exactly the one battery added (42 cases). C1–C6 behaviour-preserving.

Seat measurements on the head tree (git archive fed29ced): check-clause2-carriers.mjs --self-test → 588 cases pass, exit 0. check-skill-line-ratchetcontract-review.md 60 lines (ceiling 60), widest table row 0. LC_ALL=C awk 'length>120' over contract-review.md → nothing. Live blast radius, the PR's script in report-only sweep at 2026-09-13T10:23Z: 24 pairs from 25 open PRs, exactly 2 C7 rows — #17983/#17781 (record 5652680125) and #17979/#17911 (record 5652668107), both ## Contract review records posted after the ruling with no Served-tier: line: the priced transition — each owes one re-post carrying the line, and nothing re-blocks until a landing --pair. --pair 17990 on main's script → exit 0 (a Clause-②: no pair, outside C7's population by design). The dispatch's live control --pair 17877 → exit 2 on both scripts (the PR is no longer open), so the at-tier live spelling is pinned in the battery instead.

② semver: no package touched; skip-changeset is right; ratchets unmoved.

③ boundary flags: open_questions empty. Deviations answered in ① 3–4. #17991 left bare for triage per ruling ③ (5652544529).

Checks on fed29ced: 38 runs; Test Core (1/6) red in packages/spec/scripts/gen-sdui-manifest-cleanup.test.ts ("reaps the whole session it started…": LOCKFDS expected 0, received 1) — a file this diff does not touch, in a package it does not import; main's Test Core (1/6) on 65767d23 is green. Not this PR's. A re-run is refused to this seat (POST /actions/jobs/103706765153/rerun → 403), so the shard stays red until a maintainer re-run or the next push; landing under ruling C waits on a green shard. Lint & Repo Gates was still in progress at 10:20Z.

Implemented-by: claude/issue-17915-served-tier-gates-the-strip
Reviewed-by: session_01DAcomhvR9kKizeYgg89Vo8

Verdict: PASS — items 1–3 of ruling 5651924370 implemented as ruled; nothing widens. Governed (references/**): draft stays; needs-user-decision + reviewers os-zhuang / hotlong; landing by this seat under ruling C (#17971) once an authorized approval exists and the checks read green.


Generated by Claude Code

@claude

claude Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor Author

维护者速读(终稿)

改了什么 —— 一行规则、一道门。规则:契约复审裁决首行必带 Served-tier:,值是复核者转录里 harness 盖的 model 章(可前置 N/N),无此行不成裁决(contract-review.md 60/60,净零行)。门:check-clause2-carriers.mjs 新增 C7 —— 清标所依的最新裁决若无此行、读不出、或不等于 CONTRACT_REVIEW_TIER(精确,不认族名/前缀)⇒ exit 4,点名 PR、评论、读数。只看已清标的 Clause-②: yes 对;no 对与门未摘的对碰不到。

为什么 —— 裁决 5651924370 第 1–3 项:常量 docblock 写着「与服役档精确比较」,树上没人在比;卡上实测多轮在档下清了标。

风险 —— 收紧,不放宽。现网今天已有两条裁决没带这行(#17983#17979 的记录),各补发一条记录即可,落地前不会被卡。一处顺手:该文件的 no-path-population 标记因 import 常量而退役,改 dispatch-gates.mjs 的卡会多跑这道自测,不会少跑。

一红 —— Test Core (1/6) 红在 packages/spec/scripts/gen-sdui-manifest-cleanup.test.ts(本 diff 不碰、main 同分片绿),本席无重跑权限(403)。

批准即由本席按裁决 C 落地;顺手点一次 re-run 即可,或等下一次 push。要不要批?


Generated by Claude Code

@claude

claude Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor Author

The red, once more and with a card — skills seat, 2026-09-13T11:29Z. Test Core (1/6) on fed29ced is still the single red (job 103706765153, 10:01Z): packages/spec/scripts/gen-sdui-manifest-cleanup.test.ts counted one caller lock fd where it expects none — a file this diff does not touch, in a package it does not import; main is green on that shard at ae1b2588 (11:03Z) and c45ea8c3 (11:18Z). A re-run is refused to this seat (403), so the shard stays red until someone with the button re-runs it or the branch gets its next push. Filed as #18010 for the owning lane (bare, for triage). Nothing else on this PR changed: the contract review 5652701385 stands on this head, and the seat lands it under ruling C once an authorized approval exists and the shard reads green.


Generated by Claude Code

@claude

claude Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor Author

Green — skills seat (session session_01DAcomhvR9kKizeYgg89Vo8), 2026-09-13T12:29Z. The one confirming re-run of the failed shard was triggered by this seat at 2026-09-13T12:21Z through the MCP GitHub channel (actions_run_trigger rerun_failed_jobs on run 34750740645 → 201; the seat's REST token was the channel that read 403, now recorded as #18025). Attempt 2: Test Core (1/6) completed success, the run's conclusion success; head fed29ced reads 42 checks, 0 pending, 0 red. Same head, same commit, second run green ⇒ the LOCKFDS failure was the flake shape #18010 describes, not this PR's. Nothing else changed: the contract review 5652701385 stands on this head; the PR now waits only on an authorized approval, after which this seat lands it under ruling C.


Generated by Claude Code

@os-zhuang
os-zhuang marked this pull request as ready for review September 13, 2026 13:12
@os-zhuang
os-zhuang added this pull request to the merge queue Sep 13, 2026
@claude

claude Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor Author

Ruling C landing step — skills seat (session session_01DAcomhvR9kKizeYgg89Vo8), 2026-09-13T13:13Z. Authorized approval read: os-zhuang APPROVED on head fed29ced at 2026-09-13T13:12Z (review 5190785460; not dismissed), who also flipped the PR ready and enqueued it in the same minute. Seat readings at 2026-09-13T13:13Z: ① contract review of record 5652701385 names this head fed29ced (unchanged since); ② check-clause2-carriers --pair 17990 exit 0; ③ 42 checks on fed29ced, 0 pending, 0 red (the one red shard re-ran green at 12:2xZ — 5653266976); ④ needs-user-decision cleared in this pass and read back. Queue ref gh-readonly-queue/main/pr-17990-* present. Landing record follows on card #17915 once origin/main carries (#17990); that landing unblocks #18020 (the queue-guard tier, Blocked-by: #17915) and frees references/contract-review.md for the charter chain's references PR.


Generated by Claude Code

Merged via the queue into main with commit 273a665 Sep 13, 2026
60 of 62 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-17915-served-tier-gates-the-strip branch September 13, 2026 13:39
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 17, 2026
…ce tiering, ui/triage lane lines (objectstack-ai#17942 chain) (objectstack-ai#18018)

Fixes objectstack-ai#17971
Fixes objectstack-ai#17742
Fixes objectstack-ai#17624
Part of objectstack-ai#17942
Part of objectstack-ai#17950
Part of objectstack-ai#17933
Part of objectstack-ai#17934
Part of objectstack-ai#17951

Dev session `session_01DAcomhvR9kKizeYgg89Vo8` on branch
`claude/issue-17942-charter-revision` (worktree
`objectstack-issue-17942`). Rules-layer charter revision for the
eight-card family whose chain head is objectstack-ai#17942. Deferred to the follow-up
references PR (files occupied by PR objectstack-ai#17990 / objectstack-ai#17992, seat-landable once
this PR lands the tiering): `contract-review.md` (the objectstack-ai#17934 落地前检③
exception, objectstack-ai#17933's per-lane table, objectstack-ai#17950's 落地前检③ line, objectstack-ai#17942's 复核归属 /
独立性件), `dispatch-runbook.md` (objectstack-ai#17942's 跨车道直接接手), `lanes/director.md` +
`decision-analysis.md` (objectstack-ai#17951's director half); the objectstack-ai#17950 register /
queue-guard script items are their own script PR. objectstack-ai#17934 has no edit
here — its ruling lives in a deferred file.

## Rulings landed (verbatim, one commit per card)

- **objectstack-ai#17942** — 5651976160 「同意」 to `1A(+三类表)·2(1)·3A·4B`; 5652079343
「以上接受你的建议。」; 5652544529 ruling ③ 「同意」 (「一律经过分诊 … 唯一例外:在飞卡的衍生子卡 …
立卡者不查重,只在卡面附 3–5 个查重词 … 查重缓存住在分诊席自己的容器里」). Q3 withdrawn — no
self-routing text had reached SKILL.md (`git grep -E '自路由|自定级|self-rout'
origin/main -- .claude/skills/pm-dispatch` exits 1; control 「分诊座位唯一」
hits), so nothing came out. The 「分诊不重指派」 wording does not exist in the
tree (git grep exits 1) — nothing to narrow.
- **objectstack-ai#17950** — 「我点头」 to the boundary 「规则层(你确认)… 事实层(席内契约复审档复核后入队):只有
`.claude/skills/pm-dispatch/references/**`」.
- **objectstack-ai#17971** — 5652306063 「C. approve 后不管后续改动都由席位落地:」.
- **objectstack-ai#17933** — the maintainer's misreading 「不是说你的 ui 车道不需要契约复审了吗?为什么还有
`needs:contract-review`」; `lanes/ui.md` :38 now states the
applicability.
- **objectstack-ai#17742** — self-triage 5642823259 (fourth disjunct if the population
is small); count posted on the card first (5653074219): `pm:queue` ∧
`domain:*` ∧ ¬`priority:*` = 7 objectstack / 31 objectui at
2026-09-13T11:46Z.
- **objectstack-ai#17951** — 「③ 受管面手合本身也是一条队列。能否简化我的审核步骤,比如我召唤项目总监时可以批量决定?」 — SKILL.md
director line paid (+1 inside the 812), AGENTS.md PD objectstack-ai#14 clause.
- **objectstack-ai#17624** — 5650203410 「同意」 to B; precondition measured:
`lanes/triage.md` :7 differed from the SKILL.md line only by 「分诊」
inserted and 「裁定」 dropped — no rule lost.

## Acceptance greps (`git grep -c -F` at `BASE` → this head; every
file's control still hits)

| card | 0 → 1 phrase | file | control (1 → 1) |
|:--|:--|:--|:--|
| objectstack-ai#17942 | 「认领即跟到 MERGED」, 「分诊座位唯一生产」, 「在飞卡衍生三分」; 「简单阻塞项」 3 → 0 |
SKILL.md | 「每个方案必须沿四条固定评估轴分析」 |
| objectstack-ai#17950 | 「受管面两层」 / `Landing is tiered` / 「两层分档」 | SKILL.md / AGENTS.md
/ lanes/skills.md | `GOVERNED_SURFACES` / 「QA 波次由维护者手动触发」 |
| objectstack-ai#17971 | 「席位落地」 0 → 3 (SKILL) and 0 → 1 (core-rules); `landed by the
owning seat` | SKILL.md, core-rules.md, AGENTS.md | 「一座位一车道双射」 |
| objectstack-ai#17933 | 「条款②复核本席适用」; 「不凭记忆或继承的注记」 1 → 0 | lanes/ui.md | 「零读数恒配点亮的正控」
|
| objectstack-ai#17742 | 「析取 ④」, 「未定级数另计析取④」; core 「队列卡缺域或缺定级」 | SKILL.md,
core-rules.md | 「分诊座位唯一」 |
| objectstack-ai#17951 | 「受管草稿呈为一批」; `director seat requests as ONE` | SKILL.md,
AGENTS.md | `A version release is performed by the maintainer` |
| objectstack-ai#17624 | 「六态属他席」; 「分诊不挂」 1 → 0 | lanes/triage.md | 「饥饿守卫」 |

## Line budgets (net 0 per file, paid by density inside the file —
payments named in each commit)

SKILL.md 812 → 812 (widest table row 342 B, untouched); core-rules.md
151 → 151; AGENTS.md 1075 → 1075 (widest row 768 B, untouched; the `node
-e` helper line under PD objectstack-ai#14 is the one deletion that is not a rule);
lanes/ui.md 38 → 38; lanes/triage.md 7 → 7; lanes/skills.md 33 → 33.
Every added line ≤ 120 B (`git diff BASE..HEAD | grep '^+' | awk
'length>120'` prints nothing); the whole-file `awk 'length($0)>120'`
baseline is 23 / 23 on SKILL.md and 15 → 14 on AGENTS.md — all table
rows or the dropped helper line, the shape the ratchet exempts (the PM's
"empty over edited files" expectation is false on `origin/main`
already). Decision frame block byte-identical: md5
`3327d02c56f8a0eca88569dad2270f32` (now at :733–:754).

## Deviations from the dispatch, stated

- P1 「:454 leave」 falsified: the ruling lifts the S-level cap that :454
stated; leaving it would keep SKILL.md contradicting Q2 class 2 while
the references PR cannot touch SKILL.md and stay seat-landable — the
four 简单阻塞项 lines became the three-class rule (commit 1).
- 5652544529's 「lanes/triage.md gains the cache line」: triage.md is 7/7
and its only spendable line went to ruling B — the cache rule lands in
SKILL.md 〈分诊座位职责〉 instead.
- The `references/**` spelling in prose: `check:pm-governed-prose` reds
any `**`-shaped code span outside the register, so the tier is spelled
`.claude/skills/pm-dispatch/references/` (AGENTS.md) and 本技能
`references/` (SKILL.md).
- SKILL.md 〈复核〉 had a carve-out letting `.claude/`
hooks/workflows/settings PRs self-land on the skills seat's review; the
objectstack-ai#17950 ruling names hooks and settings in the rules layer, so those
three lines became the tiering lines (provenance beyond the shallow
window not read).
- Table rows left untouched (widest-row pins): 状态模型 rows still list
「跨车道移交」 / 「跨域 PR 指定车道」; the bullet lines govern.

## Gates (derived with `node scripts/pm/dispatch-gates.mjs --commands
--repo objectstack-ai/objectstack` on the merged head `269933d9`; all 18
run in the foreground, exit codes captured before any pipe, `--ran`
reconciled: 18 derived, 18 run, 0 unrun)

`check:pm-skill-ratchet` ✓ (SKILL.md 812/812 · core-rules 151/151 ·
AGENTS.md 1075/1075 · ui 38/38 · triage 7/7 · skills 33/33; both
widest-row pins at headroom 0) · `check:pm-skill-id-lint` ✓ 27 files
clean · `check:skill-frame-sync` ✓ · `check:pm-governed-prose` ✓ (5
register surfaces named, no over-claim) · `check:pm-governed-merges` ✓ ·
`check:nul-bytes` ✓ 8593 files · `check:refd-timer-probe` ✓ ·
`check:doc-authoring` ✓ · `check:required-contexts` ✓ ·
`check:watch-hint-literal` ✓ · `check:agent-test-spelling` ✓ ·
`check:docs-audit-scope` ✓ · `check:driver-memory-census` ✓ ·
`check-closing-keyword-parity` (+ self-test) ✓ ·
`check-comment-mask-corpus` ✓ · `check-governed-queue-guard --self-test`
✓ 183 cases · `@objectstack/lint check:doc-formula-expressions` ✓ (first
run exit 3 PREREQUISITE NOT MET — formula/lint unbuilt; built under the
verify lock, 2m52s, then ✓). No package is touched ⇒ no build/test
closure owed; changeset: `.claude/**` and `AGENTS.md` publish nothing ⇒
`skip-changeset`.

## Acceptance notes (noted, not filed; 承接者 named)

- `.claude/agents/os-dev.md` :50 「先搜再立」 still asks the dev to dedupe
before filing; ruling ③ moves dedupe to triage (filer attaches
keywords). 承接者: the skills seat — a class-1 sub-issue of objectstack-ai#17942
(os-dev.md has its own ceiling; not in this PR's file surface).
- `lanes/ui.md` :25 and objectui `AGENTS.md` §9 still read 「停在 draft
等人合」 — ruling C applies to objectui governed PRs too (the card names
objectui#9374 / objectstack-ai#9377). 承接者: the skills seat, sibling-repo PR.
- `SKILL.md` :113 `pm:retriage` row and :106 assignee row (table rows)
still name 改路由 / 跨车道移交 without the pre-dispatch qualifier; the bullet
「`pm:retriage` 改路由只对未派发卡」 governs. 承接者: whoever next edits those rows
under the 342 B pin.

## 维护者速读(草稿)

**改了什么**:把这周你在聊天里定下的八条裁决写进 PM 章程的规则层——认领的卡跟到合并(碰到 spec
面借复核不换席)、衍生子卡与阻塞项的三分表、定级/路由/查重收回分诊席(立卡者只附查重词)、受管面分两层(只有 pm-dispatch 的
references 目录经席内复核后入队,其余仍由你确认)、你批准之后由席位自己落地(后续推送也是)、ui 车道条款②适用面写明、分诊
sweep 补第四态、总监席把待你确认的受管草稿一批呈报、分诊席「并发出现的六态属他席」。SKILL.md、核心条款、AGENTS.md 第
14 条、三个车道文件,行数全部不变,每处新增都在同文件删重付账。

**为什么改**:每条都是已裁未落地的章程滞后;裁决原文逐字引在各 commit 与上文。

**风险与代价(含回滚)**:不发布任何包、无 changeset;规则层文本改动,回滚 = revert 本
PR。风险在两处判断:AGENTS.md 第 14 条为付行数删掉了 `node -e`
打印受管面的辅助命令(注册表所在文件仍点名);〈复核〉里「`.claude/` hooks/settings 纯代码面由 skills
席自审直接落地」的旧例外被你 09-13 的分层裁决覆盖,已删。

**未含**:references 层的对应条文(contract-review.md
的落地前检③例外与逐车道适用表、dispatch-runbook.md 的接手细则、director.md 与
decision-analysis.md 的总监批呈)另起一个 references PR,待 PR objectstack-ai#17990 / objectstack-ai#17992
落地后由席位自落;objectstack-ai#17950 的注册表与队列守卫脚本项另起脚本 PR。

**一句问**:同意「hooks/settings 的旧自审直落例外」随分层裁决一并删除吗(是/否)?

---
_Generated by [Claude
Code](https://claude.ai/code/session_01DAcomhvR9kKizeYgg89Vo8)_

---------

Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 17, 2026
…identifier — AGENTS.md's comment rule (objectstack-ai#18060) (objectstack-ai#18087)

Fixes objectstack-ai#18060

A review of record is a GitHub **comment**, and `AGENTS.md` is
unqualified about that surface:

> …the pre-push hook refuses a model identifier in that pair; **no model
identifier lands in a PR title or body, a comment, a changeset, a doc or
a code comment.**

The `Served-tier:` top line the in-seat contract-review protocol
mandates carried the **value** of `CONTRACT_REVIEW_TIER`, which is a
literal model identifier. So a record could clear a carrier **only** by
putting that identifier into the very artifact the rule names.

This PR makes the identifier-free spelling the **only** one: the line's
token is the constant's **NAME**. Nothing evidential is traded away —
the line was never the reading. The protocol already says 「⛔
自述档位与传参皆非读数」, and the authoritative control is the seat's own transcript
grep against the constant's value, which produces **no repository
artifact at all**.

Direction was ruled by triage (comment 5656662371, quoted verbatim,
untranslated):

> ### ⭐ Direction ruled — **the convention yields to `AGENTS.md`.** ⛔
Not a decision card.

> ⛔ Triage rules the direction; the exact replacement wording is the
implementer's.

## Premise readings

All four checked against `origin/main` at `57343f761`, in the worktree,
on 2026-09-14.

| # | premise | verdict | evidence |
|:--|:--|:--|:--|
| P1 | both readers compare the token to the constant's **VALUE**, which
is a literal model identifier ⇒ a record can pass today only by carrying
that identifier in a comment | **holds** | `servedTierStands()` read
`served.value === CONTRACT_REVIEW_TIER`;
`check-governed-queue-guard.mjs` imports that very predicate through
`loadRecordRecognisers`, so both gates answered from one comparison
(00:10Z) |
| P2 | `contract-review.md` :29 and :56 require that value | **holds** |
:29 read 「值取转录 harness `model` 盖章」 and :56 「裁决 `Served-tier:` ≠
`CONTRACT_REVIEW_TIER` ⇒ exit 4」 (00:08Z) |
| P3 | `git grep -n Served-tier origin/main -- .claude AGENTS.md skills`
finds ONLY those two lines | **holds** | exactly 2 hits, both
`contract-review.md`; the whole-tree grep adds only the two script
files, 26 + 14 hits (00:09Z) |
| P4 | the card's 「no gate reads it *yet*」 is **already false** on
`origin/main` | **FALSIFIED — the window the card names has closed** |
`273a66501` (2026-09-13T13:12Z, objectstack-ai#17990) and `60b99552a`
(2026-09-13T15:00Z, objectstack-ai#18036) are both ancestors of `origin/main` — `git
merge-base --is-ancestor` exit **0** for each, the self-proving
direction that needs no control leg (00:41Z) |

P4 is why the **readers change too** rather than only the prose. The
card was filed while this was a habit; it is a gate now, and 「a gate is
much harder to walk back than a habit」.

## What changed

1. **`references/contract-review.md` :29 and :56** — equal-line edits,
file still 60 lines, both lines inside the 120-byte CJK prose budget
(110 B and 120 B).
   - :29 — 「值写常量名 `CONTRACT_REVIEW_TIER`,可前置 N/N;无此行不成裁决。」
- :56 — 「`Served-tier:` ≠ 常量名 ⇒ exit 4,点名 PR、评论、读数;型号串按 `AGENTS.md` 拒。」
- The evidence stays exactly where :53 already puts it — the seat's
transcript grep, which lands no artifact.
2. **`check-clause2-carriers.mjs`** — new `CONTRACT_REVIEW_TIER_NAME` is
the one accepted token; new `isModelIdentifierToken()` refuses the
constant's value **and** the id shape (the word claude, a hyphen, a
model word — a shape, never a list, so a model nobody has named yet
binds). C7's remedy quotes the **new** rule lines and names
`AGENTS.md`'s rule, and ⛔ never quotes an identifier token back — a
refusal that echoed it would land the identifier in one more artifact.
3. **`check-governed-queue-guard.mjs`** — its references-tier record
reader takes the predicate through the same lazy recogniser import
(fail-closed on a rename: a missing export is `available: false`),
carries the flag on the record so the renderer never re-decides it, and
its merge-queue refusal prints no identifier either. Remedy 3 now spells
the token as the NAME.

⛔ **Neither gate is weakened.** The line is still required, a missing
line is still a refusal, the comparison is still EXACT — no family
match, no prefix floor — and the accepted token is still exactly one.
The only behavioural delta is *which* single token, plus one **new**
refusal class.

## Tests

Self-test batteries, on `57132927c`. ⛔ No case deleted — 15 cases were
re-spelled and the rest are additions:

| battery | before | after | delta |
|:--|--:|--:|--:|
| `check-clause2-carriers --self-test` | 598 | **605** | +7 |
| `check-governed-queue-guard --self-test` | 229 | **233** | +4 |

The 598 baseline was measured by running `origin/main`'s own copy of the
file in this tree. The 229 is derived (4 added `assert(` calls, 0
removed), because that file's copy cannot be run against a modified
sibling — its fixtures are the thing this PR changes.

New cases include: the constant's VALUE is refused; the refusal never
quotes it back; the refusal names `AGENTS.md`'s rule; a never-shipped id
binds too (a SHAPE, not a list); the two refusals differ exactly on
quoting; and the row is not widened.

**Gate sweep** — `node scripts/pm/dispatch-gates.mjs --commands` derived
43 families from the three changed paths; all 43 run, recorded with
`--ran`:

```
✓ dispatch-gates --ran: 43 derived famil(ies) accounted for — 43 run,
  0 NOT-MEASURED (a DERIVED zero — all 43 recorded an exit code and none of them is 3).
```

`pnpm --filter @objectstack/lint run check:doc-formula-expressions`
first returned **exit 3 — PREREQUISITE NOT MET** (unbuilt
`@objectstack/formula` / `@objectstack/lint`, ⛔ not a finding); after
`turbo run build` for those two packages under the shared verify lock it
returned exit 0.

**Lint** — the full repository run, not a narrowed union: `pnpm exec
eslint . --no-inline-config --format json` over **6741 files**, **0
errors, 0 warnings**, exit 0.

## Legacy count — a reading for the seat, ⛔ not a work item here

`origin/board-archive` at `bd7bbf53b` (snapshot 2026-09-13T20:23Z), over
the archived comment bodies:

| probe | count |
|:--|--:|
| `Served-tier` | **0** |
| lit control — `Reviewed-by` | 88 |
| lit control — `CONTRACT_REVIEW_TIER` | 303 |

⚠️ **Read this zero narrowly.** Two corrections to how it was specified:

- The prescribed path `-- archive/` does not exist on that branch — a
grep there returns 0 for the wrong reason, with the lit control ALSO
reading 0. The archive lives under `board/` (11,593 files). The table
above is the `board/` reading, with the instrument lit.
- The archive's highest card is **objectstack-ai#17600**, while the `Served-tier:`
rule landed with **objectstack-ai#17990** on 2026-09-13. ⇒ the archive's window
**predates the convention entirely**. The zero says the archive does not
reach the window, ⛔ **NOT** that no live comment carries the line. The
card's 「how many comments across the fleet carry the line」 stays
unmeasured, and this PR migrates nothing.

## Landing note

This PR's own governed surface is
`.claude/skills/pm-dispatch/references/contract-review.md` —
`check-governed-merges.mjs --test` confirms it hits the register, so the
PR is **draft-only and human-merged**, or lands on the references tier's
review of record.

⚠️ **The review of record on THIS PR must use the NEW form** —
`Served-tier:` naming `CONTRACT_REVIEW_TIER`, ⛔ not its value — because
the merge-group leg runs **this PR's own guard**. A record written in
the old spelling is refused by the code this PR ships.

## Acceptance notes

**Open question surfaced by triage, ⛔ NOT decided here, and ⛔
`AGENTS.md` untouched.** Triage named a class the rule as written does
not distinguish: **a preserved verbatim maintainer ruling that happens
to contain an identifier** vs. **an identifier a seat emits as its own
artifact**. This PR only removes the second kind. A blanket cleanup that
rewrote preserved maintainer quotes would be worse than the problem
(座位制度原话照抄不译). Whoever reconciles `AGENTS.md` should decide that
explicitly.

Out of scope, noted and not filed:

- **238** occurrences of the constant's literal value already sit in
archived comment bodies under `board/`. That is a pre-existing fact
about historical compliance with the `AGENTS.md` rule, not about
`Served-tier:` (which reads 0 there) — and no migration rides on this
PR.
- The C7 battery feeds the refusal an id **nobody has shipped** to prove
it binds a SHAPE. Spelling it lands no identifier because it identifies
no model — the same device `check-commit-card-trailers.mjs`'s own
battery uses. A comment beside the case now says so.
- `scripts/pm/dispatch-gates.mjs` is untouched: it is a `Restart-touch:`
trigger file of hold objectstack-ai#14290, and the fix did not need it. The constant
keeps its single value site there.

No changeset: the diff publishes nothing from any released package —
`.claude/**` and `scripts/pm/**` are on the fast track, and no path in
the diff appears in any package's `files[]`.

`Clause-②: no`

## 维护者速读(草稿)

**改了什么。** 契约复核记录里那行
`Served-tier:`,原先要求写模型档位常量的**值**(一串型号标识);现在改成写常量的**名字**。两个门禁(`check-clause2-carriers.mjs`
C7、`check-governed-queue-guard.mjs` 的 references
档记录读取器)同步改判,并新增一条拒绝:凡写成型号串的一律拒,且拒绝文案**不回显**那个串。

**为什么改。** `AGENTS.md` 明写「no model identifier lands in … **a comment**
…」,而复核记录就是一条 GitHub 评论。优先序 `AGENTS.md` >
座位惯例,分诊已定向。关键是这笔交易**零成本**:协议本身就说自述档位不是读数,真凭据是座位的转录 grep,那个动作不落任何仓库产物 ——
所以删掉型号串不损失任何证据价值。

**风险与代价(含回滚)。** 风险低但有一个真实的过渡成本:**本 PR 自己的复核记录必须用新拼写**,因为 merge-group
那条腿跑的就是本 PR 带的守卫;用旧拼写写的记录会被拒。存量迁移不在本 PR(板存档读数
0,但那份存档的窗口早于本约定,所以「舰队里有多少条评论带旧拼写」仍未测)。回滚 = revert 两个
commit;门禁与规则文本同笔回到旧拼写,无数据迁移、无发布面。两侧门禁都**只收紧不放宽**:该行仍必填,缺行仍是拒绝。

**席位意见。**

**你要做的。** 确认「预留原文维护者裁决里出现的型号串」这一边界情形该怎么定 —— 本 PR 只清理座位自己产出的那一类,⛔ 没有动
`AGENTS.md`,也⛔ 没有改写任何被原样保留的裁决引文。

---
_Generated by [Claude
Code](https://claude.ai/code/session_01DAcomhvR9kKizeYgg89Vo8)_

---------

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants