# artifact-routing 制品自声明 P2（规则型）— 设计 spec

- **日期**：2026-07-17
- **状态**：**设计待 owner 审**（3 决策点，见 §7）→ 审后实现 + planted-drift → 报 owner 复审后 commit
- **tracked**：backlog INFRA-F68 · TRIG/REWORK 组 · REWORK-01 P2
- **前序**：P1（文件型）已 ship `143e2990`，spec `2026-07-17-infra-f68-rework-01-artifact-routing-self-declaration-design.md`。本 spec 续 P1 的 §3.2 分期承诺。
- **纪律**：改**阻塞型共享 pre-commit gate**（`.husky/pre-commit → node scripts/audit-artifact-routing.mjs`），误报卡全员提交 → 沿用 DUP-03 / P1 范式：先设计 + planted-drift 严测，禁赶工（[[feedback_root-cause-scope-creep]] / pickup 纪律）。

---

## 1. 问题（承 P1，亲核收窄）

P1 把**文件型制品**（skill/prompt frontmatter）改成了自声明扫描，加制品零改脚本。但脚本里仍留 `DOC_ARTIFACTS` **硬编码 3 条**（PRD / UX 交付卡结构 / 交付物同步），P1 spec §3.2 明标"保留不动，归 P2 自声明化范畴"。

**根因同 P1**：这 3 条是制品清单的**第二处真源**（脚本数组 ≠ 制品本体），违反 meta-rules 规则 1（真源单一）。新增一个带唤醒词的规则型制品，漏加数组则静默不受检。

## 2. 亲核发现：规则型制品的"可唤醒"真相（修正 P1 spec 的宽口径）

P1 spec §1 曾写"可唤醒制品 ≈48（skill 13 + prompt 3 + 触发器 ~17 + M-rule ~15）"。**本 spec 亲核 3 份真源后判定该口径不准**：

| 规则型制品 | 数量 | 是否有**用户唤醒词** | 路由性质 |
|---|---|---|---|
| meta-rules 触发器 A–Q | 17 | **0 个有** | 全是 AI 内部行为触发器（提规则时 / 排期时 / 诊断时…），由 AI 侦测条件自触发，非用户短语唤醒 |
| mockup-conventions M-rule（M0–M52） | ~40 | **仅 2 个有**（M23.0 ← `@TVU UX说明`；M-DISCIPLINE ← `同步交付物`）| 其余靠 mockup-conventions jump 表 AI 自动路由，或起手就地应用，无用户唤醒词 |
| design-process 段（Step B）| 1 | **1 个有**（`@TVU PRD`）| — |

**结论**：真正"带用户唤醒词的规则型制品"= 恰好脚本里那 **3 条硬编码 DOC_ARTIFACTS**（PRD / M23.0 / M-DISCIPLINE）。触发器与绝大多数 M-rule **没有用户唤醒词**，在 P1"豁免权在制品"模型下本就该 skip。

这把 P2 的问题从"给 32 条规则加声明"收窄成**两件事**：
- **(a) 把 3 条硬编码 DOC_ARTIFACTS 改成自声明**（消除第二真源，与 P1 同构的 dead-wakeWord 防漂）。
- **(b) 让将来任何"带用户唤醒词的规则型制品"都能被同一自声明机制自动纳入**（加制品零改脚本）。

## 3. 范围岔口（§7 决策点 1）：窄口径 vs 宽口径

亲核后暴露一个 P1 spec 没讲清的岔口——规则型制品有**两种不同的"路由"**，防漂目标不同：

| 口径 | 防的漂移 | 校验目标 | FP 面 | 与 P1 同构 |
|---|---|---|---|---|
| **窄（推荐）= 用户唤醒词对等** | 死唤醒词：规则声明能被某短语唤醒，但该短语不在 WAKE-WORDS.md 注册表 → 路由层 surface 不到 | 声明的 wakeWord ∈ `WAKE-WORDS.md` | **低**（字符串包含判定，同 P1）| ✅ 完全同构 |
| **宽 = AI 路由覆盖** | 死规则：某触发器/M-rule 存在，但不在其路由索引（mockup jump 表 / 触发器目录）里 → AI 永不被路由到 | 每个触发器/M-rule 出现在对应索引 | **高**（要枚举 ~57 条规则、判定"哪些该进索引"是判断题）| ✗ 全新审计 |

**推荐窄口径**：
- 它精确对齐 P1 的防漂保证（dead-wakeWord），FP profile 与 P1 一致（已验证安全）。
- 它根治本 entry 的原始根因（3 条硬编码第二真源）。
- 宽口径是**另一件事**（"规则路由覆盖完整性"），FP 高、含判断成分、值不值得做该单列 entry，不塞进 P2 赶工。

**宽口径显式 DEFER**：作为独立候选（可挂 INFRA-F68 L7 治理 / DUP 组），本 spec 不做。

## 4. 方案（窄口径）：doc-embedded 规则自声明

### 4.1 声明语法

markdown 段无法带 per-section YAML frontmatter → 用**紧贴规则标题的 HTML 注释**（渲染不可见、机器可解析、与规则同处 = 真源单一）：

```markdown
### 触发器 XX：……
<!-- artifact-routing: wakeable=true; wakeWords=["@TVU XXX"] -->
```

3 条现存制品的声明落点（def 站点，就是它们各自的 defAnchor 段）：

| 制品 | 声明落点（文件 : 段）| 声明内容 |
|---|---|---|
| PRD | `docs/internal/design-process.md` : Step B | `wakeable=true; wakeWords=["@TVU PRD"]` |
| UX 交付卡结构 | `docs/internal/mockup-conventions.md` : §M23.0 | `wakeable=true; wakeWords=["@TVU UX说明"]` |
| 交付物同步 | `docs/internal/mockup-conventions.md` : §M-DISCIPLINE.SYNC | `wakeable=true; wakeWords=["同步交付物"]` |

> 注：3 条唤醒词已全部在 WAKE-WORDS.md table A 注册（第 14/15/16 行亲核），故加声明后现树即 PASS。

### 4.2 gate 行为

gate 扫 3 份 doc 真源（`meta-rules.md` + `mockup-conventions.md` + `design-process.md`）里的 `artifact-routing:` HTML 注释标记，对每个 `wakeable=true` 的声明，断言其每个 wakeWord ∈ `WAKE-WORDS.md`。**加带唤醒词的规则型制品 → 只在其段加一行注释，脚本零改动。**

### 4.3 误报防控（三条，照搬 P1，保守优先）

| 场景 | gate 行为 | 理由 |
|---|---|---|
| 段无 `artifact-routing:` 注释 | **skip（不校验）** | 保守默认：没声明就不管（触发器/大多数 M-rule 天然落此，绝不误报阻塞）|
| 注释声明 `wakeable=false` | **skip** | 豁免权在制品（显式退出）|
| 注释存在但解析失败（格式坏 / wakeWords 缺失）| **fail-open：warn 不阻塞** | 解析器脆弱不该卡全员 commit；报警人工看 |

### 4.4 §7 决策点 2：旧 3 条的 `route` 断言是否保留

现 `DOC_ARTIFACTS` 每条断言 **def + route + wake 三件**（defAnchor 在 defFile / routeKeyword 在 routeFile / wakeWord 在 wakeFile）。自声明化后：
- **def**：声明注释本身就在 def 段 → 存在性天然被证。
- **wake**：wakeWord ∈ WAKE-WORDS（保留，核心防漂）。
- **route**（routeKeyword 出现在某路由表）：自声明模型不含此项。

两个选择：
- **(推荐) 弃 route 断言**：自声明 + wakeWord 注册已保证"可被唤醒且 surface 得到"；routeKeyword 字面匹配原是"是否接进路由"的弱代理，与 P1 保持一致（P1 只校 wakeWord）。
- (保留) 声明里加可选 `routeAnchor` 字段继续校：多一层保证，但语法变复杂、FP 面略增。

倾向弃，减复杂度 + 对齐 P1；owner 定。

## 5. 实现改动面（窄口径，审批后）

1. `scripts/audit-artifact-routing.mjs`：
   - 新增 `collectDocRuleArtifacts(root)` 扫 3 份 doc 的 `artifact-routing:` 注释（正则解析 wakeable / wakeWords，复用 P1 `parseSelfDeclaration` 的解析风格，注释语法适配）。
   - 新增 `checkDocRuleArtifact()`（skip / warn / ok / fail 四态，同 P1 `checkFileArtifact`）。
   - **移除** `DOC_ARTIFACTS` 硬编码数组 + 其遍历段（决策点 2 若保留 route 则改造而非移除）。
   - 单测扩展（同 P1 的 10 单测风格：解析各态 + 校验各态）。
2. 3 份 doc 各加 HTML 注释声明（§4.1 表）。
3. 不改 `.husky/pre-commit` 挂载行（脚本路径不变）。
4. 不改 WAKE-WORDS.md 内容（只读它校验；发现真死唤醒词单独报 owner，不顺手改 — [[feedback_root-cause-scope-creep]]）。

## 6. planted-drift 严测协议（实现后必跑，DUP-03 / P1 范式）

1. **现树 PASS**：`node scripts/audit-artifact-routing.mjs` exit 0（3 doc 声明 + P1 的 7 skill 全绿）。
2. **植入无路由假声明必 exit 1**：临时在 meta-rules.md 某段下加 `<!-- artifact-routing: wakeable=true; wakeWords=["@TVU 不存在XYZ"] -->`（不在 WAKE-WORDS）→ gate 必 exit 1 且指名该文件+唤醒词。
3. **植入豁免必 PASS**：改 `wakeable=false`（或删注释）→ gate 必 exit 0（skip）。
4. **解析失败 fail-open 验**：植入 `wakeable=true` 但缺 `wakeWords=` → gate warn 不阻塞、exit 仍取决于其他项。
5. **复原**：删植入 → gate 回 exit 0，工作树干净。

全程截真实 stdout + exit code 作证据，不凭"应该能过"。

## 7. owner 决策点（审批后才实现）

1. **范围口径**：窄（用户唤醒词对等，推荐）还是宽（+AI 路由覆盖，DEFER 建议）？
2. **旧 3 条 route 断言**：弃（推荐，对齐 P1）还是保留（加 `routeAnchor` 字段）？
3. **声明语法**：HTML 注释 `<!-- artifact-routing: wakeable=...; wakeWords=[...] -->`（推荐）是否可接受，还是要别的落点？

## 8. enforcement 层级（触发器 K 强制）

- **层级 = L4（pre-commit gate）**，与 P1 / 被替换的旧脚本同级、不降级。
- 为何不是 L1/L3：制品-注册表一致性**客观可测**（字符串包含判定），必须机器强制。
- 为何不升 L5（CI）：pre-commit 已覆盖 owner 直推 + 贡献者 PR 两路径（gate 平权原则）；升 CI 正交、非本项必需。

## 9. 范围边界

- **不含宽口径**（AI 路由覆盖完整性）——DEFER，另单列。
- **不动 WAKE-WORDS.md 内容**、**不动 .husky 挂载**。
- **不碰任何 .vue**（本 entry 纯治理/脚本层，不触视觉门）。
