# 铺 GT-002 / GT-003 到 N=5：区分度实验执行计划

> 用户 2026-08-25 17:1x 授权：**① 铺 GT-002/GT-003 到 N=5**。
> 排除的两条及理由记在 §0.3（⛔ 别下轮重问）。
> ⛔ 这份是**计划 + 口径裁决记录，不是报告**。落地读数进 `reports/2026-08-25-round2-task-discrimination.md`。
>
> ⚠️ **这不是 spec 的「第 12 步」。** spec §14 没有这一步 —— 它是
> [`reports/2026-08-25-round2-step11.md`](../reports/2026-08-25-round2-step11.md) §8 第 6 条那笔挂账的销账动作
> （「GT-002 / GT-003 仍一次没跑过」+「E35 那条只在 GT-001 口径下成立，铺开后必须重核」）。

---

## 0. 目标、预算、与被排除的两条

### 0.1 一句话目标

在同一 pin、同一模型、同一 prompt 模板下，把 GT-002（表单流）/ GT-003（数据可视化）各跑 **N=5**，
**回答一个现有量具结构上答不出的问题**：三条 golden task 是不是**三个不同的被测对象**。

**「结构上答不出」不是修辞** —— 实看过五个量具的入口，全部硬断言单一 taskId：

| 量具 | 逐字断言 |
|---|---|
| `metrics/noise-floor.mjs:40` | `混入了 ${taskIds.length} 个 taskId ⇒ 不是同一个随机变量` |
| `metrics/first-shot.mjs:112` | `混入 ${taskIds.length} 个 taskId ⇒ 不是同一个随机变量` |
| `metrics/convergence-probe.mjs:58` | `混入多个 taskId ⇒ 不是同一个随机变量` |

⇒ 这些断言是**对的**（组内噪声必须同分布），但它们把「跨 task 比较」整个挡在门外。
⇒ **区分度必须新建量具**，且新量具必须**尊重**那条断言：组内噪声仍分 task 算，只在**组间**做比较。

### 0.2 预算与中止条件

| 项 | 值 | 口径 |
|---|---:|---|
| 新生成 run | **10** | GT-002 × 5 + GT-003 × 5 |
| token 点估计 | **92 M** | 9.20 M/run（6′ N=5 实测均值口径）× 10 |
| token 区间 | **44 – 125 M** | 单 run CV 32.7%、极差 2.85 倍（6′ 放量档实测口径） |
| 墙钟 | **1.5 – 1.7 小时** | 单 run 16.4 / 19.4 / 24.7 分钟（headless 实测）· 2 路并发 · 5 批 |
| 新量具 | 1 个 | `metrics/task-discrimination.mjs` |
| 现有量具重跑 | 6 次 | 3 个量具 × 2 条新 task（**纯 T1，零 token**） |

**⛔ 中止条件（硬）**：跑完 GT-002 那 5 个（r007–r011）后**必须**核一次实际 token。
若 5 run 实耗 > **62 M**（= 区间上限 125 M 的一半），**停下来回报用户**，⛔ 不自动续跑 GT-003。

### 0.3 被排除的两条 —— ⛔ 下轮别重问

| 选项 | 排除理由 |
|---|---|
| ② 加大 GT-001 的 N（5→10，46 M） | (a) **现在没有对照组** —— 降地板是为了判「改动前后变好没有」，那要两组跨 DS 改动的 run，现在只有一组，买到的灵敏度当下无处可用；(b) **等于把钱花两次** —— re-pin + 修 N40 的 `-d→-e` 后新旧 run 不可并池（status §14.3），现在花 46 M 加到 N=10，re-pin 后还要再花 92 M 重建，而合并付只要 92 M |
| ③ 加他厂 API key 解锁铁律 1 严口径 | 零 token 且免疫 re-pin，**本身仍是好动作**，但它把进度压在两个外部依赖上（用户拿 key + DS 裁严/宽口径）。而 lab 已在 step11 §6 第 5 条明确写「⛔ 本轮 lab 不替你裁」⇒ 抢先按严口径解锁等于替 DS 预设了裁决方向。**保留为下一站候选** |

### 0.4 ⚠️ 一条要顺带订正的既有读数（`lab:E40`）

`docs/round2-status.md` §13.2 那张表逐字写着：

> | 加大 GT-001 的 N | **统计力** ✅ | 过不去**分辨率地板** |

**这句是把规则口径的结论写成了通用结论。** 实看 step11 报告 §2.2 表自己：

| 口径 | 分辨率地板 | 噪声地板(N=5) | 谁主导 |
|---|---:|---:|---|
| ②规则口径 | **0.20** | 0.1584 | **分辨率** ⇒ 加 N 无用，原句成立 |
| ④`unmatchedDistinct`（**已裁定的主验收口径**） | **1** | **3.4082** | **噪声** ⇒ 加 N 有用 |

外推（sd=1.9235 固定，系数 `(z_{α/2}+z_β)·√(2/n)`，与 step11 §2.4 同一公式）：

| N | 5（现状） | 10 | 20 | **58** |
|---|---:|---:|---:|---:|
| 噪声地板 | 3.408 | 2.409 | 1.703 | **1.00 ← 才撞到分辨率地板** |
| MDE/mean | 21% | 13% | 9.4% | 5.5% |

⇒ **`lab:E40`：主验收口径下，N 加到 58 之前加 N 一直在买真东西。**
⇒ 与 `lab:E39` 同族（两个数长得像同一个量时最该标口径名），这次踩的是 lab 自己。
⇒ ⛔ **不推翻另外两半**：拆细分母仍是死路（`lab:E36`）、铺新 task 仍不买统计力（本计划自己）。

---

## 1. 全局硬约束（每个阶段都适用，⛔ 不重复声明）

从 `AGENTS.md` 与 `docs/round2-status.md` 逐条抄来，**值照抄，不转述**：

1. **每次用 golden set 前必跑冻结校验**：
   `node metrics/golden-set-freeze.mjs --pin ~/.ai-ds-lab/pins/tvu-ds-4a68e02d`
2. **⛔ 三个 pin worktree 一个都别拆**（`71ac2711` / `339a72bf` / `4a68e02d`）。造故障一律在 scratchpad 假 pin 上。
3. **run 前后核 pin 的 `git status`，非空即判该 run 作废并抛**（`run-golden-task.mjs` 边界 ⑤ 已内建，⛔ 不绕过）。
4. **§5.2 三元组主键 `(subjectSha, modelId, promptTemplateVersion)` 缺则作废**，本批全部 =
   `(4a68e02d3d695cee3c39d255ea5d20f7f2bf9b8c, claude-opus-5, pt-v1)`。
5. **凡引用分母必带口径名**（AGENTS §2.4）。**跨仓引编号带仓前缀**（`lab:E35` / `ds-adr:E9`，推论二）。
6. **比例型指标两个地板并排报**，有效地板 = `max(分辨率地板, 噪声地板)`（AGENTS §2.9）。
7. **二值 / 低基数口径走精确检验，⛔ 不走 2σ**；「落在判定边界」必须显式写（AGENTS §2.10）。
8. **fail closed**：输入面塌了要抛，⛔ 不静默降级成 0 命中（AGENTS §3.1）。
9. **每条判据配 must-hit + must-not-hit 并内建进脚本**；控制钉**具名事实**，⛔ 不钉「解析器跑通没跑通」（AGENTS §3.2）。
10. **纯 Node、零依赖、不依赖任何 AI harness 特有能力**（AGENTS §5 / D4）。
11. **⛔ 不外包 subagent。** 跑 run 的通道等价性已判死（status §12.3：`pwd` 不是 pin、`cd` 不跨工具调用持久、
    `CLAUDE_PROJECT_DIR` 空、Agent 工具无 cwd 参数）；量具开发受 AGENTS §4「不接受文字总结」约束。
12. **⛔ 不主动做 `injectionEra` 分档**（status §14.3 已裁：下一次 re-pin 的同一批动作里一起做）。
    **⛔ 更不选「换桩」**（撤哨兵）。

### 1.1 run id 分配（⛔ r006 保留，别占用）

| runId | task | repeatIndex | 通道 |
|---|---|---:|---|
| r001–r005 | GT-001 | 1–5 | headless（**已有**，6′ 产出） |
| **r006** | GT-001 | 6 | **manual —— ⛔ 保留给用户人工跑，本批不占用** |
| **r007–r011** | **GT-002** | 1–5 | headless（本批新建） |
| **r012–r016** | **GT-003** | 1–5 | headless（本批新建） |

**作废纪律**：某个 run fail closed（无 ```html 围栏 ⇒ `run-golden-task.mjs` exit 1）时：
① 把目录改名 `runs/rXXX-void/`；② 在目录里写 `VOID.md` 记作废原因与原始 exit code；
③ **重跑用下一个未用 id**，⛔ 不复用作废 id、⛔ 不把 `repeatIndex` 也顺延（repeatIndex 记的是「这条 task 的第几次」）。
④ **作废本身是读数** —— 报告里要如实报「N=5 是第几次尝试凑齐的」。

---

## 2. 预注册预测（⚠️ 跑之前冻结，跑完逐条对账）

**这是本轮方法学上的真正增量。** 两条新 task 的 JSON 里**早就写着**对区分度的预测
（`graders[].expectedDiscrimination` 字段），而那些 JSON 已冻结在 pin `4a68e02d` 上、
`golden-set-freeze.mjs` 每次跑都校验其完好。⇒ **这些预测是跑之前就存在的，不是事后解释。**

| # | 预测（**形式化**） | 逐字出处（task JSON） |
|---|---|---|
| **P1** | GT-002 上 `popupbox-for-confirm` 的命中 run 数 = **0** | `gt-002…json` → `graders[0].expectedDiscrimination`：「不含确认弹窗 ⇒ **popupbox-for-confirm 在本条上应恒不命中**，可作为跨任务的 must-not-hit 对照」 |
| **P2** | GT-002 上 `unconstrained-svg` **或** `hardcoded-color` 的命中 run 数 > 0 | 同上：「表单面主要打 unconstrained-svg 与 hardcoded-color」 |
| **P3** | GT-003 上 `unconstrained-svg` + `hardcoded-color` 的命中 run 数 **≥ GT-001 与 GT-002 各自的同一量** | `gt-003…json` → `graders[0].expectedDiscrimination`：「⇒ 这条任务对这两条规则的曝光最强」 |
| **P4** | GT-003 的 `escape-rate.unmatchedDistinct` 均值 **> GT-001 与 GT-002 各自的均值** | `gt-003…json` → `graders[1].expectedDiscrimination`：「**本条是三条里 escape-rate 的主力样本**…`sameValueToken` 为空的那类（= 真源缺的洞）分子最足」 |

⚠️ **诚实标注（要写进量具产物与报告）**：
- `expectedDiscrimination` 是**自然语言**，P1–P4 是 **lab 对它的形式化**，⛔ **不是 task 文件的原话**。
- ⇒ 量具必须同时输出 `sourceQuote`（逐字取自 task JSON）与 `formalizedBy: "lab"`，让读者能自己判形式化对不对。
- ⇒ **预测 miss 不是失败** —— miss 本身是读数，且比 hit 信息量更大。⛔ 量具不因 miss 而 exit 1。

---

## 3. 文件结构（本计划要碰的全部文件）

| 文件 | 动作 | 职责 |
|---|---|---|
| `runs/r007…r016/` | **新建**（量具产出） | 10 个 run 的三件套 + `context-manifest.json` |
| `metrics/task-discrimination.mjs` | **新建** | 跨 task 区分度：组间差 vs 组内噪声 + 规则曝光矩阵 + 预注册对账 |
| `subjects/tvu-ds/inventory/noise-floor-gt002-n5-4a68e02d.json` | 新建（量具产出） | GT-002 的 MDE |
| `subjects/tvu-ds/inventory/noise-floor-gt003-n5-4a68e02d.json` | 新建（量具产出） | GT-003 的 MDE |
| `subjects/tvu-ds/inventory/first-shot-gt002-n5-4a68e02d.json` | 新建（量具产出） | GT-002 双地板表 |
| `subjects/tvu-ds/inventory/first-shot-gt003-n5-4a68e02d.json` | 新建（量具产出） | GT-003 双地板表 |
| `subjects/tvu-ds/inventory/convergence-probe-gt002-n5-4a68e02d.json` | 新建（量具产出） | GT-002 组件面散度 |
| `subjects/tvu-ds/inventory/convergence-probe-gt003-n5-4a68e02d.json` | 新建（量具产出） | GT-003 组件面散度 |
| `subjects/tvu-ds/inventory/task-discrimination-v1-4a68e02d.json` | 新建（量具产出） | 区分度矩阵 + E35 重核 + 预注册对账 |
| `reports/2026-08-25-round2-task-discrimination.md` | **新建** | 报告 |
| `docs/round2-status.md` | 修改 | §13.2 订正（`lab:E40`）+ 新增 §15 |
| `AGENTS.md` | 修改 | §2 加一条（`lab:E40` 的操作口径） |
| `runs/README.md` | 修改 | run id 分配表补 r007–r016 |

---

## 4. 阶段 A：跑 10 个 run

### A.1 起手校验

- [ ] **步骤 A1：跑冻结校验**

```bash
node metrics/golden-set-freeze.mjs --pin ~/.ai-ds-lab/pins/tvu-ds-4a68e02d
```

期望：`✅ v1 冻结完好：3 条 · 引文全部可定位 · 控制全过`，`exit 0`，**6 条引文逐字命中**。
⛔ 任何一条不命中就停手 —— 那意味着 pin 被动过或 task 文件被改过。

- [ ] **步骤 A2：核三个 pin 都干净**

```bash
for p in 71ac2711 339a72bf 4a68e02d; do
  echo -n "$p: "; git -C ~/.ai-ds-lab/pins/tvu-ds-$p status --porcelain | wc -l
done
```

期望：三个都是 `0`。⛔ 非 0 就停手，先查是谁写的。

- [ ] **步骤 A3：dry-run 验两条新 task 的命令面**

```bash
node adapters/run-golden-task.mjs --task tasks/tvu-ds/v1/gt-002-ndi-source-type-form.json \
  --pin ~/.ai-ds-lab/pins/tvu-ds-4a68e02d --channel headless \
  --run-id r007 --model claude-opus-5 --repeat-index 1 --dry-run
node adapters/run-golden-task.mjs --task tasks/tvu-ds/v1/gt-003-cost-per-token-trend.json \
  --pin ~/.ai-ds-lab/pins/tvu-ds-4a68e02d --channel headless \
  --run-id r012 --model claude-opus-5 --repeat-index 1 --dry-run
```

期望：各打印 `cwd=` / CLI 命令行 / `cliVersion=` / `prompt 字节=` 与 `promptHash=` 前 16 位，`exit 0`。
**把两个 promptHash 记下来** —— 它们必须**互不相同**、且都**不等于** GT-001 的
`559c6decc5b4cf58d12e2062aed55810d67367ecca16a9761b019c4275813d14`（前 16 位 `559c6decc5b4cf58`）。
⛔ 相同就说明 task 文件串了，停手。

### A.2 跑 GT-002 五个（r007–r011）

- [ ] **步骤 A4：2 路并发跑 r007 + r008**

```bash
node adapters/run-golden-task.mjs --task tasks/tvu-ds/v1/gt-002-ndi-source-type-form.json \
  --pin ~/.ai-ds-lab/pins/tvu-ds-4a68e02d --channel headless \
  --run-id r007 --model claude-opus-5 --repeat-index 1 &
node adapters/run-golden-task.mjs --task tasks/tvu-ds/v1/gt-002-ndi-source-type-form.json \
  --pin ~/.ai-ds-lab/pins/tvu-ds-4a68e02d --channel headless \
  --run-id r008 --model claude-opus-5 --repeat-index 2 &
wait
```

期望：各打印 `═══ run r00X · GT-002 · headless ═══` + `usage` 行 + 产物行，`exit 0`。
⚠️ **2 路并发对 pin 是安全的**（`Write/Edit/Bash` 全禁用 ⇒ 结构上只读），6′ 放量档已实测过同样形态。

- [ ] **步骤 A5：核 pin 仍干净，并采两个 run 的 contextManifest**

```bash
git -C ~/.ai-ds-lab/pins/tvu-ds-4a68e02d status --porcelain | wc -l   # 期望 0
for r in r007 r008; do
  node metrics/context-manifest.mjs --cwd ~/.ai-ds-lab/pins/tvu-ds-4a68e02d \
    --harness-type headless-cli --harness-version "$(node -e "console.log(JSON.parse(require('fs').readFileSync('runs/$r/run.json','utf8')).cli.version)")" \
    --transcript runs/$r/transcript.jsonl --run-id $r --out runs/$r/context-manifest.json
done
```

期望：各打印 L1/L2 分层摘要，`exit 0`。
**把两个 `L1Fingerprint` 记下来** —— 应与 headless 侧既有值 `87517baf72da418f` **逐字相同**
（同 pin、同 hook 注入面 ⇒ L1 是确定性层）。⛔ 不同就停手查注入面是不是被动了。

- [ ] **步骤 A6：同样跑 r009 + r010，再跑 r011**

重复 A4/A5 的形态，`--repeat-index` 依次 3 / 4 / 5。r011 单跑（奇数尾）。

- [ ] **步骤 A7：⛔ 中止检查点 —— 核 GT-002 五个 run 的实际成本**

```bash
node -e '
const fs=require("fs");let tot=0;
for(const r of ["r007","r008","r009","r010","r011"]){
  const j=JSON.parse(fs.readFileSync(`runs/${r}/run.json`,"utf8")),u=j.usage||{};
  const t=(u.input_tokens||0)+(u.output_tokens||0)+(u.cache_read_input_tokens||0)+(u.cache_creation_input_tokens||0);
  tot+=t;console.log(`${r}: ${(t/1e6).toFixed(2)} M · wall ${(j.wallMs/6e4).toFixed(1)} min · exit ${j.exitCode} · ${j.output.extraction}`);
}
console.log(`GT-002 小计: ${(tot/1e6).toFixed(1)} M ⇒ 外推 10 run = ${(tot*2/1e6).toFixed(1)} M`);
console.log(tot/1e6>62?"⛔ 超中止阈值 62 M —— 停下来回报用户":"✅ 在预算内，可续跑 GT-003");'
```

**⛔ 打印「超中止阈值」就停手回报用户，不自动续跑 GT-003。**

### A.3 跑 GT-003 五个（r012–r016）

- [ ] **步骤 A8：重复 A4–A6 的形态，task 换 `gt-003-cost-per-token-trend.json`，id r012–r016，repeatIndex 1–5**

- [ ] **步骤 A9：全批收尾核验**

```bash
git -C ~/.ai-ds-lab/pins/tvu-ds-4a68e02d status --porcelain | wc -l   # 期望 0
ls -d runs/r0*/ | wc -l                                               # 期望 15（r001-r005 + r007-r016）
node -e '
const fs=require("fs");const ids=[...Array(10)].map((_,i)=>"r"+String(i+7).padStart(3,"0"));
for(const r of ids){const j=JSON.parse(fs.readFileSync(`runs/${r}/run.json`,"utf8"));
console.log(`${r} ${j.taskId} rep=${j.repeatIndex} exit=${j.exitCode} fence=${j.output.fenceCount} bytes=${j.output.htmlBytes} sha=${(j.output.htmlSha256||"").slice(0,8)}`);}'
```

期望：15 个 run 目录、10 条新记录、全部 `exit=0` 且 `htmlBytes > 0`。
⛔ 任何一条 `fenceCount=0` ⇒ 按 §1.1 作废纪律处理并补跑。

- [ ] **步骤 A10：提交 run 产物**

```bash
git add runs/ && git commit -m "feat(lab): 铺 GT-002/GT-003 到 N=5 —— 10 个 headless run 落地

- GT-002 (表单流) r007-r011 · GT-003 (数据可视化) r012-r016
- 三元组主键全批 = (4a68e02d, claude-opus-5, pt-v1)
- pin 起止 git status 均为空

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>"
```

---

## 5. 阶段 B：现有量具在两条新 task 上各跑一遍（纯 T1，零 token）

⚠️ **顺序重要**：`noise-floor` 必须先跑 —— `first-shot` 的入口逐字写着
`--noise-floor 必给 —— 本器的噪声地板要与 6′ 产物对账，⛔ 不自己另立一套`。

### B.0 ⚠️ 预先侦察（run 跑的同时做的）：**两个量具的控制钉死在 GT-001 上，在新 task 上会炸**

这是本计划初稿漏掉的障碍。逐条实看三个量具的控制清单后：

| 量具 | 控制性质 | 新 task 上的预期 |
|---|---|---|
| `noise-floor.mjs` | 8 条**全部结构性**（三元组唯一 / 单 taskId / `files===1` / fixture 自证 / `htmlBytes≥2000` / 语料 1–3 份 / N<5 全 null / 单通道） | ✅ **预期能过** —— 没有一条钉 GT-001 的读数 |
| `first-shot.mjs` | **MH2 / MH3 钉死 GT-001 的具名读数** | ⛔ **预期必炸**（除非天降巧合） |
| `convergence-probe.mjs` | **MH3 / MN6 有风险** | ⚠️ **可能炸** |

逐字摘录会炸的那三条：

- `first-shot.mjs:370` **MH2**：`exitCode 分布必须是 4 个 1 + 1 个 0（6′ 实测具名读数）`
- `first-shot.mjs:372` **MH3**：`命中规则集必须 ≡ {popupbox-for-confirm} 且逐 run error = [1,1,4,0,2]`
- `convergence-probe.mjs:240` **MN6**：`Button 宽口径全中 ∧ 严口径全无`
  （GT-002 表单流 / GT-003 数据可视化未必用 `Button`）
- `convergence-probe.mjs:205` **MH3**：`faces.every(f => f.wide.length > 0)`
  （GT-003 撞 DS 自陈的 chart primitives 缺位 ⇒ 有 run 一个组件都不用的可能）

**⇒ 这不是量具的 bug，是 AGENTS §3.2 推论二的正面兑现**：
控制钉的是**具名事实**（而不是「解析器跑通没跑通」），所以被测对象一换代**就该炸**。
⇒ 而且这本身是「**扩扫描面是形态发现手段**」的又一次实证（第四次）——
铺新 task 立刻炸出了「量具的控制钉死在第一个被测对象上」这个此前不可见的形态。

**⇒ 处置纪律（⛔ 三条硬的）**：

1. **⛔ 不预先绕开。** 先照原样跑，**让它炸给我看** —— 那是控制走真代码路径的证明（§3.2 推论三）。
   把「哪条炸了、实测值是多少」如实记进报告。
2. **⛔ 不放松控制。** 按 §3.2 **推论四**加 **`taskEra` 分档**（与 `chainEra` / `contractEra` 同构）：
   建一张 `TASK_FACTS` 具名事实表，**每档都断言**，⛔ 不是在新形态上把断言删掉。
3. **新 task 首跑没有具名事实可断言** —— 这时 **⛔ 不许静默跳过**（那是 fail open）。
   处置：该档控制标 `baselineEstablishing`，产物顶层写
   `baselineEstablishing: true` + `⛔ 本次为建档 run，读数不得用于改善判定`，
   并把实测值打印出来供写进表。**下一次跑就必须断言它。**

- [ ] **步骤 B1：GT-002 的 noise-floor**

```bash
node metrics/noise-floor.mjs --pin ~/.ai-ds-lab/pins/tvu-ds-4a68e02d \
  --runs runs/r007 runs/r008 runs/r009 runs/r010 runs/r011 \
  --out subjects/tvu-ds/inventory/noise-floor-gt002-n5-4a68e02d.json
```

期望：`exit 0`，8 条内建控制全过，输出 MDE 表（N=5 ⇒ 不触发 `N<5 拒绝输出 MDE`）。

- [ ] **步骤 B2：GT-003 的 noise-floor**（同上，runs 换 r012–r016，out 换 `-gt003-`）

- [ ] **步骤 B3：GT-002 / GT-003 的 first-shot**

```bash
node metrics/first-shot.mjs --pin ~/.ai-ds-lab/pins/tvu-ds-4a68e02d \
  --runs runs/r007 runs/r008 runs/r009 runs/r010 runs/r011 \
  --noise-floor subjects/tvu-ds/inventory/noise-floor-gt002-n5-4a68e02d.json \
  --out subjects/tvu-ds/inventory/first-shot-gt002-n5-4a68e02d.json
```

期望：14 条控制全过。⚠️ **MH1 会重新解析规则面并断言 5 条** —— 若炸，先查是不是又踩了
「规则 id 正则收窄成纯小写」那个坑（`alert-confirmText` 有大写 C，step11 §2.6 的自证）。
GT-003 同形态。

- [ ] **步骤 B4：GT-002 / GT-003 的 convergence-probe**

```bash
node metrics/convergence-probe.mjs --pin ~/.ai-ds-lab/pins/tvu-ds-4a68e02d \
  --runs runs/r007 runs/r008 runs/r009 runs/r010 runs/r011 \
  --out subjects/tvu-ds/inventory/convergence-probe-gt002-n5-4a68e02d.json
```

期望：11 条控制全过。⚠️ **MN6 钉的是「`Button` 宽口径全中 ∧ 严口径全无」这个具名事实** ——
它是在 GT-001 产物上实证的。**若在新 task 上炸，⛔ 不要放松控制**，
先判是「Button 根本没被用」还是「签名抽取变了」，把判定写进报告。
GT-003 同形态。

- [ ] **步骤 B5：提交**

```bash
git add subjects/tvu-ds/inventory/ && git commit -m "feat(lab): GT-002/GT-003 的 noise-floor / first-shot / convergence 读数

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>"
```

---

## 6. 阶段 C：新建 `metrics/task-discrimination.mjs`

### C.1 它要回答什么 —— 以及**不**回答什么

| 回答 | ⛔ 不回答 |
|---|---|
| 三条 task 在每个 T1 指标上**是不是可区分**（组间差 vs 组内噪声） | 哪条 task「更好」——区分度不是质量 |
| 5 条 HTML 规则的**跨 task 曝光矩阵** ⇒ `lab:E35` 的边界重核 | 那 4 条规则是不是「死规则」（15 run 仍是有限样本） |
| 4 条**预注册预测**的逐条对账 | 预测 miss 时的原因（miss 是读数，归因要另做） |
| 三条 task 各自的**有效地板**（双地板并排） | 统计力（铺 task 买不到，本计划自己的结论） |

### C.2 判据设计

**输入面**：15 个 run 目录（3 task × 5）+ pin 上的 HTML grader + 三个 task JSON。

**核心判据 —— 两条 task 在指标 m 上「可区分」**：

按指标类型分档，⛔ 不一刀切（AGENTS §2.10）：

| 指标类型 | 指标 | 主检验 | 并排报 |
|---|---|---|---|
| 连续 | `output.htmlBytes`、`escape-rate.escapeRate` | **Welch t 检验**（不假设等方差） | `abs(Δmean) / MDE_pooled` |
| 计数·低基数 | `ds-conformance.errors`、`escape-rate.unmatchedDistinct` | **Mann-Whitney U 精确检验**（n=5 vs 5 ⇒ 枚举 C(10,5)=252 种，无近似） | `abs(Δmean) / MDE_pooled` |
| 二值 | 闸 `exitCode` pass/fail | **Fisher 精确检验** | ⛔ **不报任何 sd 派生量** |

`MDE_pooled` 用**与 `first-shot` 同一个公式**，⛔ 不另立一套：

```
sd_pooled = sqrt((sd_A² + sd_B²) / 2)                    // n_A = n_B = 5
MDE_pooled = (z_{α/2} + z_β) · sd_pooled · sqrt(2/n)      // = 2.8016 · sd_pooled · 0.63246 = 1.7719 · sd_pooled
```

⚠️ **语义标注（要写进产物 `note`）**：MDE 是**功效分析**的量，不是检验统计量。
拿它当阈值是**保守**的（要求效应达到 80% power 的可检测水平）。
⇒ 产物里 `discriminable` 以**主检验的 p** 为准，`mdeRatio` 只作与 first-shot 可比的并排读数。
⛔ 别把 `mdeRatio > 1` 当成「显著」。

**规则曝光矩阵（`lab:E35` 重核）**：

对每条规则 `r` × 每条 task `t`，统计 `hitRuns(r,t)` = 该 task 的 5 个 run 里命中过 r 的 run 数。
输出 3×5 矩阵。判据：

- 规则 r **在全部 15 个 run 里 0 命中** ⇒ 记 `neverExposed: true`
- ⇒ `lab:E35` 的「有效分母」在 **15 run · 三 archetype 口径**下 = `5 − |{r : neverExposed}|`

⚠️ **边界（AGENTS §2.7，要逐字写进产物）**：15 run / 3 archetype 仍是有限样本。
⛔ `neverExposed` **不得**读作「死规则」，只能读作「本 set、本 N 下未曾曝光」。

### C.3 内建控制（must-hit / must-not-hit）

| # | 类型 | 断言 |
|---|---|---|
| **MH1** | must-hit | 规则面解析出**恰好 5 条**，且 id 集合逐字 = `{popupbox-for-confirm, unconstrained-svg, handrolled-nav, hardcoded-color, alert-confirmText}`（钉**具名事实**，⛔ 不钉「解析器跑通」） |
| **MH2** | must-hit | 恰好 **3 个 taskId**，每个恰好 **5 个 run**（⛔ 不允许 4 个就往下算） |
| **MH3** | must-hit | 三个 `promptHash` **互不相同**（否则不是三条不同 task） |
| **MH4** | must-hit | §5.2 三元组在 15 个 run 上**唯一**（同 subjectSha / model / pt-v1）—— 否则区分度里混进了别的变量 |
| **MH5** | must-hit（**双向夹逼**） | 本器独立重算的 GT-001 五个 run 的 `ds-conformance.errors`，必须与 `noise-floor-gt001-n5-4a68e02d.json` 的 `perRun` **逐 run 对上**（不一致 0 条） |
| **MH6** | must-hit | 4 条预注册预测的 `sourceQuote` **逐字命中**对应 task JSON 的 `expectedDiscrimination` 字段（预测被改了当场炸） |
| **MH7** | must-hit | GT-001 的规则曝光行必须复现 step11 §2.3.1 的具名事实：`popupbox-for-confirm` 命中 **4** 个 run，其余 4 条命中 **0** |
| **MN1** | must-not-hit | 二值口径（闸 pass/fail）的产物里**不得出现任何 sd 派生字段**（`sd` / `mde` / `noiseFloor`）——同 `first-shot` 的 MN3 |
| **MN2** | must-not-hit | 任一 task 的 run 数 < 5 时，**全部区分度字段必须为 null**（⛔ 不允许「4 个也算一下」的 fail open —— step11 §2.5.1 那个坑） |
| **MN3** | must-not-hit | 产物里**不得**出现把三条 task 的 15 个 run pooled 成单一噪声地板的字段（组内噪声必须分 task 算，尊重三个既有量具的断言） |
| **MN4** | must-not-hit | 本器**不得读任何 `context-manifest.json`**（区分度的输入面是**产物**，不是上下文面 —— `lab:E32`） |
| **MN5** | must-not-hit | 预测 miss **不得**导致 `exit 1`（miss 是读数不是失败） |

### C.4 造故障（⛔ 全部在 scratchpad 假 pin 上，三个真 pin 一个不拆）

| # | 造什么 | 期望拦它的 |
|---|---|---|
| **F-A** | 把某条 task 的 run 从 5 个减到 4 个 | **MH2 + MN2**（且全部区分度字段 null） |
| **F-B** | 把 GT-003 的 5 个 run 的 taskId 改成 GT-002（伪造成 10 个 GT-002） | **MH2 + MH3** |
| **F-C** | 规则表少一条（5→4） | **MH1** |
| **F-D** | 改 task JSON 的 `expectedDiscrimination` 文字 | **MH6** |
| **F-E** | 闸退化成**恒绿**（`lab:N33` 假绿同族） | **MH5 + MH7**（GT-001 的具名事实对不上） |

### C.5 步骤

- [ ] **步骤 C1：先写控制，跑一次看它炸**

先只实现输入面 + MH1–MH4 + MN2–MN4，跑：

```bash
node metrics/task-discrimination.mjs --pin ~/.ai-ds-lab/pins/tvu-ds-4a68e02d \
  --runs runs/r001 runs/r002 runs/r003 runs/r004 runs/r005 \
  --tasks tasks/tvu-ds/v1/gt-001-live-source-console.json
```

期望：**MH2 炸**（只有 1 个 taskId，要 3 个）。⇒ 证明控制走的是真路径（AGENTS §3.2 推论三）。

- [ ] **步骤 C2：补全统计与曝光矩阵，跑全量**

```bash
node metrics/task-discrimination.mjs --pin ~/.ai-ds-lab/pins/tvu-ds-4a68e02d \
  --runs runs/r001 runs/r002 runs/r003 runs/r004 runs/r005 \
         runs/r007 runs/r008 runs/r009 runs/r010 runs/r011 \
         runs/r012 runs/r013 runs/r014 runs/r015 runs/r016 \
  --tasks tasks/tvu-ds/v1/gt-001-live-source-console.json \
          tasks/tvu-ds/v1/gt-002-ndi-source-type-form.json \
          tasks/tvu-ds/v1/gt-003-cost-per-token-trend.json \
  --noise-floors subjects/tvu-ds/inventory/noise-floor-gt001-n5-4a68e02d.json \
                 subjects/tvu-ds/inventory/noise-floor-gt002-n5-4a68e02d.json \
                 subjects/tvu-ds/inventory/noise-floor-gt003-n5-4a68e02d.json \
  --out subjects/tvu-ds/inventory/task-discrimination-v1-4a68e02d.json
```

期望：全部控制通过，输出区分度矩阵 + 曝光矩阵 + 预注册对账表。

- [ ] **步骤 C3：手算独立复核两个精确检验的 p 值**

同 step11 §2.3.3 的做法：Mann-Whitney U 在 n=5 vs 5 时的精确分布可枚举（252 种），
挑**一格**手算与量具输出逐位对账，把手算过程写进报告。⛔ 不「量具说是就是」。

- [ ] **步骤 C4：跑 5 次造故障（F-A…F-E）**

按 §C.4 表逐条造，**全部在 scratchpad 假 pin / 假 run 目录上**。每次记「实际拦住它的是哪条控制」。
⚠️ 造完逐个核三个真 pin `git status` 为空。

- [ ] **步骤 C5：提交量具**

```bash
git add metrics/task-discrimination.mjs subjects/tvu-ds/inventory/task-discrimination-v1-4a68e02d.json
git commit -m "feat(lab): 新建 task-discrimination 量具 —— 跨 task 区分度 + E35 曝光矩阵 + 预注册对账

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>"
```

---

## 7. 阶段 D：报告与文档订正

- [ ] **步骤 D1：写 `reports/2026-08-25-round2-task-discrimination.md`**

必须含的节（⛔ 一节都不能省）：

1. **§0 一句话** + 三元组主键 + pin 起止 `git status` 为空的登记
2. **§1 授权、花费、被排除的两条**（成本要报**实测**，并与 92 M 点估计 / 44–125 M 区间对账）
3. **§2 区分度矩阵** —— 每个指标 × 每对 task，主检验 p + `mdeRatio` 并排
4. **§3 `lab:E35` 重核** —— 3×5 曝光矩阵 + 有效分母在 15 run 口径下是多少 + **边界逐字写**
5. **§4 预注册对账** —— P1–P4 逐条 hit/miss，**miss 要展开写**（信息量比 hit 大）
6. **§5 三条 task 各自的双地板表**（与 GT-001 并排）
7. **§6 `lab:E40`** —— §0.4 那条订正的完整论证
8. **§7 新增勘误**（编号从 `lab:E41` 起，⛔ 带仓前缀）
9. **§8 仍未覆盖**（如实登记，⛔ 不算已验）

- [ ] **步骤 D2：订正 `docs/round2-status.md`**

- §13.2 那张表的「加大 GT-001 的 N | 统计力 ✅ | 过不去分辨率地板」一格，
  改成带口径名的两行（规则口径 / 主验收口径），并挂 `lab:E40`。
- §13.7 第 6 条（「GT-002 / GT-003 仍一次没跑过」）**销账**，改成指向本轮报告。
- 新增 **§15**：本轮交付摘要 + 下一站候选（②③ 仍在盘上）。

- [ ] **步骤 D3：`AGENTS.md` §2 加一条**

内容：**「地板谁主导」是口径级事实，⛔ 不得跨口径外推。**
实证 `lab:E40`：同一份报告的同一张表里，规则口径是分辨率主导（加 N 无用）、
主验收口径是噪声主导（加 N 到 58 之前都有用），而 status 把前者写成了通用结论。
⇒ 操作口径：报双地板时，**每个口径都要显式标注「谁主导」**，
并在写「加大 N 有没有用」这类跨口径判断前，先问「我说的是哪个口径」。

- [ ] **步骤 D4：`runs/README.md` 补 run id 分配表**（§1.1 那张，含 r006 保留说明）

- [ ] **步骤 D5：提交并 push**

```bash
git add -A && git commit -m "docs(lab): 区分度实验报告 + E35 边界重核 + lab:E40 订正 status §13.2

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>"
git push
```

---

## 8. 边界 —— ⛔ 这些是边界不是 TODO

1. **铺 task ⛔ 买不到统计力。** 不同 task 是不同被测对象，不是重复观测。
   本轮买到的是**区分度**，⛔ 任何「N 变成 15 了所以更准」的读法都是错的。
2. **`lab:E35` 的重核仍是有限样本。** 15 run / 3 archetype 下 `neverExposed` 的规则，
   ⛔ 不得读作「死规则」。
3. **保真度缺口两处原样保留**：run 不能写文件/自查、无 Figma MCP ⇒ 测的是
   「不能自查时的一次成型质量」。
4. **区分度不是质量。** 三条 task 可区分 ≠ 哪条更好。
5. **本轮不动 `actionability` / 铁律 1 / 铁律 2** —— 仍卡第三家厂商（`lab:E38`）。
6. **本轮不做 `injectionEra` 分档、不换桩** —— status §14.3 已裁「下一次 re-pin 的同一批动作里一起做」。
7. **manual r006 仍待用户人工跑** —— 本批 ⛔ 不占用 r006。
8. **GT-002 的引文出处已在 DS 主工作树消失**（`483e3feb`），pin 上还在 ⇒ 本轮 9/9 通过；
   **re-pin 时必须换出处**（`lab:N37` 的到期日）。本轮 ⛔ 不提前换。
