# 设计：INFRA-F133 `audit:claim-vs-livesource` —— 把硬规则 #10 的一个形态从 L1 抬到 L4+L5

> **状态**：设计待 owner review（2026-08-20）
> **Backlog**：`INFRA-F133`（号取自 `pnpm new-backlog INFRA`，非手编）
> ⚠️ **本号是 2026-08-20 晚订正过的 —— 初版写的 `INFRA-F132` 撞号了**，而 §7.1 末尾那句「落地时必须
> 重新核一次」正是为此写的、当天就中了。撞的经过与两个由此暴露的真实缺口（取号器只读本地 / 本闸家族
> 看不到 spec 里的号）记在 §3.2。
> **闸名**：`audit:claim-vs-livesource`
> **治的规则**：AGENTS.md 硬规则 #10「结论必须对活源验证，不凭摘要 / 镜像 / 记忆下判断」
> **本设计的数据来源**：2026-08-20 一次性原型探测（4 个 probe 脚本，跑在本仓真实文档上）。
> 下文每个数字都标了复现方式；**没标复现方式的数字一律视为未验证**。
> **基线 commit**：`a09ba7ef`（含当日对 #10 尾注的订正 —— 见 §3.1，那次订正正是本设计的活体实证）
> **实施计划**：[`docs/superpowers/plans/2026-08-20-infra-f133-claim-vs-livesource-gate.md`](../plans/2026-08-20-infra-f133-claim-vs-livesource-gate.md)（6 个 Task，含 §8 三条历史 bug 的回归用例与 §9 的退档判定）
> ⚠️ **as-built ≠ 本文**：S2 已于 2026-08-20 按 §9 退档为 **report-only**（⇒ 下文 §6 判据表里 S2 那格的「阻塞」是设计意图，不是当前实况）；as-built 状态的真源 = [`docs/internal/backlog.md` 的 `INFRA-F133` entry](../../internal/backlog.md) + [闸脚本头注释](../../../scripts/audit-claim-vs-livesource.mjs)。本文是意图的历史记录，⛔ 不回改正文。

---

## 0. 一句话说明

计划文件里写的具体值（prop 名 / 导出名 / 状态集）现在**没有任何机制核对活源**，
本闸让机器代替人做「对活源核」这个动作——**不要求作者提供证据，闸自己去核**。

---

## 1. 总计划 / 总数字

⛔ **形态清单与各自层级的真源是 [AGENTS.md 硬规则 #10 正文](../../../AGENTS.md)，此处不维护第二份**
（该条自己刚在 2026-08-20 立下这条纪律：「覆盖面与仍看不到的三样，真源是闸脚本头注释，别在本表维护第二份」）。

本节只给数字与本轮增量：

- #10 正文当前登记 **5 种**「信二手源」形态（①–⑤）+ 本设计新增登记 **1 种**（计划里的具体值未对活源核）
- **本轮前**：仅形态 ⑤ 在 L4+L5（`audit:rule-number-collision`，当日刚扩面到 backlog entry ID）
- **本轮后**：⑤ 与新增形态共 **2 个在 L4+L5**，其余 **4 个仍 L1**
- 硬规则 **#9**（不得编造工具输出）本轮**不碰**，仍 L1

本轮要改的 Enforcement 列见 §10（那是**改动清单**，不是形态表的镜像）。

---

## 2. 本次范围

**做**：新增 1 个闸，覆盖「指令型文档的结构化区域里，组件 / prop / 导出名断言 × 活源不一致」。

**不做**（各有理由，见 §9）：
- 闸 2（派生镜像引用检测）—— 实测假阳性过高，只留登记
- 硬规则 #9 的机械化
- `audit-hard-rule-compliance.mjs` 的命名/内容错位修正（它名为硬规则合规，实检 memory 的 H1–H3）
- 散文区域的断言（只 report-only，不阻塞）

---

## 3. 问题：#10 停在 L1 的实测代价

扫 15 份 `~/.claude/work-logs/*.md`（复现：`grep` 关键词计数）：

| 关键词 | 命中 |
|---|---|
| 纠正 | 106 |
| 误判 | 45 |
| 漏了 | 19 |

其中最硬的一条实证，`docs/superpowers/plans/2026-07-30-v1x-next-batch-ai-consumable-page-layer.md`
Task 4 自己写着：**「实测结果 —— 草稿 4 条具体断言里 3 条要改（同 Task 3 的命中率）」**：

| 草稿写的 | 活源实测 |
|---|---|
| `Breadcrumb` 表达层级位置 | Breadcrumb 本体**零 props** |
| 详情内分段用 `Tab` / `TabList` / `TabItem` 三件套 | Tab 与 TabList 是**两种用法二选一** |
| 只 4 个态 | 漏了 `master-empty`（Table 明明有 `#empty` 槽） |

**为什么不能再写一条文字规则**：`audit-plan-lifecycle.mjs` 文件头已记录同型教训——
兄弟目录 README 逐字写着同一条规则、纯 L1，此后 11 天里该归档的一份都没归档，
结论是「**再写第三份文字 = 无效动作**」。#10 的处境相同。

### 3.1 本设计撰写过程中的活体实证（2026-08-20，比 07-30 那例更贴切）

写本设计时，作者（AI）读 `AGENTS.md` 硬规则 #10 正文，并据此在设计里写下
「⑤ 已上闸，⛔ 该闸只覆盖三份规则真源的编号，**看不到 backlog entry ID**」。

**那句话在被读到时已经过期。** 同一天 `16:02:41` 的 commit `a09ba7ef` 正是把它订正掉——
当日 INFRA-F109（已完成删档，此处仅历史提及）落地后，backlog entry ID **已进覆盖面**，仍靠纪律的只剩「已删档号被重新占用」。
另一个 session 在同一仓库并行工作，本 session 读文件的时机恰好卡在那次 commit 前后。

**这次自撞的价值**：
1. 它命中的正是 #10 自己 2026-08-11 尾注补的那个形态——**「你自己的本地工作树也是一种会过期的镜像」**。
   读的是本地实际文件（不是摘要、不是 memory、不是面板 UI，完全符合 #10 的字面要求），仍然读错。
2. 它说明 L1 的失效**与主观努力无关**：本设计的整个目的就是防这个病，写它的过程仍然中招。
3. 它给出了本闸覆盖面的一个**已知缺口**：本闸只核「文档断言 × 代码活源」，
   **不核「文档断言 × 另一份文档的当前版本」**。#10 正文这类规则文档的时效性，本闸抓不到。
   → 登记为缺口，不假装覆盖（见 §10）。

⚠️ 因此本设计的 §5–§10 已按 `a09ba7ef` 后的 #10 正文校正；**基线 commit 写在文件头**，
下一个读本设计的人应先 `git log -1 AGENTS.md` 核基线是否又已过期。

### 3.2 第二次自撞：本设计自己的编号撞了号（2026-08-20 晚，实测）

本设计初版取号 `INFRA-F132` 并 commit（本地 `85d0f93d`，未推）。同一天更晚，`origin/master`
上的 `5cd26e41` 把 **`INFRA-F132` 用给了另一件事**（发给 consumer 的 `audit:consumer-mockup --file`
命令结构上跑不通）。按硬规则 #10「全局命名空间的活源是 `origin/master`」⇒ 要改号的是本地这份，
已改为 **`INFRA-F133`**（在 base = `origin/master` 的 worktree 里重跑取号器所得，已用号 81 个含 132）。

**它暴露了两个不是理论风险的缺口**：

1. **`pnpm new-backlog` 只读本地文件**（`scripts/new-backlog.mjs` 的 `SOT_FILES` 是三条相对路径，
   `readFileSync` 本地磁盘）。在本地 master 落后 origin/master 两个 commit 的工作树上，它**自信地
   给出了一个已被占用的号** —— 与它存在的目的正好相反。撞号闸有 `--require-remote` 让 S2 从
   fail-open 反转为 fail-closed，**取号器没有任何对应物**。⇒ 已随本轮修：见该脚本头注释。
2. **撞号闸看不到只出现在 spec / plan 里的号**：闸按 `backlog.md` 的 `###` **标题行**判「谁在声明
   一条 entry」，而本设计取了号却（按 §7.1 的设计）**还没落 backlog entry** ⇒ 号只存在于本文件与
   commit message 里，闸的覆盖面结构上够不着。

   **⛔ 「把 spec/plan 目录纳入闸的覆盖面」这条路已被实测否决（2026-08-20，不是凭直觉排除）**：
   `docs/superpowers/{specs,plans}` 共 **58** 份，其中 **41 份**正文里出现 `INFRA-F…` 号
   （`INFRA-F95` 在同一批文件里出现 **21 次**），**全是引用**；而带结构化 `**Backlog**` 字段的只有
   **4 份**，且其中 **3 份**该字段的语义是「本设计服务于哪条**既有** entry」
   （`closes INFRA-F69①` / `INFRA-F55 支柱①` / `INFRA-F68 L1 §APID-01`），**不是**「本文声明了一个新号」。
   ⇒ 散文里**「引用一个号」与「声明一个号」不可机械区分**，而唯一像「声明槽」的那个字段，
   既有语义恰好是前者。照 S1（本地新增的 ID 若也存在于 origin/master 即 FAIL）扫这 41 份，
   每一份新 spec 引用的每一个既有号都会当场报红 —— 那不是收紧，是把闸变成噪声源。

   **⇒ 实际处置：修第一因，不扩闸。** 撞号的第一因不是「闸没看见」，而是
   **`pnpm new-backlog` 只读本地磁盘、把一个已占用的号自信地发了出来**。已于同日改成扫
   「本地 ∪ `origin/master`」并集（拿不到远端时 fail-open 但**大声打印降级**，另给
   `--require-remote` 反转为 fail-closed —— 形态与撞号闸的 S2 一致）。真源与故障复现探针见
   `scripts/new-backlog.mjs` 头注释。修完的效果是**撞号根本不会发生**，比事后检测强一档。

   **⇒ 剩下的残余风险如实登记，不建第二条闸**：两条线**几乎同时**取号（都还没推）且其中一条
   始终不落 entry ⇒ 闸仍看不见。这与撞号闸头注释里已登记的边界 ②「两人几乎同时 commit 且都还没推」
   是同一件事，归 L1 纪律。要机械化它，得先在 58 份 spec/plan 上建立一个「声明槽」约定 ——
   那是**约定变更**，不是加个正则，值得 owner 单独拍，⛔ 别顺手做掉。

---

## 4. 设计推翻史（三次实测推翻，解释为什么最终范围这么小）

保留这一节，是因为**范围收缩本身是结论**，不是妥协。

| 轮 | 假设 | 实测 | 结论 |
|---|---|---|---|
| 1 | 交付物结构自查清单（owner 走查挑错编码化） | work-log 里「走查补正」全库仅 3 次命中，而「误判」45 次 | **A 类占比最小，方向错** |
| 2 | 「已执行的 Step 必须挂 evidence」 | 67.1% 红线率；校准后作用面几乎空——18 份计划里 14 份 checkbox 是 `0/N` | **不存在可靠的"已执行"信号** |
| 3 | 「引派生镜像必须给真源指针」 | 71 处引用、48 处报红，多数假阳性（`- Produces: ds-bundle/…`、资产清册列路径） | **"当依据"vs"当对象"是语义，非词法** |

三次的共同教训：**「是不是断言」「断言归属谁」都不是词法特征，扫散文的方向必然塌。**

---

## 5. 架构

### 5.1 分层

| 层 | 位置 | 本轮动作 |
|---|---|---|
| 规则真源 | AGENTS.md #10 正文 | **正文不动**，只改 Enforcement 列 + 给形态逐条标层级 |
| 形态登记 | 同上（①–⑤ 已在正文里） | 就地标注，**不新建清单**（`audit-rule-inventory` 的教训：一份清单散在 N 处必漂） |
| 闸 | `scripts/audit-claim-vs-livesource.mjs` | 新增 |
| 触发 | `.husky/pre-commit` + `.gitea/workflows/pr-checks.yml` | 双挂（`audit:gate-ci-parity` 硬要求） |

### 5.2 数据流

```
staged / PR-changed 的 .md 命中指令型目录
  → 闸启动
      活源侧 ← src/index.ts + src/canonical/index.ts 导出并集；src/canonical/*.vue 的 defineProps
      文档侧 ← 仅结构化区域（定义见 §6.1）
  → 对不上 → 报 file:line + 活源实况 + 修法
  → 阻断 commit / PR
```

### 5.3 命名

从 `assertion-evidence` 改名为 `claim-vs-livesource`：判据已从"要求作者给证据"
变成"闸自己核活源"，**名字必须跟着职责变**，否则下一个读它的人会误判它要什么。
（反面教材就在仓内：`audit-hard-rule-compliance.mjs` 名为硬规则合规、实检 memory 规则。）

### 5.4 四个刻意的设计选择（别"顺手改掉"）

1. **扫描面 `import { MANAGED_DIRS } from './audit-plan-lifecycle.mjs'`**，不复制清单——否则本闸自己成了"第 N 份清单"。
2. **判据正文留在 `.md`，脚本只放检测手段**（meta-rules 反模式 #1）。
3. **不拦 `Read`，只判写进文件的断言**——hook 只能注入文本，那还是 L1。
4. **不可机检的形态显式登记为缺口**，不假装覆盖。

---

## 6. 判据（沿用本仓 S1–S5 阻塞 / report-only 分层）

| 判据 | 内容 | 档位 |
|---|---|---|
| **S1** | 活源提取分母非空：barrel 导出解析为 0 / `src/canonical` 空 → 红 | 阻塞·fail-closed |
| **S2** | 结构化区域内 prop / 导出名断言 × 活源不一致 → 红 | 阻塞 |
| **S3** | 扫描面 fail closed：受管目录不存在 / 0 份 `.md` → 红；排除 `.claude/worktrees/` 与 `docs/_archive/` | 阻塞 |
| **S4** | 豁免表 shrink-only：`claim-exempt.json` 里某条已不再命中 → 红，要求删行 | 阻塞 |
| **S5** | 散文区域的疑似断言 → **只印不红** | report-only |

### 6.1 「结构化区域」的定义（分两档，对应 §9 的收窄路径）

**初始档（校准起点）**——结构本身已表达归属关系的三类区域：

| 区域 | 形态 | 归属如何确定 |
|---|---|---|
| markdown 表格同行 | `\| Component \| prop \| … \|` | 同行即同一断言 |
| 定义列表 | `- \`Component\`：… \`prop\` …` | 行首标识符为宿主 |
| 固定字段块 | `Files:` / `Interfaces:` / `Produces:` 下的列表项 | 字段块的宿主声明 |

**收窄档（校准不达标时退到这里）**：**只保留 markdown 表格同行**，
放弃定义列表与字段块——它们的归属靠行首约定，比表格弱。

⛔ 两档都**不含散文行**。散文归 S5 report-only，不因"看起来像断言"进 S2。

**S3 的 worktrees 排除是实测得出的**：本仓 `.claude/worktrees/` 下有 20+ worktree，
每个都有计划文件副本，不排除会让同一条 finding 重复 20 次。

**S5 的意义**：让散文那一半**可见但不阻塞**，积累数据后再决定升不升格，
而不是现在拍一个阈值。

---

## 7. 落地

### 7.1 文件清单

| 动作 | 文件 |
|---|---|
| 新增 | `scripts/audit-claim-vs-livesource.mjs` |
| 新增 | `tests/audit-claim-vs-livesource.test.ts` |
| 新增 | `figma-data/audit-allowlist/claim-exempt.json`（初始 `[]`） |
| 修改 | `package.json` → `"audit:claim-vs-livesource"` |
| 修改 | `.husky/pre-commit` → 条件触发块 |
| 修改 | `.gitea/workflows/pr-checks.yml` → 同判据 |
| 修改 | `AGENTS.md` #10 的 Enforcement 列（按 §10 清单；#9 不动） |
| 修改 | `docs/meta-rules.md` → 触发器 K 登记 |
| 修改 | `docs/internal/backlog.md` → 新增 `INFRA-F133` entry（stub 由 `pnpm new-backlog INFRA` 生成） |

⚠️ 落地时 `INFRA-F133` 这个号**必须重新用 `pnpm new-backlog INFRA` 核一次**——
本设计取号于 2026-08-20 基线 `a09ba7ef`，并行 session 可能已占用（§3.1 就是这么中招的）。

### 7.2 存量策略：只扫改动文件

**闸只判 staged（pre-commit）/ 本 PR 改动（CI）的文件，不全量扫 live tree。**

一举解决三件事：
- 历史 ghost 断言不受影响——**历史断言是历史记录**，组件演进后本不该与当前活源一致
- 不需要 baseline 快照文件（那本身又是一份会 drift 的镜像）
- 上闸当天零红，之后逐步收紧

**代价（明说）**：从此不再改动的文件，其错断言永不被检。
可接受——那类文件由 `audit:plan-lifecycle` 的可达性判据负责归档。

### 7.3 实现范式（沿用 `audit-layout-tokens.mjs`）

```js
export const MANAGED_DIRS          // import 自 audit-plan-lifecycle.mjs，不复制
export function extractLiveSource({ repoRoot })
export function extractClaims({ text })
export function evaluate({ claims, live, exemptions })
if (process.argv[1] && resolve(process.argv[1]) === resolve(fileURLToPath(import.meta.url))) { main() }
```

四条约束：
1. **必须有 `IS_MAIN` guard** —— 否则不可单测（`audit-hard-rule-compliance.mjs` 现在的病）
2. 纯函数 export，vitest 可直接测
3. CI 侧**直接调 `node` 而非 `pnpm run`** —— 仓库注释点名的 `pnpm --` 参数透传坑
4. 豁免语法与既有闸对齐

---

## 8. 验收：用历史真实 bug 做回归

把 2026-07-30 那三条已知错断言还原成草稿态注入，**闸必须红且逐条点名**：

| 注入 | 闸必须报出 |
|---|---|
| `Breadcrumb` 有层级 props | Breadcrumb 零 props |
| `Tab` / `TabList` / `TabItem` 三件套 | 两者 props 不相交，非三件套 |
| 状态集只 4 个（漏 `master-empty`） | Table 有 `#empty` 槽 → 状态集不对称 |

**抓不到这三条就是没做成**——不接受"判据合理但抓不到历史 bug"。

活源侧已验证可提取。**复现命令**（任何人可直接跑，不依赖一次性脚本）：

```bash
for c in Breadcrumb Table Tab TabList; do
  printf '%-12s ' "$c"
  sed -n '/defineProps<{/,/}>/p' "src/canonical/$c.vue" \
    | grep -oE '^\s*[a-zA-Z][A-Za-z0-9]*\??:' | tr -d ' ?:' | paste -sd, -
done
```

2026-08-20 实际输出：

```
Breadcrumb                                                      ← 零 props
Table        align,type,columns,data,striped,loading,rowKey,selectedKeys
Tab          modelValue,fill,color
TabList      items,modelValue,fill
```

与 07-30 人工实测结论**逐条吻合** —— 这是本方案的可行性证明。

另加标准故障注入：活源解析归零 → S1 红；豁免表塞已修条目 → S4 红；受管目录清空 → S3 红。

---

## 9. 校准迭代与退档条件（本设计唯一的未知量）

`extractClaims` 的结构化区域解析需对着真实表格调。循环：

1. 在 34 份指令型文档上跑，人工判前 30 条真假阳性
2. 目标：**假阳性率 < 10%**，且 §8 三条必须在真阳性里
3. 达不到 → 收窄区域（先只收 markdown 表格同行）

**退档条件（硬性）**：校准后假阳性率仍 ≥ 10% → **S2 退到 report-only，不上阻塞档**。
宁可只观察，不造一个靠大量豁免维持的假绿闸——那正是
`docs/internal/retrospection/2026-08-05-fault-injection-harness-can-fake-success.md` 复盘的病。

---

## 10. 本轮要改的 Enforcement 列（触发器 K 登记）

这是**改动清单**（落地时照它改 AGENTS.md），不是形态表的镜像——形态真源见 §1 指路。

| 要改哪一条 | 改成 |
|---|---|
| #10 新增一行形态「计划里的具体值未对活源核」 | **L4**（pre-commit 条件触发）**+ L5**（pr-checks），限结构化区域 |
| #10 形态 ①④ 的 Enforcement 备注 | 追加「2026-08-20 已评估：无可靠词法解（实测 71 处引用中 48 处报红、多数假阳性）⇒ 维持 L1」 |
| #10 形态 ②③ | 不动 |
| #9 | 不动（本轮不碰，仍 L1） |

### 已知缺口（显式登记，不假装覆盖）

| 缺口 | 层级 | 依据 |
|---|---|---|
| 散文区域的断言 | S5 report-only | §4 三次实测：非词法特征 |
| 对话里的口头结论（"应该没问题"） | L1 | 机械抓不到，由 #9 事后核对兜底 |
| **文档断言 × 另一份文档的当前版本** | **L1** | §3.1 自撞实证：本闸只核代码活源，规则文档的时效性抓不到 |
| 从此不再改动的文件里的错断言 | L1 | §7.2 存量策略的已知代价，由 `plan-lifecycle` 可达性判据兜底 |

---

## 11. 明确推荐

**推荐：按本设计实施（S2 先按阻塞档做，校准不达标就退 report-only）。**

理由 3 条：

1. **可行性已被数据证明**，不是推测——活源侧原型已自动复现 07-30 人工实测的三条结论。
2. **零存量成本**：只扫改动文件，上闸当天零红，不需要 baseline 镜像文件。
3. **有硬验收**：拿历史真实 bug 当回归测试，抓不到就判失败——不给"看起来合理"留空间。

---

## 12. ⛔ 反对本方案的最强理由（自审 · meta-rules 触发器 F）

**最强反对**：本闸覆盖面窄到可能不值得——它只管「写进 markdown 表格的 prop / 导出名断言」。
而 45 次「误判」里，绝大多数发生在**对话里**（"应该没问题"、"已经一致了"），
或发生在散文里。本闸对那些**完全无效**。

**这个反对是成立的。** 诚实的期望值：本闸能拦住的是 07-30 那一类
（计划文件里成表格的具体值断言），量级大概是每月个位数次，不是 45 次。

**为什么仍推荐做**：
- 那"个位数次"的单次代价很高（07-30 那次 4 条断言 3 条错，下游照抄会连带错）
- 它是 #10 的**第一个**可机检形态被拿下，方法论可复用到后续形态
- 替代方案（写第四份文字规则）已被 `audit-plan-lifecycle.mjs` 的记录判定为无效动作

**有没有把最省事项包装成 #1**：有此风险。更稳健但更贵的选项是
「先花一轮把 45 次误判逐条分类，确认哪一类量最大再动手」——
本设计把它压成了 §4 的三行表格。若 owner 认为该先做那轮全量分类，
**本设计应当搁置**，那是更负责的顺序。

---

## 13. 该谁拍

- **本设计是否实施** → owner 拍（涉及新增阻塞闸，影响每次 commit）
- S2 阻塞档 vs report-only → 可由执行者按 §9 的 10% 阈值机械判定，无需 owner
- 闸 2 / 硬规则 #9 是否进后续轮 → owner 拍
