# Q3「救活迭代账本」落地口径 —— 预注册（⛔ 动手前写，判据在此固化）

> owner 2026-09-07 拍 `revive`（三选一里的「救活，让机器自己写」）。
> 本文件在**写任何实现代码之前**钉死口径与判据。理由与 Q7 那份预注册同：
> 这一条要改的是**新鲜度判定**本身 —— 一旦先实现后写判据，「它现在不报警了」
> 就可以被解释成「治好了」，而真相可能只是「把报警关掉了」。

---

## 0 拍板前的读数改判 —— ⛔ 账本正文声明的口径不能照字面用

owner 选的分支字面是「接进 ds-sweep，每跑一轮就自动追加一行」。照字面动手前先推演
（本机 memory「照交接字面动手前先推演」），撞出一处**文档字面 vs 活体产物冲突**：

账本头部规则逐字：「数字来自 `figma-data/normalized/component-attributes.audit.json` + a11y inspector JSON」。

| 候选数据源 | `summary.checkedAt` | 距 2026-09-07 | 账本实际在用吗 |
|---|---|---:|---|
| `figma-data/normalized/component-attributes.audit.json` | `2026-05-20T05:23:15.780Z` | **110 天** | ❌ **只有 row #1–#11**。row #12–#20 那些数字列**全部是 `—`**，尾列写「manifest path A」 |
| `figma-data/normalized/render-verification.report.json` | `2026-09-04T07:59:02.692Z` | **3 天** | ✅ **就是它** —— row #12–#20 叙述里的 Entry PASS/SKIP/FAIL 与 field A/B 出自这里 |
| `figma-data/normalized/react-render-verification.report.json` | `2026-09-04T08:00:40.157Z` | 3 天 | ✅ 同族（React 侧） |

⇒ **照账本正文救活，会复活一个它自己 4 个月前就弃用了的量**，且数字会永远冻在 05-20
（那份产物要重生成需要 Figma sync）。**⛔ 不这么做。**

⚠️ 这不是新的 owner 决策，是读数把选项收窄了：owner 拍的是「让机器自己写这个信号」，
而「这个信号此刻的活体量具是哪一份」是可查的事实，不是价值取舍
（本机 memory「决策从读数出，不从文档措辞出」）。
**账本正文那句过期的口径声明另登记为 C3**（见 §5）。

### 被埋了 102 天的读数（这条本身就是账本存在的理由）

| 量 | row #20（2026-05-28 人工记） | 活体产物（2026-09-04） |
|---|---:|---:|
| `A_TRUE_DRIFT_CANDIDATE`（真 drift 候选） | 116 | **0** |

⇒ A 类已清零，而**账本断线让这个成果 102 天没有任何人看见**。

⛔ **但 B 列不许跨断点比**：row #20 的 `B 10` 是 **field-level** 计数；
活体 `classifications.B_RESIDUAL_SCHEMA_GAP: 245` 与同份产物里 `excuse.excusedChecks: 245`
**相等** ⇒ 它是 **check-level 且全部被 31 条 excuse 行豁免**的量。**两个不同的量。**
⇒ ⛔ 不许声称「B 从 10 涨到 245」。跨断点 Δ 一律不算（判据见 §2）。

---

## 1 写在哪 —— 受 `ds-sweep` 的 report-only 约束

`metrics/ds-sweep.mjs` 头注释逐字：「**⛔ 它不修任何东西、不提交、不改 DS。**」
⇒ **采集器不得往 DS 仓写账本行。** 两条硬理由：

1. 会**破坏 report-only** —— 那是这个装置的定义，不是偏好。
2. 会**每天在 DS 仓造一个脏文件**。本仓已登记过这个具体后果：
   `.claude/hooks/post-figma-write.sh` 头注释逐字「落仓库会造 dirty 文件, 并行 session
   会把它误判成「别人正在写」而不敢动」。

**落点判据**：

| 写什么 | 谁写 | 落在哪 | 本轮做吗 |
|---|---|---|---|
| **日度测量序列**（每轮一行，保证新鲜度真实） | `ds-sweep`（report-only，只写自己的状态目录） | `~/.claude/ds-metrics/iteration-ledger.jsonl`（**固定路径，⛔ 不绑 STATE_DIR** —— sweep 与 autofix 各跑一次采集器，必须append 进同一条序列） | ✅ **本轮** |
| **账本 markdown 的新一行**（人读的「波次」视图） | `ds-autofix`（A 档，有写权 + 提交权） | DS `docs/internal/audit-iteration-ledger.md` | ⛔ **不在本轮** |

⛔ **如实登记：本轮只落第一半。** 第二半（autofix 往 DS 追 markdown 行 + 提交）不在本轮，
理由是它需要 ≥2 个 JSONL 点才能算出第一个 Δ，而此刻序列里 0 个点。
⚠️ **⛔ 不许在交付物里把它写成「账本已救活」** —— 本轮救活的是**测量**，不是**账本正文**。

### 同一天跑两次怎么办（去重判据）

`ds-sweep`（名义 09:17）与 `ds-autofix`（名义 10:17）各跑一次采集器 ⇒ 一天两次 append。
**判据**：仅当「最后一行的 `measuredAt` 日期 **或** `fingerprint` 与本次不同」才追加。
⇒ 同日同指纹只留一行；同日指纹变了则追第二行（**那是真变化，该留**）。

---

## 2 每行记什么 —— 口径钉死

```json
{
  "kind": "iteration-ledger",
  "measuredAt": "2026-09-07",
  "artifacts": {
    "vue":   { "path": "figma-data/normalized/render-verification.report.json",       "checkedAt": "2026-09-04T07:59:02.692Z" },
    "react": { "path": "figma-data/normalized/react-render-verification.report.json", "checkedAt": "2026-09-04T08:00:40.157Z" }
  },
  "vue":   { "total": 936, "pass": 262, "passByModeSkip": 585, "fail": 89, "passRate": 0.9049, "A": 0, "B": 245 },
  "react": { "total": 930, "pass": 262, "passByModeSkip": 582, "fail": 86, "passRate": 0.9075, "A": 0, "B": 242 },
  "fingerprint": "<两份产物 checkedAt + inputsFingerprint 的 sha1 前 12>",
  "dsHead": "<DS 仓 HEAD 短 hash>"
}
```

**⛔ 刻意不存的字段**：

- **`Δ vs prev` ⛔ 不写进 JSONL** —— Δ 是从相邻两行算出来的，存进去等于造第二份真源，
  且会漂（与 Q8「存在性 ≠ 声明为真」同族）。读的人自己算。
- **`changedFromPrev` ⛔ 不写** —— 同上，指纹相等即可推出。
- **跨断点 Δ ⛔ 不算**：自动行与 row #20 之间不存在可比的 Δ（理由见 §0）。
  ⇒ 第一条自动行的 Δ 是 **`null`（无前值）**，⛔ 不是 0。

---

## 3 新鲜度判定怎么改 —— 这是本条的要害

`audit-iteration-ledger` 这条探针从**一条线**拆成**两条读数**：

| 读数 | 取自 | 进 `needsHuman` 吗 | 为什么 |
|---|---|---|---|
| `lastMeasuredAt` | lab 侧 JSONL 最后一行 | ✅ **进** | 「**没有新的测量**」= 装置真的断了 ⇒ 该叫人。而这条现在由机器自己保证不会发生 |
| `lastWaveAt` | DS 侧 markdown 最后一行的日期 | ⛔ **不进** | 「**没有新的一波改动**」是关于**人的工作节奏**的事实，不是故障 |

🔴 **判据的核心理由**（⛔ 别在后续放宽时绕过它）：
把 `lastWaveAt` 算进「要人看」**正是过去 102 天每天空转的根因** ——
采集器每轮报「度量线断 1 条」，autofix 每天被唤起一次、发现无事可做、退出。
今天那次真跑（`run-2026-09-07T170354`）就是这个形态：`classification: none` · 改动为零。

⚠️ **这个改动会让一个报警消失，所以它必须能被独立falsify** ⇒ §4 的阳性对照里
**必须有一条验「真断的时候它仍然红」**，⛔ 不能只验「现在不红了」。

---

## 4 判据（pass / fail 在此固化，⛔ 跑之前写）

### 4.1 自证（`--self-test`，随采集器每次运行）

| id | 类型 | 判据 |
|---|---|---|
| `MH-L1` | must-hit | 两份产物都在 ⇒ 7 个数（total/pass/passByModeSkip/fail/passRate/A/B）× 2 侧全部抽出且都是有限数 |
| `MH-L2` | must-hit | 最后一行同日期同指纹 ⇒ **不追加**（去重生效） |
| `MH-L3` | must-hit | 最后一行同日期但指纹不同 ⇒ **追加** |
| `MH-L4` | must-hit | 序列为空 ⇒ 第一行的 Δ 前值为 `null` |
| `MN-L1` | must-**not**-hit | 产物缺 `summary.classifications` ⇒ **exit 2**（装置故障），⛔ 不得静默写 `null` 或跳过 |
| `MN-L2` | must-**not**-hit | `lastWaveAt` 断（哪怕 999 天）⇒ ⛔ **不得**进 `needsHuman` |
| `MN-L3` | must-**not**-hit | `lastMeasuredAt` 断 > 30 天 ⇒ **必须**进 `needsHuman`（这条防「把报警关掉当治好」） |

### 4.2 阳性对照（造故障 → 验红并点名 → 还原 → 逐字比对零残留）

| # | 造什么故障 | 要求的红侧读数 |
|---|---|---|
| **P1** | 把 `render-verification.report.json` 的 `summary.classifications` 改名 | **exit 2** 且输出**点名该字段**。⛔ exit 1 不算过（那会把装置故障读成被测对象缺陷） |
| **P2** | 把 JSONL 最后一行的 `measuredAt` 改成 40 天前 | `needsHuman: true` 且逐字印出 `lastMeasuredAt` 断 —— **这条证明报警没被关掉** |
| **P3** | 删掉 `render-verification.report.json` | **exit 2**（产物不在 = 装置前置不成立），⛔ 不是「账本断了」 |

**绿侧要求**（⛔ 绿灯不算证据，本仓通则）：三条各自还原后，
采集器输出与造故障前**逐字一致**（仅 `sweptAt` / `measuredAt` 类时间戳可差），
且 DS 仓 `git status` 回到开工前的脏文件集合（那两个文件），零新残留。

### 4.3 ⛔ 本轮不声称的（如实登记的边界）

1. **DS 侧 markdown 账本本轮不追加任何行** ⇒ 「账本正文救活了」⛔ 不许写。
2. **序列本轮只会有 1 个点** ⇒ 第一个真 Δ 要等下一次指纹变化，
   「趋势」这个词本轮**⛔ 不可用**。
3. **产物本身的新鲜度不由本条保证** —— `render-verification.report.json` 要重生成需跑
   Playwright（`test:render-verification`），那不在采集器的扫描面（头注释逐字
   「⛔ 别往这里加需要 dist/ 或浏览器的闸」）⇒ 若它长期不重生成，
   序列会记下一串同指纹的行，**那是如实的「没人重测」，⛔ 不是趋势**。

---

## 5 派生登记（⛔ 不在本轮改）

**C3 · 账本正文的口径声明已过期 4 个月**：头部规则逐字「数字来自
`component-attributes.audit.json` + a11y inspector JSON」，而 row #12 起实际用的是
`render-verification.report.json`。⇒ 改正文是**措辞决定**（且要判要不要保留旧口径那 11 行的可比性）
⇒ 按队列 §0 属 C3，**登记进 decision-queue，⛔ 本轮不改 DS 正文**。

---

## 6 跑完之后 —— ⛔ 上面的预测一字未改，这里只记结果

### 🔴 P2 当场推翻了我自己的实现（这是本条最重要的一次留痕）

第一版实现把 `stale` 判定接在**序列最后一行的年龄**上（`lastMeasuredAt`）。
跑 §4.2 的 **P2**（把序列最后一行改成 40 天前 ⇒ 要求它红）时立刻发现：

> **序列最后一行是「读它的那一轮」自己写进去的** ⇒ `lastMeasuredAt` 恒等于今天
> ⇒ 这个判定**永远不会红**。

⇒ 我等于把「**每天都响的无信息报警**」换成了「**永远不响的无信息报警**」。
两者都是零信息，与本仓已命名的「恒真分母稀释」/「恒红 = 零信息」同族。
**⛔ 那不是修好，那是把报警关掉。**

**改法**：报警源换成**产物 `checkedAt` 的年龄**（取两侧最旧那个）——
产物 30 天没重生成 ⇒ 序列只是一串同指纹的行 ⇒「没有新的测量」是真的。
`lastMeasuredAt` 保留为**打印读数**，并在输出里逐字标注「仅自证装置跑过，⛔ 不是报警源」。

⚠️ **值得记的是这条判据的来源**：P2 是在**写任何实现之前**钉死的，而第一版实现
**无法通过它**。⇒ 判据是它自己被推翻的原因，**⛔ 不是事后找的理由**。
如果先实现后写判据，「它现在不报警了」会被顺理成章地解释成「治好了」——
§0 开头那句担心不是假想，是当场发生的。

补的自证：`MN-L5 / L5′ / L5″ / L5‴`（取最旧不取最新 · 最旧超阈值必红 ·
两侧都新不红 · 任一侧读不出时间即算断）。

### 判据逐条结果

| # | 判据 | 结果 |
|---|---|---|
| `MH-L1`–`MH-L4″` · `MN-L1`–`MN-L3″` · `MN-L5`–`L5‴` | 自证（随每次运行） | ✅ 全过。**并造故障验了自证真在跑**：把 `Math.max` 改成 `Math.min` ⇒ **恰好** `MN-L5` 转红并自我点名，其余不动；还原后 `cmp` 逐字一致 |
| **P1** 改名 `summary.classifications` | 要求 exit 2 + 点名字段 | ✅ **EXIT=2**，逐字「缺 \`summary.classifications\` —— 产物结构变了 ⇒ 探针不再知道自己在测什么」。⚠️ 附带确认：**序列文件根本没被创建** ⇒ ⛔ 没记下一个假测量 |
| **P2** 产物 `checkedAt` 改成 40 天前 | 要求 needsHuman | ✅ **EXIT=1**，印 `✗ **产物年龄 40 天**（阈值 30）` + 总结行逐字 `· **迭代趋势没有新测量**`。⚠️ **报警确实没被关掉** |
| **P3** 删掉产物 | 要求 exit 2（≠ 账本断线） | ✅ **EXIT=2**，逐字「⛔ 这是装置前置不成立，不是账本断线」 |
| 绿侧 | 还原后逐字一致 + DS 零残留 | ✅ 三次还原均 `cmp EXIT=0`；DS `git status` 全程只剩开工前那两个脏文件 |

### 本轮读数（真实落盘：`~/.claude/ds-metrics/iteration-ledger.jsonl`，1 点）

```
产物年龄 3 天（阈值 30）· 指纹 e12cf92942e0
vue   total 936 · pass 262 · skip 585 · fail 89 · A 0 · B 245 · passRate 90.49%
react total 930 · pass 262 · skip 582 · fail 86 · A 0 · B 242 · passRate 90.75%
Δ = 无前值（⛔ 不是 0）
```

### 空转治掉了 —— 但要说清是怎么治的

改前：采集器每轮 `EXIT=1`（度量线断 1 条）⇒ `ds-autofix` 每天唤起 agent ⇒ 无事可做 ⇒ 退出。
改后：**`EXIT=0` 全清** ⇒ autofix 不会再唤起 agent。

⛔ **但这不是因为「不报了」**：同一轮里 6 条闸、提交探针、另两个账本**全部照常判**，
`audit-iteration-ledger` 的 102 天**照常印**（只是标成 `〔读数·⛔ 不叫人〕`），
且新增的产物年龄那条**已由 P2 证明会红**。⇒ 关掉的只有「人多久没写新一波」这一条，
而那正是 owner 拍的那件事。

### ⛔ 仍然不能说的（如实登记）

1. **DS 侧 markdown 账本本轮一行未追加** ⇒ ⛔ 不许说「账本救活了」。救活的是**测量**。
2. **序列只有 1 个点** ⇒ ⛔「趋势」这个词此刻不可用；第一个真 Δ 要等指纹变化。
3. **`A_TRUE_DRIFT_CANDIDATE` 从 116 → 0 这个成果，本轮只是把它变可见，⛔ 不是本轮做的。**
   它是过去某几轮改动的结果，被账本断线埋了 102 天。
