From 2a8afbc063efbb74fb5865c3f14e91857af98324 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 2 Sep 2026 15:04:30 +0000 Subject: [PATCH] fix(pm): the contract-review tier constant names the served tier and is the only place the tier's value lives MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bump CONTRACT_REVIEW_TIER in scripts/pm/dispatch-gates.mjs to the tier the platform serves, hoist its definition above the clause-① mandate table so the three table rows read the constant instead of spelling the literal, and make the two header comments, the seven self-test assertions and the three prose sites in .claude/skills/pm-dispatch/SKILL.md name the constant. The comparison against the served tier stays exact — no family or prefix floor. SKILL.md stays line-neutral under the skill line ratchet: two sentences are rebalanced across their own existing lines so every touched line stays under the 120-byte cap; no words are dropped. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1 --- .claude/skills/pm-dispatch/SKILL.md | 10 ++--- scripts/pm/dispatch-gates.mjs | 58 ++++++++++++++++------------- 2 files changed, 37 insertions(+), 31 deletions(-) diff --git a/.claude/skills/pm-dispatch/SKILL.md b/.claude/skills/pm-dispatch/SKILL.md index e078f3b149..bae4cd95e6 100644 --- a/.claude/skills/pm-dispatch/SKILL.md +++ b/.claude/skills/pm-dispatch/SKILL.md @@ -357,8 +357,8 @@ issue 任何人不得认领** —— 那意味着分诊还没走到,不是「可 跑到清空排空稳态,阈值吸收突发(实测 40 分钟到 13 张 ≈ 稳态四倍,审计式倾倒形态)—— 没有阈值,倾倒只能按到达顺序摊进常规轮,失掉同族同上下文一次读完的批次红利, 队尾同期继续增长;⛔ 二者不可互相替代。 -**紧急卡直接分诊** (维护者 2026-08-13):维护者点名或 p0 嫌疑 ⇒ 立即起 -`claude-fable-5` 分诊子代理,不等定时轮次;授权面 = 分诊本身(定级/路由/标签/既有评论格式), +**紧急卡直接分诊** (维护者 2026-08-13):维护者点名或 p0 嫌疑 ⇒ 立即起 `CONTRACT_REVIEW_TIER` 档 +分诊子代理,不等定时轮次;授权面 = 分诊本身(定级/路由/标签/既有评论格式), ⛔ 不写码不认领;产出落卡,与定时轮分诊同格式同效力(细则见 `references/dispatch-runbook.md`)。 **跨仓 pin 链的窗口级兜底也在本 sweep**(联动单第一产者仍是接受座位; ⛔ 只立单不执行 bump): objectui pin 落后且其队列已空 ⇒ 在本仓立/刷新 console bump 单; @@ -587,8 +587,8 @@ closes-list:部分交付 PR 特意写 Refs 而非 Fixes 时它恰为空,正是 **派发模型是 PM 的逐卡显式决定** —— 下限 `sonnet`(机械卡: 正确性由门禁农场机械判定,失败在漏跑门不在判断);默认判断档 `opus`(M/L、 裁决实施、任何带设计判断的卡;拿不准就升一档 —— 错派低档的返工贵过省下的额度); -上限 `fable`(最重协议/流程/编排卡,按卡取用非新默认)。**⛔ 强制条款两条**: -① 凡改**协议语义面**的卡一律 `model: "claude-fable-5"` —— 面 = 本 `SKILL.md` 主文件、 +上限 `fable`(最重协议/流程/编排卡,按卡取用非新默认)。**⛔ 强制条款两条**:① 凡改 +**协议语义面**的卡一律 `CONTRACT_REVIEW_TIER` 档(`--tier` 输出)—— 面 = 本 `SKILL.md` 主文件、 决策框架拷贝所在文件(以 `check:skill-frame-sync` 强制处为准)与 `.claude/agents/os-dev.md`(2026-08-20 收窄,维护者原话:「接受你的建议」—— fable 当审计师用,不当施工队用); 仅 `references/**` 面、或任一 pm-dispatch 治理面上一行级机械文本改动的卡降为 **opus 施工** @@ -596,7 +596,7 @@ closes-list:部分交付 PR 特意写 Refs 而非 Fixes 时它恰为空,正是 ①:复核席跑契约复审档位)—— opus 施工的治理面改动只经 fable 档复核到达维护者, ⛔ 不新增标签不新增链;② 凡**改变契约接受/拒绝行为或扩大公开面**的卡(`domain:spec` 语义面;判据即分诊代裁的机械边界测试与 spec 席内分派判据 —— `references/lanes/spec.md`, -⛔ 不另抄第二份)一律 `claude-fable-5`(维护者 2026-08-12 裁定, +⛔ 不另抄第二份)一律 `CONTRACT_REVIEW_TIER` 档(维护者 2026-08-12 裁定, 原话:「同意,就按语义面收窄,立卡并通知 spec 席」)—— 契约错毒化一切下游, 全仓最贵(条款②闸门见「入队与落地」)。**负边界(维护者 2026-08-28 裁定)**: 运行时权限/安全**行为**变更不是条款② —— 归人工地板「安全/权限边界」 diff --git a/scripts/pm/dispatch-gates.mjs b/scripts/pm/dispatch-gates.mjs index 7d11d5dc5a..f6c14686ea 100644 --- a/scripts/pm/dispatch-gates.mjs +++ b/scripts/pm/dispatch-gates.mjs @@ -7934,6 +7934,26 @@ export function residueLines( // Model-tier derivation — the half of the tier decision that IS a path question // --------------------------------------------------------------------------- +/** + * The single source of truth for the model tier the PM lane's governance + * reads — clause ②'s CONTRACT-REVIEW tier: the tier a card that changes + * contract accept/reject behaviour or widens the public surface must be + * dispatched at, and the tier the `needs:contract-review` re-review sub-round + * must itself be running at (its opening self-check reads this). Declared HERE + * and only here, as a constant, so a model upgrade is a one-line change in one + * file — the clause-① mandate rows below read it, the self-test compares + * against it, and the PM skill's prose names it, so the model id is spelled as + * a VALUE on this one line and nowhere else across `scripts/pm/**` and + * `.claude/skills/pm-dispatch/**` (a second value site is what let this + * constant drift from the served tier unnoticed). The comparison against the + * served tier is EXACT, never a family or prefix floor — widening a governance + * gate's accept set is the maintainer's decision, not a refresh-time + * convenience. The review label deliberately names WHAT is reviewed, never a + * model (maintainer, 2026-08-16: 「needs:fable-review 这个标签不好,下次模型升级怎么办」). + * Rulebook: `.claude/skills/pm-dispatch/SKILL.md` 「入队与落地」 — the clause-② gate and the `needs:contract-review` review-chain bullets. + */ +export const CONTRACT_REVIEW_TIER = 'claude-fable-5-1'; + /** * The globs that MANDATE a model tier for any card whose file surface touches * them, as DATA. This is the one list in this file besides CHANGE_KIND_GATES, @@ -7967,7 +7987,7 @@ export function residueLines( * about paths: * * - clause ①, encoded below: a card editing the PM lane's PROTOCOL-SEMANTIC - * surfaces is `claude-fable-5` — the pm-dispatch SKILL.md main file, every + * surfaces is `CONTRACT_REVIEW_TIER` — the pm-dispatch SKILL.md main file, every * file carrying an enforced copy of the decision frame (the COPIES table * of check:skill-frame-sync), and the dev-agent definition. Narrowed from * "the whole skill tree, references included" by the maintainer's @@ -7977,7 +7997,7 @@ export function residueLines( * file-surface predicate, and exactly what this script takes as argv; * - clause ②, NOT encoded and deliberately not: a card that changes contract * accept/reject behaviour or widens the public surface is also - * `claude-fable-5`. That is judged from the card's CONTENT — what the change + * `CONTRACT_REVIEW_TIER`. That is judged from the card's CONTENT — what the change * does to the contract — and a path cannot answer it. An ordinary-looking * surface (one package's source file) is the NORMAL shape of a clause-② * card. The closest a path can honestly get is SUSPICION: @@ -8061,35 +8081,21 @@ export function residueLines( export const MANDATORY_TIER_GLOBS = [ { glob: '.claude/skills/pm-dispatch/SKILL.md', - tier: 'claude-fable-5', + tier: CONTRACT_REVIEW_TIER, why: 'clause ① of the model-tiering ruling (narrowed to protocol semantics, 2026-08-20): the PM dispatch skill MAIN file is the lane\'s own operating protocol and a wrong edit propagates to every later dispatch — references/** dropped out of the path mandate that day', }, { glob: '.claude/agents/os-dev.md', - tier: 'claude-fable-5', + tier: CONTRACT_REVIEW_TIER, why: 'clause ① (2026-08-20 narrowing): the dev-agent definition is protocol semantics — every dispatched dev runs under it, and it carries an enforced copy of the decision frame', }, { glob: 'skills/objectstack-pm-dispatch/SKILL.md', - tier: 'claude-fable-5', + tier: CONTRACT_REVIEW_TIER, why: 'clause ① (2026-08-20 narrowing): the published PM skill carries two enforced copies of the decision frame (check:skill-frame-sync COPIES) and ships verbatim to third-party projects', }, ]; -/** - * Clause ②'s single source of truth for the CONTRACT-REVIEW tier: the tier a - * card that changes contract accept/reject behaviour or widens the public - * surface must be dispatched at, and the tier the `needs:contract-review` - * re-review sub-round must itself be running at (its opening self-check reads - * this). Declared HERE and only here, as a constant, so a model upgrade is a - * one-line change in one file. The review label deliberately names WHAT is - * reviewed, never a model (maintainer, 2026-08-16: 「needs:fable-review 这个标 - * 签不好,下次模型升级怎么办」), and the PM skill's prose points at this - * constant instead of spelling a model name. - * Rulebook: `.claude/skills/pm-dispatch/SKILL.md` 「入队与落地」 — the clause-② gate and the `needs:contract-review` review-chain bullets. - */ -export const CONTRACT_REVIEW_TIER = 'claude-fable-5'; - /** * The globs that make a surface a clause-② SUSPECT — a HINT, never a verdict. * @@ -15298,12 +15304,12 @@ function selfTest() { // mandate, and the ordinary surface that must NOT be mandated (a tool that // mandates everything is ignored, which loses the guardrail by the other road). const fableOf = (paths) => deriveTier(paths); - t('the pm-dispatch SKILL.md MAIN file is fable-mandatory', fableOf(['.claude/skills/pm-dispatch/SKILL.md']).tier === 'claude-fable-5'); - t('the dev-agent definition is fable-mandatory', fableOf(['.claude/agents/os-dev.md']).tier === 'claude-fable-5'); - t('the published PM skill (two enforced frame copies) is fable-mandatory', fableOf(['skills/objectstack-pm-dispatch/SKILL.md']).tier === 'claude-fable-5'); + t('the pm-dispatch SKILL.md MAIN file is fable-mandatory', fableOf(['.claude/skills/pm-dispatch/SKILL.md']).tier === CONTRACT_REVIEW_TIER); + t('the dev-agent definition is fable-mandatory', fableOf(['.claude/agents/os-dev.md']).tier === CONTRACT_REVIEW_TIER); + t('the published PM skill (two enforced frame copies) is fable-mandatory', fableOf(['skills/objectstack-pm-dispatch/SKILL.md']).tier === CONTRACT_REVIEW_TIER); t('a pm-dispatch REFERENCES path carries NO path mandate — the 2026-08-20 narrowing, inverted from the pre-narrowing pin', fableOf(['.claude/skills/pm-dispatch/references/review-checklist.md']).mandatory === false); const mixed = fableOf(['packages/spec/src/data/filter.zod.ts', '.claude/agents/os-dev.md']); - t('a MIXED surface is mandatory — one mandatory path decides, ordinary paths do not dilute it', mixed.mandatory && mixed.tier === 'claude-fable-5'); + t('a MIXED surface is mandatory — one mandatory path decides, ordinary paths do not dilute it', mixed.mandatory && mixed.tier === CONTRACT_REVIEW_TIER); t('the mixed verdict reports the offending path, not just the verdict', mixed.hits.length === 1 && mixed.hits[0].path.endsWith('.claude/agents/os-dev.md')); t('an ordinary surface carries no path-derived mandate', fableOf(['packages/spec/src/data/filter.zod.ts']).mandatory === false); t("this tool's own file is not mandatory — the card that added this section reads itself correctly", fableOf(['scripts/pm/dispatch-gates.mjs']).mandatory === false); @@ -15316,7 +15322,7 @@ function selfTest() { // The rendering is where the invariant is actually delivered: the claim // comment quotes THESE lines. const mandLines = tierLines(mixed).join('\n'); - t('the mandatory rendering names the tier', mandLines.includes('claude-fable-5')); + t('the mandatory rendering names the tier', mandLines.includes(CONTRACT_REVIEW_TIER)); t('the mandatory rendering says MANDATORY in a word a reader cannot skim past', mandLines.includes('MANDATORY')); t('the mandatory rendering shows its provenance — the path and the glob that covered it', mandLines.includes("- .claude/agents/os-dev.md ⇢ '.claude/agents/os-dev.md'")); t('the mandatory rendering names every sanctioned exit, so a downgrade needs a stated reason', mandLines.includes('quota exemption') && mandLines.includes('opus, never lower') && mandLines.includes('one-line-class') && mandLines.includes('proactive low-headroom')); @@ -15337,7 +15343,7 @@ function selfTest() { let ambiguityRefused = false; try { deriveTier(['.claude/skills/pm-dispatch/SKILL.md'], [ - { glob: '.claude/skills/pm-dispatch/**', tier: 'claude-fable-5', why: 'a' }, + { glob: '.claude/skills/pm-dispatch/**', tier: CONTRACT_REVIEW_TIER, why: 'a' }, { glob: '.claude/skills/**', tier: 'opus', why: 'b' }, ]); } catch { @@ -15375,7 +15381,7 @@ function selfTest() { /* frameFiles stays empty and the cases below fail loudly */ } t('the frame-sync COPIES table is readable and non-empty, so the pin below is not vacuous', frameProbe.status === 0 && Array.isArray(frameFiles) && frameFiles.length > 0); - t(`every frame-sync-enforced copy is fable-mandated (unmandated: ${frameFiles.filter((f) => !deriveTier([f]).mandatory).join(', ') || 'none'})`, frameFiles.length > 0 && frameFiles.every((f) => deriveTier([f]).tier === 'claude-fable-5')); + t(`every frame-sync-enforced copy is fable-mandated (unmandated: ${frameFiles.filter((f) => !deriveTier([f]).mandatory).join(', ') || 'none'})`, frameFiles.length > 0 && frameFiles.every((f) => deriveTier([f]).tier === CONTRACT_REVIEW_TIER)); t('the SKILL.md main file and the dev-agent definition are declared in their own right, not only via the frame table', MANDATORY_TIER_GLOBS.some((g) => g.glob === '.claude/skills/pm-dispatch/SKILL.md') && MANDATORY_TIER_GLOBS.some((g) => g.glob === '.claude/agents/os-dev.md')); // ── Clause-② suspicion (the enqueue-gate card): hit / no hit / wording ────