Skip to content

fix(pm): the Served-tier line names the tier constant, never a model identifier — AGENTS.md's comment rule (#18060) - #18087

Merged
os-project-manager merged 2 commits into
mainfrom
claude/issue-18060-served-tier-identifier-free
Sep 14, 2026
Merged

os-project-manager merged 2 commits into
mainfrom
claude/issue-18060-served-tier-identifier-free

Conversation

@claude

@claude claude Bot commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

Fixes #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, #17990) and 60b99552a (2026-09-13T15:00Z, #18036) are both ancestors of origin/maingit 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 sweepnode 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:

Landing note

This PR's own governed surface is .claude/skills/pm-dispatch/references/contract-review.mdcheck-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 formServed-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 dispatch-gates: STAGE-THEN-RUN reaches a program by an edge neither follow traverses — check:objectui-changeset inherits nothing from scripts/bump-objectui.sh #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

…identifier

A review of record is a GitHub comment, and AGENTS.md is unqualified about
that surface: no model identifier lands in a PR title or body, a comment, a
changeset, a doc or a code comment. The Served-tier: line required by the
in-seat contract-review protocol carried the constant's VALUE, so a record
could clear a carrier ONLY by putting an identifier into the very artifact
that rule names -- and two gates had already landed readers that compare
against that value, so the convention was on its way from a habit into a
gate, which is much harder to walk back.

So the accepted token is now the constant's NAME. Nothing evidential is
traded away: the line was never the reading. The protocol already says a
self-declared tier and a passed parameter are both non-readings, and the
authoritative control is the seat's own transcript grep against the
constant's value -- a grep that produces no repository artifact at all.

- references/contract-review.md: the two rule lines spell the token as the
  constant's name and name the refusal, at equal line count.
- check-clause2-carriers.mjs: C7 accepts the NAME; a token of identifier
  shape -- the old spelling included -- is refused with a remedy that names
  AGENTS.md's rule and never quotes the token back, because a refusal that
  quotes it lands the identifier in one more artifact.
- check-governed-queue-guard.mjs: the references-tier record reader imports
  that predicate (fail-closed on a rename) and carries the flag to its
  renderer, so the merge-queue refusal never prints one either.

Neither gate is widened: the line is still required, a missing one is still
a refusal, and the accepted token is still exactly one.

Claude-Session: https://claude.ai/code/session_01DAcomhvR9kKizeYgg89Vo8
Co-authored-by: Claude <noreply@anthropic.com>
The C7 battery proves the refusal binds a SHAPE by feeding it an id nobody
has shipped. Spelling it is safe precisely because it identifies no model,
and that is the same device the pre-push trailer rule's own battery uses --
worth one comment, so a later reader greps `claude-` and finds the answer
beside the case instead of filing it.

Claude-Session: https://claude.ai/code/session_01DAcomhvR9kKizeYgg89Vo8
Co-authored-by: Claude <noreply@anthropic.com>
@claude claude Bot added domain:skills skip-changeset PR has no user-facing published change; bypasses the changeset gate labels Sep 14, 2026
@github-actions github-actions Bot added the documentation Improvements or additions to documentation label Sep 14, 2026
@claude

claude Bot commented Sep 14, 2026

Copy link
Copy Markdown
Contributor Author

Verification on the final head — 57132927c

⚠️ Posted as a comment rather than folded into the body: a bare REST PATCH /pulls is measured in this repo to APPEND a second, bare footer while keeping the existing session-URL one (references/platform-readings.md). The body stays as created.

Gate sweep, re-derived and re-run after the last commit. node scripts/pm/dispatch-gates.mjs --commands derived the same 43 families on 57132927c as on the first commit (set diff: empty). All 43 ran, exit codes captured BEFORE any pipe, all exit 0:

✓ 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).

Lint, on the same head: pnpm exec eslint . --no-inline-config --format json6741 files, 0 errors, 0 warnings, exit 0. The whole repository, so no narrowing is claimed and none needs defending.

Ablation — the new refusal can actually fail

A guard that cannot fail is not a guard, so the new refusal was measured rather than asserted.

Mutation: one edit in servedTierStands() — accept the constant's VALUE as well as its NAME, i.e. exactly the widening this PR removes.

On-disk proof, before reading any result — the anchor was counted on both sides (1 occurrence of the removed text before, 0 after; 1 occurrence of the injected text after) and the blob hash moved (679dd831…ee9fb8ce…). ⛔ An editor's exit code is not evidence that anything reached disk.

Result:

battery mutated restored
check-clause2-carriers --self-test exit 1 — the three new #18060 cases failed by name exit 0, 605 pass
check-governed-queue-guard --self-test exit 1 — 2 of 233 failed, the record reading state: "stands" with servedIsIdentifier: true exit 0, 233 pass

⇒ the refusal is load-bearing in both files, and the guard leg fails through the imported predicate rather than a second parser of its own.

Restore proof: the target was checked out by naming HEAD explicitly — ⛔ never the bare two-dash form, which restores from the polluted index and exits 0 having undone nothing — then its blob hash compared equal to the HEAD blob and git diff HEAD read empty. The script armed a restore trap on EXIT, INT and TERM with an absolute repo-root path throughout, and treats an empty hash as a FAILURE rather than as nothing to compare.

⛔ No permanent artifact: the ablation is a one-shot proof, cited here and in the report, and no file from it is left in the tree.


Generated by Claude Code

@claude

claude Bot commented Sep 14, 2026

Copy link
Copy Markdown
Contributor Author
  • Served-tier: 1638/1638 CONTRACT_REVIEW_TIER — harness model stamp counted over this seat's own transcript (non-sidechain assistant messages a model served; <synthetic> harness notices excluded) at 2026-09-14T01:08Z and compared to the constant's value outside the repository; get_session external_metadata.last_served_model read equal to the constant at 2026-09-14T00:20Z. This is the spelling this PR makes the only one; the merge-group leg reads this record with the reader this PR ships.

Contract review

Head: 57132927 (PR #18087, card #18060) — reviewed at 2026-09-14T01:08Z by the skills seat at the contract-review tier. Governed references tier (references/contract-review.md, two equal-line edits) + non-governed PM tooling ⇒ the in-seat record lands it (PR #18036's tier, enforced on the merge-group leg).

① derived judgments (seat-measured on the fetched head, ⛔ not taken from the report):

  1. The premise the card led with is falsified in the urgent direction, and the fix follows it: on origin/main both readers already REQUIRED the identifier (273a6650 feat(pm): gate the clause-② carrier strip on the served tier the verdict declares #17990 C7; 60b99552 feat(pm): teach the merge-queue guard the governed surface references tier #18036 the references-tier record reader), so the two contract-review.md lines and both readers change together; the guard imports the recogniser lazily, fail-closed on a rename — still exactly one parser.
  2. The accepted token is the constant's NAME and nothing else: servedTierStands compares served.value === CONTRACT_REVIEW_TIER_NAME; a token of model-identifier SHAPE (the constant's value, or claude + hyphen + a model word) is refused with a remedy that names AGENTS.md's comment rule and ⛔ never quotes the token back; a missing line is still a refusal; the comparison is still exact (a family prefix and a constant-prefixed suffix are still refused). ⛔ Neither gate is weakened — the deltas are which token, and one new refusal class.
  3. contract-review.md: line 29 值写常量名 CONTRACT_REVIEW_TIER; line 56 Served-tier: ≠ 常量名 ⇒ exit 4,型号串按 AGENTS.md 拒 — equal-line, 60/60; check-skill-line-ratchet exit 0 on the head (an archive with the governed tree present).
  4. Self-tests on the head: check-clause2-carriers --self-test 605 cases pass (main 598: +7 new, 15 re-spelled, 0 deleted — 14 of the 15 old case names reappear verbatim; the fifteenth is renamed on purpose, its old assertion 「the fixture carries the constant's value」 being the refusal now pinned); check-governed-queue-guard --self-test 233 cases pass (115 assert( lines before and after). The dev's ablation (accept the VALUE too) fails 3 named C7 cases and 2 guard cases through the imported predicate, then restores to the HEAD blob — falsifiable, no artefact left.
  5. Gates: --ran 43 derived / 43 run / 0 NOT-MEASURED / 0 UNRUN, re-derived on the final head; repo-wide eslint 6741 files, 0 errors; no changeset owed (.claude/** + scripts/pm/**, nothing in any files[]). Checks on the head at 2026-09-14T00:53Z: 38 — 21 green, 15 skipped, 2 running, 0 red.
  6. Byte hygiene: the dev's identifier probe over the three files, the PR body and the report — no match, with a lit control (dispatch-gates.mjs, 1 line: the constant's single value site, untouched — hold dispatch-gates: STAGE-THEN-RUN reaches a program by an edge neither follow traverses — check:objectui-changeset inherits nothing from scripts/bump-objectui.sh #14290's trigger file).
  7. Scope held: three files; ⛔ AGENTS.md untouched; Clause-②: no holds — no contract path. --pair 18087 with THIS PR's reader → exit 0 at 2026-09-14T01:08Z before this record; the post-record run is on the card's ACCEPT.

② semver: unchanged — nothing published.

③ boundary flags: (a) the preserved-verbatim-quotation class — AGENTS.md's comment rule says 「a comment」 without qualification, while seat posts carry maintainer rulings that contain identifiers under 原话照抄不译 — surfaced by triage and the dev, ⛔ not decided here; this seat files it as a decision card (option A recommended: carve out preserved quotations in AGENTS.md). (b) Legacy: Served-tier reads 0 in the board archive only because the archive's window (highest card #17600) predates the convention (#17990); the fleet count stays unmeasured; the dispatch's probe path archive/ does not exist on that branch (the archive lives under board/) — the seat's instruction was wrong, the dev read the right path. (c) Cross-seat: the domain:services seat's Routine and records spell the old form — once this lands its own --pair refuses that spelling; this seat knocks on #6021 with the new form on landing.

Implemented-by: claude/issue-18060-served-tier-identifier-free
Reviewed-by: session_01DAcomhvR9kKizeYgg89Vo8

Verdict: PASS — ready + auto-merge by this seat.


Generated by Claude Code

@os-project-manager
os-project-manager marked this pull request as ready for review September 14, 2026 01:08
@os-project-manager
os-project-manager added this pull request to the merge queue Sep 14, 2026
Merged via the queue into main with commit 66e34d1 Sep 14, 2026
41 checks passed
@os-project-manager
os-project-manager deleted the claude/issue-18060-served-tier-identifier-free branch September 14, 2026 01:36
This was referenced Sep 14, 2026
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 domain:skills size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants