# Migration: schema_version 1.3 → 1.4

> **Type**: MINOR **but BREAKING for blind channel integrity**——state 字段不变，但 `rubric_notes.md` 必须拆开。**老用户跑 migrate 之前 blind sub-agent 仍泄漏实绩**。

---

## WHAT changed

### 文件层（**核心**）

| 之前 | 之后 |
|---|---|
| `rubric_notes.md` 顶部累积所有 bump Memo（含视频名 + 实绩 + 派生证据） | `rubric_notes.md` 只含通用语言（公式 / 维度定义 / bucket 边界 / 指针），**绝不**含实绩 |
| —— | `rubric-memo.md`（**新**）累积所有 bump Memo 全文（含视频名 + 实绩 + 派生证据） |

### state 字段

无新增 / 删除 / 改名。仅 `schema_version` bump 标识本次"文件层迁移"必跑。

### channel B hard refusal list

`cheat-score-blind/SKILL.md` 加 `rubric-memo.md` 路径 + refusal_code `blocked_rubric_memo`。

---

## WHY

PR #11 引入 blind-scoring sub-agent（channel B）时承诺它只能读 `scripts/<id>.md` + `rubric_notes.md`。但 cheat-bump Phase 5 落地时把升级 Memo（含证据数据表 + 派生证据 + 真实视频名 + 实绩）写进 rubric_notes.md ——sub-agent 通过白名单读到了本不该看的实绩数据，盲打分变成"看过实绩的事后合理化"。

实测复现（PR #11 测试）：

```
contamination_note: rubric_notes.md v0→v1 bump memo 含本 script 同名条目「它让我发的 38w」实绩
contamination_note: rubric_notes.md 派生证据段含本 script 名+实绩 13.7w，CC 锚点被预先校准
→ refusal: "non_blind_warning"
→ all dimensions confidence: medium
```

修复：拆 file。`rubric_notes.md` 严格只放通用规则（blind 白名单），bump 历史档案沉淀到 `rubric-memo.md`（blind 硬禁读）。详见 [shared-references/observation-lifecycle.md](../shared-references/observation-lifecycle.md) "Blind channel leak guard" 段。

---

## HOW (Claude steps for /cheat-migrate)

按顺序执行。**不可幂等的步骤要先检查是否已跑过**——本迁移涉及内容文件改写，重跑可能产生重复内容。

### Step 1：前置检查

1. 读 `.cheat-state.json` → 校验 `schema_version == "1.3"`，否则报错"当前 state 不是 1.3，无需此迁移"
2. 检查 `rubric_notes.md` 存在；不存在 → 报错"找不到 rubric_notes.md。是不是项目还没 init？"
3. 检查 `rubric-memo.md` **是否已存在**：
   - 已存在 → 询问用户"rubric-memo.md 已有内容，跳过 split 直接 bump schema 到 1.4？回 yes 跳过，回 no 中止迁移"
   - 不存在 → 进 Step 2

### Step 2：备份

```bash
cp rubric_notes.md rubric_notes.md.backup-$(date +%s)
```

提示用户："已备份 `rubric_notes.md` —— 拆分若出错可恢复"。

### Step 3：扫描 + 抽离 `rubric_notes.md` 的违禁段

读 `rubric_notes.md` 全文。识别下列 pattern：

| Pattern | 处理 |
|---|---|
| 整段 `## v.X → v.Y 升级 Memo` 含子段（触发观察 / 证据数据 / 诊断 / 跨模型审核 / 已知局限） | **整段抽离**到 rubric-memo.md，原位**删除** |
| 派生证据段中带视频名 + 数字（如「停止期待」实绩 13.7w） | **整条抽离**到 rubric-memo.md 的"派生证据"子段；原位用通用语言替代（"高抽象密度样本 → CC=1 → 低 reach"） |
| 校准池重打表 / 排序对照表 | **整表抽离**到 rubric-memo.md 对应 Memo 的"证据数据"子段；原位删除（公式速查表保留——公式速查不含视频名） |
| 跨模型审核引用含具体视频名 + 实绩 | 抽离 |
| 散落的 `\d+w` / `\d+万` 数字（**例外：bucket 边界** 5-30w / 30-100w 等保留） | 用 Claude 判断：是 bucket 边界 → 保留；是实绩引用 → 抽离 |

### Step 4：写 `rubric-memo.md`

如果文件不存在 → 从 `cheat-on-content/templates/rubric-memo.template.md` 复制空骨架。

然后把 Step 3 抽离出来的 Memo 段**按时间顺序**（旧的在上 / 新的在下）append 到文件末尾。每段 Memo 用 [templates/rubric-memo.template.md](../templates/rubric-memo.template.md) 的 6 组件格式包装；缺组件的标 `(unknown from legacy migration)`。

### Step 5：清理 `rubric_notes.md`

- 顶部 metadata 段加：
  ```markdown
  **Upgrade memos**: 见 [rubric-memo.md](rubric-memo.md)
  ```
- Memo 段被 Step 3 抽走的部分留**短指针**："v0→v1 升级 Memo 已迁移到 rubric-memo.md，跑 `cat rubric-memo.md` 查看"
- 派生证据段替换为通用语言

### Step 6：自检 leak guard

对清理后的 `rubric_notes.md` 跑：

```bash
grep -E '\d+\s*[wWmMkK万]|播放|实绩|实际' rubric_notes.md \
  | grep -v -E '^\s*\|[^|]*\|[^|]*\|.*[0-9]+\s*[wkM][^a-zA-Z]' \
  | grep -v -E '5-30w|30-100w|100-150w|>150w'
```

（grep 排除明显的 bucket 边界数字）

- 无命中 → 通过
- 命中 → 报告剩余的"实绩引用" + 让用户手工 review / 进一步抽离 → 继续 Step 7 或中止

### Step 7：bump schema + 写回 state

1. 设 `.cheat-state.json` 的 `schema_version = "1.4"`
2. **原子写**
3. 报告："✅ 迁移到 1.4 完成。`rubric_notes.md` 现仅含通用语言（{N} 行），`rubric-memo.md` 含 {M} 段历史 Memo。备份保留：rubric_notes.md.backup-{ts}。"

---

## Manual fallback

不想跑 `/cheat-migrate` 的用户可以**手动拆**：

1. 备份：`cp rubric_notes.md rubric_notes.md.backup`
2. 把所有 `## v.X → v.Y 升级 Memo` 段从 `rubric_notes.md` 整体剪切到**新建**的 `rubric-memo.md`（按时间顺序保留）
3. 把派生证据段的视频名 + 实绩剪到 rubric-memo.md，原位用通用语言替代
4. 在 `rubric_notes.md` 顶部加 `**Upgrade memos**: 见 [rubric-memo.md](rubric-memo.md)` 指针
5. 手改 `.cheat-state.json` 的 `schema_version` 为 `"1.4"`

或者**最懒**：删 `rubric_notes.md` 重 init（损失：所有历史观察 + bump memo），跑 `bash install.sh` 后说"初始化"——cheat-init 会重新创建两份空文件。仅适合校准池还很小（< 5 条）的早期用户。

---

## 验收

迁移完成后：

```bash
# state 升到 1.4
jq '.schema_version' .cheat-state.json
# → "1.4"

# rubric_notes.md 无实绩泄漏
grep -E '播放|实绩|实际|\d+w[^a-zA-Z0-9]' rubric_notes.md \
  | grep -v -E '5-30w|30-100w|100-150w|>150w' \
  | head
# → 应输出空（除 bucket 边界）

# rubric-memo.md 含历史 Memo
ls -la rubric-memo.md
wc -l rubric-memo.md
# → 行数 > 50（取决于历史 bump 次数）

# blind sub-agent 不再标 non_blind_warning
# 跑 /cheat-score scripts/<已发样本>.md
# → JSON.refusal == null, JSON.self_check.any_contamination_signal == false
```
