# re-pin 三件事一次付清 —— 计划与口径裁决

> **执行入口**：本文件是 `docs/round2-status.md` §15.6 那三条的落地计划。
> **前置报告**：[`reports/2026-08-25-round2-task-discrimination.md`](../reports/2026-08-25-round2-task-discrimination.md)
> **基线主键（§5.2 三元组）**：`(4a68e02d…, claude-opus-5, pt-v1)` —— ⛔ **本轮不动 `pinnedSha`**，理由见 §0.2

---

## 0. 授权与范围

### 0.1 用户 2026-08-26 10:5x 拍板

在四条互斥的下一站里选了 **③ re-pin 三件事一次付清**（其余三条及排除理由见 §0.4，⛔ 下轮别重问）。

### 0.2 ⚠️ 开工后实测推翻了「这三件事需要 re-pin」这个前提

**推荐 ③ 时的假设**：三件事都要等一个新 pin。**实测三条全错**：

| 事项 | 假设 | 实测 |
|---|---|---|
| GT-002 换引文出处 | 要新 pin 才能验新出处 | **替代出处 `phase0-ledger.md` 在旧 pin `4a68e02d` 上就逐字存在** ⇒ 旧 pin 上即可完成并校验 |
| GT-003 重写立论 | 要新 pin | `lab:E41` 本来就是在**旧 pin** 上证实的（`src/components/Chart/` 在 `4a68e02d` 上存在）⇒ 改的是文字 |
| `injectionEra` 分档 | 与 re-pin 绑定 | 纯 lab 量具代码，与 pin 无关 |

**⇒ 本轮范围改为：在旧 pin `4a68e02d` 上把三件事付清，⛔ 不换 `pinnedSha`、⛔ 不建新 worktree。**

理由不止「能做」，还有「此刻不该做」：`subjects/tvu-ds/subject.json` 的 `repinProtocol.why` 逐字写着
「自然 re-pin 时机 = **DS 侧执行完某份 proposal 之后**」。而实测 DS 侧 **`27dbbaad` 提交于 08-26 10:41**（判定时刻 10:58，
17 分钟前）、当日已推 2 个 commit、工作树脏 ⇒ **DS 有 session 正在活跃跑，且跑的是 F138/F139 闸回归面那条线
（不是 lab 的处方），正跑到一半。** ⇒ 此刻 pin 下去，pin 点落在 DS 一轮的中间，不是语义完整的边界。

**⇒ 换 `pinnedSha` 推迟到 DS 那轮收尾之后。推迟不再有代价 —— 因为三件事的账本轮已经清了。**

### 0.3 本轮**不做**的（⛔ 别顺手做）

1. ⛔ **不换 `pinnedSha`**，⛔ 不建新 pin worktree，⛔ 不拆现有三个 pin。
2. ⛔ **不跑任何新 run**（本轮零 token 生成成本）。
3. ⛔ **不加大 N** —— 那是 re-pin 之后的下一轮。
4. ⛔ **不催 DS** 答严/宽口径，⛔ 不催 manual r006。
5. ⛔ **不改 `prompt.text` 与 `outputContract`** —— 见 §1 的三分档，改了它们就是 §7.5 的真 set 升级，要付桥接。

### 0.4 被排除的三条（⛔ 下轮别重问）

| 条 | 排除理由 |
|---|---|
| ① 加他厂 API key 解锁铁律 1 | 有个 lab 干不了的前置（用户去弄 key）；且 §13.7 第 3 条：解锁后 actionability **还要新生成 4 个修复 run**，那批 run 建在 pin 产物上，一样被 re-pin 作废 |
| ② 加大 N | 会被 re-pin 作废 ⇒ 单独做等于明知白花 ≈46 M token。**并进 re-pin 那批** |
| 只做零成本订正 | 被 ③ 包含（③ 本身就是零 token） |

---

## 1. 核心口径裁决：**「改了 task JSON」有三种性质，⛔ 不是一件事**

### 1.1 问题

`manifest.json` 只存**整文件 `sha256`**。⇒ 在校验器眼里：

> 「改引文出处」和「改 prompt」**完全不可区分** —— 两者都只表现为 `sha256` 变了。

而这两件事的后果天差地别：前者可比性不变，后者按 spec §7.5 要**新旧 set 各跑一遍做桥接**（≈85 M token）。

**⇒ 这正是 AGENTS §2.11「同一个观测值可能有多种解释 ⇒ 判据要把解释拆开分支」的同族形态。**
⛔ 修法不是「允许 sha 变」，是**把解释拆成三支**。

### 1.2 三分档（每档都断言，⛔ 分档不是放松）

| 档 | 判定 | 后果 | spec 依据 |
|---|---|---|---|
| **`identical`** | `sha256` 相同 | 完全没动 | — |
| **`provenance-only`** | `sha256` 变 ∧ `subjectSha256` 同 ∧ `graderSha256` 同 | **被测对象与判定口径都没变 ⇒ 既有读数完全可比，⛔ 不触发桥接** | §7.5 反面 |
| **`grader-changed`** | `subjectSha256` 同 ∧ `graderSha256` 变 | 被测对象同一，但**判定口径变了 ⇒ 跨档读数不可并池** | `lab:E28` 第 3 条（grader 换发射形态） |
| **`subject-changed`** | `subjectSha256` 变 | **真 set 升级** ⇒ 按 §7.5 要新旧各跑一遍桥接 | §7.5 |

### 1.3 两个新 sha 的口径（⛔ 口径名要写进 manifest）

| sha | 覆盖字段 | 为什么是这些 |
|---|---|---|
| `subjectSha256` | `{prompt, outputContract}` | **被测对象的身份** = AI 拿到什么指令 + 要交出什么形态。这两样定了，「被测对象是同一个」就成立 |
| `graderSha256` | `graders[]` 的 `{id, kind, judgeSource, script, caliper}` | 判定口径。⚠️ **逐字排除 `expectedDiscrimination`** —— 它是**预注册预测**不是判定口径，改它不改变任何判定。⛔ 不排除的话，本轮 Task 3 改 GT-003 的 P4 理由句会被错归成「grader 变了」 |

⚠️ **序列化必须 canonical**（递归按键名排序）—— `JSON.stringify` 的键序依赖插入序，
不做规范化的话「只调整字段顺序」会伪装成「内容变了」。

---

## 2. 顺带修的一条：**MH1/MH2/MH3 少钉了一维**（`lab:E43` 同族第三例）

`metrics/golden-set-freeze.mjs:177` 逐字：

```js
const hitFor = (taskId, needle) => citationHits.some((h) => h.taskId === taskId && h.mustContain === needle)
```

**只比 `mustContain`，不比 `path`。** ⇒ 本轮把 GT-002 的出处从 `_archive/_design-kickoffs/…` 换成
`_metrics/phase0-ledger.md` 后，只要 `mustContain` 仍含 `V4-1827`，**MH2 照样通过**，
而它的 detail 文字仍逐字写着「应在 `_archive/_design-kickoffs/v4-1827-ndi-into-ipsource.md` 命中」。

⇒ **一条本该在出处换代时炸的控制不炸，且带着一句已经不成立的说明继续报绿。**
⇒ 与 `lab:E43`（`first-shot` MH2 钉分布形状）、`lab:E42`（`convergence-probe` MH3 钉单解释）同族：
**控制钉的具名事实少了一维。** ⇒ 修法：`hitFor` 同时钉 `path` + `mustContain`。

---

## 3. 任务分解

**依赖顺序是硬的**：Task 1 是其余动作的**安全网** —— 必须先有三分档判据，
才能在改 task JSON 之后**证明**「被测对象没变」。⛔ 不许调换 1 和 2/3 的顺序。

---

### Task 1：`golden-set-freeze` 加改动性质三分档 + 修 `hitFor` 少钉的一维

**Files**
- Modify: `metrics/golden-set-freeze.mjs`
- Modify（由 `--freeze` 重写）: `tasks/tvu-ds/v1/manifest.json`

**Interfaces**
- Produces: manifest 每条 task 新增 `subjectSha256` / `graderSha256`；报告新增 `changeKind` 字段（值域见 §1.2）

- [ ] **Step 1：先跑一次基线，留下改动前的读数**

```bash
node metrics/golden-set-freeze.mjs --pin ~/.ai-ds-lab/pins/tvu-ds-4a68e02d \
  --out /tmp/gsf-before.json
```
Expected：`✅ v1 冻结完好：3 条 · 引文全部可定位 · 控制全过`（9/9）

- [ ] **Step 2：加 canonical 序列化与两个 sha 的计算函数**

在 `const sha256 = ...` 之后插入：

```js
// —— canonical 序列化：递归按键名排序 ——
// ⛔ 不做规范化的话「只调整字段顺序」会伪装成「内容变了」（JSON.stringify 键序依赖插入序）
const canon = (v) => {
  if (Array.isArray(v)) return v.map(canon)
  if (v && typeof v === 'object') return Object.fromEntries(Object.keys(v).sort().map((k) => [k, canon(v[k])]))
  return v
}
const canonSha = (obj) => sha256(Buffer.from(JSON.stringify(canon(obj)), 'utf8'))

// —— 被测对象身份：AI 拿到什么指令 + 要交出什么形态 ——
const subjectShaOf = (task) => canonSha({ prompt: task.prompt, outputContract: task.outputContract })

// —— 判定口径身份 ——
// ⚠️ 逐字排除 expectedDiscrimination：它是**预注册预测**不是判定口径，
//    改它不改变任何判定。⛔ 不排除的话，改一句预测的理由会被错归成「grader 变了」。
const graderShaOf = (task) => canonSha(
  (task.graders ?? []).map((g) => ({ id: g.id, kind: g.kind, judgeSource: g.judgeSource, script: g.script, caliper: g.caliper })))
```

- [ ] **Step 3：`tasks` 映射里带上两个 sha**

把 `return { file: f, raw, sha: sha256(raw), task: parsed }` 改成：

```js
  return { file: f, raw, sha: sha256(raw), subjectSha: subjectShaOf(parsed), graderSha: graderShaOf(parsed), task: parsed }
```

- [ ] **Step 4：`--freeze` 写入两个新 sha**

把 manifest 的 `tasks:` 那行改成：

```js
    tasks: tasks.map((t) => ({
      taskId: t.task.taskId, file: t.file,
      sha256: t.sha, subjectSha256: t.subjectSha, graderSha256: t.graderSha,
    })),
```

并在 `note` 之后加一行口径说明：

```js
    shaCaliper: {
      sha256: '整文件口径 —— 任何一个字节变了都变',
      subjectSha256: '被测对象口径 = canonical({prompt, outputContract}) —— 它变了才是 §7.5 的真 set 升级',
      graderSha256: '判定口径 = canonical(graders[].{id,kind,judgeSource,script,caliper}) —— ⛔ 逐字排除 expectedDiscrimination（那是预测不是判定口径）',
    },
```

- [ ] **Step 5：判据 B 改成三分档（⛔ 每档都断言，不是放松）**

把判据 B 整段替换为：

```js
// —— 判据 B：改动性质**三分档** —— ⛔ 不是「允许 sha 变」，是把解释拆开分支 ——
// AGENTS §2.11：同一个观测值（sha256 变了）有三种解释，后果天差地别 ⇒ 判据要分支，不能合并成一个「变了」。
//   provenance-only  被测对象与判定口径都没变 ⇒ 既有读数完全可比，⛔ 不触发 §7.5 桥接
//   grader-changed   被测对象同一、判定口径变了 ⇒ 跨档读数不可并池（lab:E28 第 3 条）
//   subject-changed  真 set 升级 ⇒ 按 §7.5 新旧 set 各跑一遍桥接，否则历史 baseline 全废
const changeKinds = []
for (const t of tasks) {
  const rec = (manifest.tasks ?? []).find((m) => m.file === t.file)
  if (!rec) continue // 已由 A 报
  // ⛔ fail closed：老 manifest 没有这两个字段 ⇒ 判不了性质，抛，不静默当成「没变」
  if (!rec.subjectSha256 || !rec.graderSha256) {
    fail('B-caliper-missing', `${t.file} 的 manifest 记录缺 subjectSha256/graderSha256 ⇒ 改动性质判不了，⛔ 不得静默降级成「未变」。请重跑 --freeze`, { taskId: t.task.taskId })
    continue
  }
  const subjectSame = rec.subjectSha256 === t.subjectSha
  const graderSame = rec.graderSha256 === t.graderSha
  const fileSame = rec.sha256 === t.sha
  const kind = fileSame ? 'identical'
    : !subjectSame ? 'subject-changed'
    : !graderSame ? 'grader-changed'
    : 'provenance-only'
  changeKinds.push({ taskId: t.task.taskId, file: t.file, changeKind: kind })
  if (kind === 'subject-changed') {
    fail('B-subject-changed', `${t.file} 的**被测对象**变了（prompt/outputContract）⇒ 这是 §7.5 的真 set 升级，要新旧 set 各跑一遍桥接，⛔ 不得直接沿用历史 baseline`, { frozen: rec.subjectSha256, now: t.subjectSha })
  } else if (kind === 'grader-changed') {
    fail('B-grader-changed', `${t.file} 的**判定口径**变了（graders）⇒ 被测对象同一但跨档读数不可并池（lab:E28 第 3 条）`, { frozen: rec.graderSha256, now: t.graderSha })
  } else if (kind === 'provenance-only') {
    fail('B-provenance-only', `${t.file} 的非被测对象字段变了（provenance/whyThisTask/boundaries/expectedDiscrimination）—— 可比性不受影响，但冻结物已不同步 ⇒ 请重跑 --freeze 后再用`, { frozen: rec.sha256, now: t.sha })
  }
}
```

- [ ] **Step 6：修 `hitFor` 少钉的一维（§2）**

把 `hitFor` 与 MH1–MH3 改成同时钉 `path`：

```js
// ⚠️ 2026-08-26 订正（lab:E43 同族第三例）：原实现只比 mustContain、⛔ 不比 path ——
//    出处换了文件而引文串不变时，这条控制**照样通过**，还带着一句已不成立的 detail 报绿。
//    ⇒ 控制钉的具名事实少了一维。修法：path + mustContain 一起钉。
const hitFor = (taskId, path, needle) =>
  citationHits.some((h) => h.taskId === taskId && h.path === path && h.mustContain === needle)

ctl('MH1', 'must-hit', hitFor('GT-001', 'docs/internal/_plans/2026-07-24-p0-systematic-gap-fixes.md', '直播源管理'),
  'GT-001 的「直播源管理」应在 docs/internal/_plans/2026-07-24-p0-systematic-gap-fixes.md 命中（DS 3-way 实验用的同一条任务）')
ctl('MH2', 'must-hit', hitFor('GT-002', 'docs/internal/_metrics/phase0-ledger.md', 'V4-1827 Round 3'),
  'GT-002 的「V4-1827 Round 3」应在 docs/internal/_metrics/phase0-ledger.md 命中（⚠️ 2026-08-26 换出处，原 _archive/_design-kickoffs/ 已被 DS 删除，见 lab:N37）')
ctl('MH3', 'must-hit', hitFor('GT-003', 'docs/internal/_metrics/phase0-ledger.md', 'chart primitives 不在 library scope'),
  'GT-003 的「chart primitives 不在 library scope」应在 docs/internal/_metrics/phase0-ledger.md 命中')
```

- [ ] **Step 7：`changeKinds` 进报告与 stdout**

`report` 对象里 `caliper` 之后加 `changeKinds,`；stdout 在「判据绑定」那行之后加：

```js
if (changeKinds.length) {
  console.log(`改动性质：${changeKinds.map((c) => `${c.taskId}=${c.changeKind}`).join(' · ')}`)
}
```

- [ ] **Step 8：造故障验证三分档真的分得开（⛔ 全在 scratchpad 副本上，AGENTS §3.13）**

三次造故障，每次改 scratchpad 里的 set 副本，⛔ **不碰 `tasks/tvu-ds/v1/`**：

```bash
SP=/private/tmp/claude-501/-Users-nancy-Documents-AICoding-VS-Code-ai-ds-lab/231fc976-d28e-472f-a0b1-1723f1699810/scratchpad
rm -rf "$SP/fault-set" && cp -R tasks/tvu-ds/v1 "$SP/fault-set"
# F-1 只改 boundaries（非被测对象）   ⇒ 期望 provenance-only
# F-2 只改 graders[].caliper          ⇒ 期望 grader-changed
# F-3 改 prompt.text 一个字           ⇒ 期望 subject-changed
```
每次跑：`node metrics/golden-set-freeze.mjs --pin ~/.ai-ds-lab/pins/tvu-ds-4a68e02d --set "$SP/fault-set"`
Expected：分别报 `B-provenance-only` / `B-grader-changed` / `B-subject-changed`，且**互不误判**。

- [ ] **Step 9：证明对既有读数 no-op（AGENTS §3.14）**

⚠️ 此时 `tasks/tvu-ds/v1/` **一个字节都没改**，所以除新增字段外读数必须逐字段相同：

```bash
node metrics/golden-set-freeze.mjs --pin ~/.ai-ds-lab/pins/tvu-ds-4a68e02d --out /tmp/gsf-after.json
node -e "const a=require('/tmp/gsf-before.json'),b=require('/tmp/gsf-after.json');
for(const k of ['caliper','citationHits','graderHits','failures','verdict'])
  console.log(k, JSON.stringify(a[k])===JSON.stringify(b[k])?'IDENTICAL':'DIFF');
console.log('controls', a.controls.length, '->', b.controls.length);"
```
Expected：五项全 `IDENTICAL`（⚠️ `B-caliper-missing` 会在旧 manifest 上先报一次 —— 那是**设计意图**，
说明 fail-closed 生效；跑 `--freeze` 补齐字段后再对拍）。

- [ ] **Step 10：Commit**

```bash
git add metrics/golden-set-freeze.mjs docs/round2-repin-plan.md
git commit -m "feat(metrics): golden-set-freeze 加改动性质三分档 + 修 hitFor 少钉的 path 那一维"
```

---

### Task 2：GT-002 换引文出处（`lab:N37` 的到期日）

**Files**
- Modify: `tasks/tvu-ds/v1/gt-002-ndi-source-type-form.json`（**只动 `provenance.citations` 与 `boundaries`**，⛔ 不碰 `prompt`）

**Interfaces**
- Consumes: Task 1 的 `changeKind` —— 改完必须实测判成 `provenance-only`

- [ ] **Step 1：确认新出处在两侧都逐字可定位**

```bash
# 旧 pin（本轮的校验面）
grep -c "V4-1827 Round 3" ~/.ai-ds-lab/pins/tvu-ds-4a68e02d/docs/internal/_metrics/phase0-ledger.md
grep -c "radio 三选一互斥" ~/.ai-ds-lab/pins/tvu-ds-4a68e02d/docs/internal/_metrics/phase0-ledger.md
# DS 当前 master（前瞻性证据，⛔ 只读查询、不是 pin 校验）
git -C ~/Documents/AICoding/VS_Code/tvu-design-system grep -c "radio 三选一互斥" 27dbbaad -- docs/internal/_metrics/phase0-ledger.md
```
Expected：三条都 ≥ 1（已实测：旧 pin 2/1、DS master 命中）

- [ ] **Step 2：把 `citations` 换成 ledger 出处**

```json
    "citations": [
      {
        "path": "docs/internal/_metrics/phase0-ledger.md",
        "mustContain": "V4-1827 Round 3",
        "why": "真 Jira 需求：RPS One 支持 NDI 作为 Live Input（Config-T）。⚠️ 2026-08-26 换出处（lab:N37 到期）——原出处 `docs/_archive/_design-kickoffs/v4-1827-ndi-into-ipsource.md` 已被 DS 在 commit `483e3feb` 连同整个 `_archive/_design-kickoffs/` 目录删除。ledger 是 append-only 台账，与 GT-003 同源。"
      },
      {
        "path": "docs/internal/_metrics/phase0-ledger.md",
        "mustContain": "radio 三选一互斥",
        "why": "增量契约成文的凭据：该行逐字记着「NDI 归入现有 IP Source 页、`Type of source` radio 三选一互斥（HDMI/SDI/USB · IP Source · NDI）、per-Tx、三态内容区」+「AFTER 三帧（IP 6卡 / NDI 单源表单 / HDMI 居中提示）+ 分隔线(`--line-light`)」⇒ 逐条覆盖本任务 prompt 的四条要求。"
      }
    ]
```

- [ ] **Step 3：`boundaries` 追加两条 —— 换出处的性质与代价，⛔ 不许含糊**

在 `boundaries` 数组末尾追加：

```json
    "⚠️ **换出处的性质（2026-08-26，lab:N37 到期）**：本条 prompt 是冻结时照 kickoff 卡的 `Baseline / Delta Contract` 段（事前契约，Keep/Change 逐条成文）转写的；新出处 `phase0-ledger.md:108` 是**事后流水记录**。两者都能逐条支撑 prompt 的四条要求（已逐条对拍），但**形态不同** ⇒ ⛔ 本条应读作「为同一段已冻结的 prompt 重新找一个可定位的凭据」，**不是**「照新出处重新转写了 prompt」。prompt.text 一个字未改（subjectSha256 未变，由 golden-set-freeze 的三分档实测钉住）。",
    "⚠️ **换出处引入一处单点风险**：GT-002 与 GT-003 现在引的是**同一份文件** `docs/internal/_metrics/phase0-ledger.md` ⇒ 该文件若被删/被搬，**两条 task 同时失去凭据**。取舍理由：它是 append-only 台账（DS 侧无删除动机），且原 `_archive/` 那条路已实证会被清理。⛔ 如实登记，不假装没有。"
```

- [ ] **Step 4：实测改动性质必须是 `provenance-only`**

```bash
node metrics/golden-set-freeze.mjs --pin ~/.ai-ds-lab/pins/tvu-ds-4a68e02d
```
Expected：stdout 出现 `改动性质：GT-002=provenance-only`，且 `failures` 里**只有** `B-provenance-only`
（⛔ 不得出现 `B-subject-changed` —— 出现了说明手滑碰了 prompt）

- [ ] **Step 5：Commit**（⛔ 此时先别 `--freeze`，留到 Task 6 一次冻结）

```bash
git add tasks/tvu-ds/v1/gt-002-ndi-source-type-form.json
git commit -m "fix(golden-set): GT-002 换引文出处到 phase0-ledger —— lab:N37 到期，prompt 未动"
```

---

### Task 3：GT-003 重写立论（`lab:E41`）

**Files**
- Modify: `tasks/tvu-ds/v1/gt-003-cost-per-token-trend.json`（**只动 `whyThisTask` / `graders[1].expectedDiscrimination` / `boundaries`**，⛔ 不碰 `prompt`、⛔ 不碰 `citations`）

- [ ] **Step 1：`whyThisTask` 重写**

原文建在已失效的前提上（「唯一命中 DS 真源结构性缺件」）。替换为：

```json
  "whyThisTask": "三条里唯一的数据可视化 archetype —— 前两条是列表台与表单流，图表的组合面（坐标轴/图例/趋势色/时间范围切换）与它们不重叠。⚠️ **2026-08-26 重写（lab:E41）**：原立论写的是「唯一命中 DS 真源结构性缺件的一条…测『无件时怎么办』」，建在 citation 那句 2026-05-11 的自陈「chart primitives 不在 library scope」上 —— **该前提在 pin `4a68e02d` 上实测已不成立**：`src/components/Chart/Chart.vue`（2,802 B，根 class `tvu-chart`）+ `build-chart-option.ts` + `use-chart-tokens.ts` 真实存在，且 GT-003 的多个 run 真的用了它。⇒ 本条现在测的是**「有件但组合面复杂时怎么选」**，不是「无件时怎么办」。",
```

- [ ] **Step 2：`graders[1].expectedDiscrimination` 重写**（P4 的理由句失效）

```json
      "expectedDiscrimination": "⚠️ **2026-08-26 重写（lab:E41）**。原预测「本条是三条里 escape-rate 的主力样本（DS 自陈 chart primitives 缺位 ⇒ 图表配色只能逃逸）」**预测与理由同时错**：实测 `unmatchedDistinct` 均值 **13.6**，反而**低于** GT-001 的 18.2（预注册对账 P4 = MISS）。机制已查明且方向合理：**有真组件可用 ⇒ 逃逸更少**。⇒ 改为：本条的逃逸面主要来自图表**周边**（时间范围切换、折叠说明面板、趋势涨跌色），⛔ **不预测它是 escape-rate 主力**。⚠️ 本条是**改写后的预测，⛔ 不再是盲预测** —— 它写于 r012–r015/r017 跑完之后，与另两条 task 的预注册预测**不同级**，⛔ 不得并排当成预注册命中率的分子或分母。"
```

- [ ] **Step 3：`boundaries` 第 2 条改成「已兑现」登记，⛔ 不删原文**

把原第 2 条替换为：

```json
    "✅ **该边界已于 2026-08-26 兑现（lab:E41）** —— 原文逐字保留在此：「『chart primitives 不在 library scope』是 2026-05-11 那一行的自陈，不是 lab 在 4a68e02d 上复核过的当前事实。若 DS 后来补了图表图元，本条的立论要重取。⇒ 已写成 citation，冻结校验器只保证这句话在 pin 上逐字存在，⛔ 不保证它仍为真。」⇒ **预警在冻结时就写下了，所以发现时不需要重新论证、只需认账**。这是「边界不是 TODO」这条纪律的一次正面回报。⚠️ citation 本身**不改** —— 那句话在 pin 上确实逐字存在，校验器验的就是这个；失效的是**立论**不是**引文**，两者要分开。",
```

- [ ] **Step 4：实测改动性质必须是 `provenance-only`**

```bash
node metrics/golden-set-freeze.mjs --pin ~/.ai-ds-lab/pins/tvu-ds-4a68e02d
```
Expected：`改动性质：GT-002=provenance-only · GT-003=provenance-only`
⚠️ **这一步是 §1.3 那个口径裁决的实测检验**：`expectedDiscrimination` 被逐字排除在 `graderSha256` 之外，
所以改它**不得**判成 `grader-changed`。判成了 = 排除没生效，回 Task 1 Step 2 修。

- [ ] **Step 5：Commit**

```bash
git add tasks/tvu-ds/v1/gt-003-cost-per-token-trend.json
git commit -m "fix(golden-set): GT-003 重写立论与 P4 预测 —— lab:E41，边界已兑现，prompt 未动"
```

---

### Task 4：`injectionEra` 分档 + 给 DS 的 N40 绿灯

**Files**
- Modify: `metrics/context-manifest.mjs:383-390`（MH2 那段）

**Interfaces**
- Consumes: `chainEra` 的分档形态（`adapters/ds-gate-chain-run.mjs:170-180`）—— **同构，⛔ 不另立形态**

- [ ] **Step 1：先跑基线留读数**

```bash
node metrics/context-manifest.mjs --pin ~/.ai-ds-lab/pins/tvu-ds-4a68e02d --out /tmp/cm-before.json 2>&1 | tail -20
```

- [ ] **Step 2：把 MH2 改成两档都断言**

```js
// —— injectionEra 分档（与 chainEra / contractEra / taskEra 同构，AGENTS §3.2 推论四）——
// ⛔ 分档**不是放松**：两档都断言。
//  degraded 档：DS 的 onboarding hook 在 worktree 里恒降级成「探测失败」（N40 的具名事实）
//  precise  档：DS 把 `-d .git` 修成 `-e .git` 之后，hook 能拿到真实 dirty 数
// ⚠️ **跨档不并池**：注入面是 §5.1 contextManifest 的 L1（确定性层）⇒ 一变，
//    6′ 那 5 个 run 的 MDE 只对 degraded 档成立，⛔ 新旧 run 不可并池（同 §5.2「不允许跨线直接 diff」）。
const injectionEra =
  ssText.includes('探测失败') && ssText.includes('当作有并行 session') ? 'degraded'
  : /并行 session|dirty/.test(ssText) ? 'precise'
  : 'unknown'

hit('MH2', injectionEra !== 'unknown',
  injectionEra === 'degraded'
    ? '注入面 = degraded 档（N40 未修）：hook 恒报「探测失败」+「当作有并行 session」⇒ 与 6′/step11/区分度实验那批 run 同档，可并池'
    : injectionEra === 'precise'
    ? '注入面 = precise 档（N40 已修，-d→-e）：hook 拿得到真实 dirty ⇒ ⛔ 与 degraded 档那批 run **不可并池**，MDE 要在本档重取'
    : `⛔ 注入面判不出档 —— 两档的具名串都没命中，装置可能坏了（先证装置再归因，AGENTS §3.9）。实测文本前 200 字：${ssText.slice(0, 200)}`)
```

- [ ] **Step 3：造故障 —— 把 `ssText` 换成 precise 档的样子，验证它判得出且不误判**

在 scratchpad 里跑一个改了桩的副本，⛔ 不碰真量具：
```bash
SP=/private/tmp/claude-501/-Users-nancy-Documents-AICoding-VS-Code-ai-ds-lab/231fc976-d28e-472f-a0b1-1723f1699810/scratchpad
cp metrics/context-manifest.mjs "$SP/cm-fault.mjs"
# 在副本里把 ssText 强制成 "检测到 3 个并行 session（dirty=2）" ⇒ 期望 injectionEra=precise 且 MH2 过
# 再强制成 "hello world"                                      ⇒ 期望 injectionEra=unknown 且 MH2 **炸**
```
Expected：precise 档过且 detail 明说「不可并池」；unknown 档**必须炸**（⛔ fail open 就是错的）

- [ ] **Step 4：证明对既有读数 no-op**

```bash
node metrics/context-manifest.mjs --pin ~/.ai-ds-lab/pins/tvu-ds-4a68e02d --out /tmp/cm-after.json 2>&1 | tail -20
diff <(node -e "const j=require('/tmp/cm-before.json');delete j.generatedAt;console.log(JSON.stringify(j,null,2))") \
     <(node -e "const j=require('/tmp/cm-after.json');delete j.generatedAt;console.log(JSON.stringify(j,null,2))")
```
Expected：diff 只出现 MH2 的 `detail` 文字变化（从旧措辞变成带档名的措辞），**判定值仍为 true，其余字段 0 行差异**

- [ ] **Step 5：Commit**

```bash
git add metrics/context-manifest.mjs
git commit -m "feat(metrics): context-manifest MH2 落 injectionEra 分档 —— 两档都断言，跨档不并池"
```

---

### Task 5：文档 —— §14.6 订正 + DS HEAD 换代登记 + 新章

**Files**
- Modify: `docs/round2-status.md`（§14.6 订正 · §14.4 补 DS HEAD · 新增 §16）
- Modify: `AGENTS.md`（§2 加第 12 条）
- Create: `reports/2026-08-26-round2-repin-part1.md`

- [ ] **Step 1：§14.6 整段订正 —— DS 已亲手证伪**

§14.6 现文照 DS 旧框架转述（「三个实例是同一个缺口 / 跨仓引用谁都看不见 / 治法二选一」）。
DS 在 `40e3043a`（08-25 17:28）读 lab 仓源码活源后**三句全推翻**。改写要点：
① 三类是**三种不同性质**，答案不一样；② lab 侧三类**全都看得见且全是 fail-closed**；
③ 两个候选**一个已存在**（lab 早有 DS-HEAD 漂移检测 = `subject.json` 的 `driftAtFirstRound` + `repinProtocol.dsMustReport`）、
**一个被 DS 本仓规则禁止**（手维护的会漂副本）；④ **真缺口 = 「发现时点」不是「可见性」**：
lab 在下次 re-pin / 下次跑量具时报错，DS 在改的那一刻没有信号；
⑤ ⇒ owner 只需拍**一个**问题（DS 文件路径那一类），另两个不需要拍。
⛔ **原文不删，标成「已被 DS 证伪」并保留**，理由同 AGENTS §2.11（被推翻的框架要留痕，否则下轮会照旧措辞再引一次）。

- [ ] **Step 2：§14.4 补 DS HEAD 换代**

`6b8ad767` → **`27dbbaad`**（+8 commit）。要登记的实测事实：
DS 第三十三轮（`40e3043a`）+ F138/F139 闸回归面线（`bd53474d`/`ef14d6e8`/`27dbbaad`）；
⚠️ **DS 有 session 正在活跃跑**（`27dbbaad` 提交于 08-26 10:41，判定时刻 10:58）；
⚠️ **严/宽口径仍未答** —— 已 grep `6b8ad767..27dbbaad` 全量 diff，零处提及。

- [ ] **Step 3：新增 §16 本轮交付**

含：范围改动的理由（§0.2 那三条被推翻的前提）、三分档口径、三件事各自的落地与实测 `changeKind`、
`injectionEra` 分档、⛔ 未覆盖清单（换 `pinnedSha` 未做、加大 N 未做、桥接未触发的理由）。

- [ ] **Step 4：`AGENTS.md` §2 加第 12 条**

```markdown
12. **「改了被测对象」和「改了它的说明文字」是两件事，⛔ 冻结物只存整文件 sha 就分不开。**
   实证（2026-08-26 re-pin 第一批）：`manifest.json` 原本只存整文件 `sha256` ⇒
   「换引文出处」与「改 prompt」在校验器眼里**完全不可区分**，两者都只表现为 sha 变了；
   而后果天差地别 —— 前者可比性不变，后者按 spec §7.5 要**新旧 set 各跑一遍桥接**（≈85 M token）。
   ⇒ **操作口径**：冻结物要按「身份」分别存 sha —— 被测对象（`{prompt, outputContract}`）、
   判定口径（`graders` 的 `{id,kind,judgeSource,script,caliper}`）、其余（provenance/立论/边界）。
   判据按三档分支断言，⛔ 不合并成一个「变了」。
   ⇒ 推论一：**`expectedDiscrimination` 要逐字排除在判定口径 sha 之外** ——
   它是**预注册预测**不是判定口径，改它不改变任何判定；不排除的话「重写一句预测的理由」
   会被错归成「grader 变了 ⇒ 读数不可并池」。
   ⇒ 推论二（§2.11 的第四例）：**控制少钉一维 = 换代时不炸**。
   `golden-set-freeze` 的 `hitFor` 原本只比 `mustContain` 不比 `path` ⇒ 出处换了文件而引文串不变时，
   MH2 **照样报绿**，还带着一句已不成立的 detail。⇒ 钉引文要 `path` + `mustContain` 一起钉。
```

- [ ] **Step 5：写报告 `reports/2026-08-26-round2-repin-part1.md`**

必含：§0 一句话 · §1 范围为什么改（三条被推翻的前提 + DS 活跃 session 的实测时间戳）·
§2 三分档口径与造故障对照 · §3 GT-002 换出处的**性质**（事前契约 → 事后流水，⛔ 不是重新转写）+ 单点风险 ·
§4 GT-003 立论重写 + 「改写后的预测 ⛔ 不算预注册」· §5 `injectionEra` 两档 · §6 §14.6 那条的证伪始末 ·
§7 no-op 证明 · §8 ⛔ 未覆盖清单 · §9 给下一轮的条目。

- [ ] **Step 6：Commit**

```bash
git add docs/round2-status.md AGENTS.md reports/2026-08-26-round2-repin-part1.md
git commit -m "docs(lab): §14.6 订正(DS 已证伪) + DS HEAD 换代 27dbbaad + §16 交付 + AGENTS §2.12"
```

---

### Task 6：重新冻结 + 全量验证 + 收尾

- [ ] **Step 1：重新冻结**

```bash
node metrics/golden-set-freeze.mjs --pin ~/.ai-ds-lab/pins/tvu-ds-4a68e02d --freeze
```
Expected：`✅ 已冻结 3 条`，manifest 里三条各有 `sha256` / `subjectSha256` / `graderSha256`

- [ ] **Step 2：⚠️ 验证 `subjectSha256` 与冻结前逐位相同 —— 这是本轮最关键的一条验证**

```bash
git show HEAD~4:tasks/tvu-ds/v1/manifest.json > /tmp/manifest-old.json 2>/dev/null || true
node -e "
const {createHash}=require('crypto');
const canon=(v)=>Array.isArray(v)?v.map(canon):(v&&typeof v==='object'?Object.fromEntries(Object.keys(v).sort().map(k=>[k,canon(v[k])])):v);
const sha=(o)=>createHash('sha256').update(Buffer.from(JSON.stringify(canon(o)),'utf8')).digest('hex');
for (const f of ['gt-001-live-source-console','gt-002-ndi-source-type-form','gt-003-cost-per-token-trend']) {
  const cur=require('./tasks/tvu-ds/v1/'+f+'.json');
  const old=JSON.parse(require('child_process').execSync('git show HEAD~4:tasks/tvu-ds/v1/'+f+'.json').toString());
  const a=sha({prompt:old.prompt,outputContract:old.outputContract}), b=sha({prompt:cur.prompt,outputContract:cur.outputContract});
  console.log(cur.taskId, a===b?'subjectSha IDENTICAL ✅':'⛔ CHANGED —— 被测对象变了，要付 §7.5 桥接');
}"
```
Expected：**三条全 `IDENTICAL`** ⇒ 被测对象一个字节没动 ⇒ 既有 15 个 run 的读数**完全可比，⛔ 不触发桥接**

- [ ] **Step 3：全量校验必须回到全绿**

```bash
node metrics/golden-set-freeze.mjs --pin ~/.ai-ds-lab/pins/tvu-ds-4a68e02d
```
Expected：`✅ v1 冻结完好：3 条 · 引文全部可定位 · 控制全过`，`改动性质：… identical`（三条全 identical）

- [ ] **Step 4：三个 pin 逐个核 dirty=0（AGENTS §3.13，起止都要核）**

```bash
for p in 339a72bf 4a68e02d 71ac2711; do
  printf '%s ' "$p"; git -C ~/.ai-ds-lab/pins/tvu-ds-$p status --porcelain | wc -l
done
```
Expected：三行全 `0`

- [ ] **Step 5：跑一遍受影响的量具，确认没连带炸**

```bash
node metrics/first-shot.mjs --task GT-001 2>&1 | tail -5
node metrics/convergence-probe.mjs --task GT-001 2>&1 | tail -5
```
Expected：14/14 与 11/11 仍全过（本轮没动这两个量具，此步是连带面检查）

- [ ] **Step 6：Commit + push**

```bash
git add tasks/tvu-ds/v1/manifest.json
git commit -m "chore(golden-set): 重新冻结 v1 —— 三条 subjectSha256 逐位未变，⛔ 不触发 §7.5 桥接"
git push origin master
```

---

## 4. 自查（写完计划后对着 §0–§3 过一遍）

| 检查 | 结论 |
|---|---|
| 三件事都有 task 落地？ | GT-002→Task 2 · GT-003→Task 3 · `injectionEra`+N40→Task 4 ✅ |
| §14.6 订正有落地？ | Task 5 Step 1 ✅ |
| 有没有 placeholder / TBD？ | 无 —— 每步都带可跑命令或可粘贴的 JSON/JS ✅ |
| 类型/字段名前后一致？ | `subjectSha256` / `graderSha256` / `changeKind` 三个名字在 Task 1、2、3、6 里逐处一致 ✅ |
| 有没有动 `prompt`？ | ⛔ 无 —— Task 2/3 逐步都写明「不碰 prompt」，且 Task 6 Step 2 用独立算式**实测**钉住 ✅ |
| no-op 证明？ | Task 1 Step 9（golden-set-freeze）· Task 4 Step 4（context-manifest）✅ |
| 造故障？ | Task 1 Step 8（三分档 3 次）· Task 4 Step 3（injectionEra 2 次）✅ |
