Просмотр исходного кода

docs(v5): 新增 v5_implementation_briefs/M0(对齐 V4 颗粒度:Index+M0A+M0B)

M0=安全网与零变化基准,不改业务代码;复用 replay_harness/snapshot.py 建 scorecard 指纹回归门;逐条比对(非计数summary);file:line 已核实

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sam Lee 1 месяц назад
Родитель
Сommit
a0dc0b87fd

+ 34 - 0
tech_documents/工程落地/v5_implementation_briefs/M0/00_M0_Brief_Index.md

@@ -0,0 +1,34 @@
+# M0 安全网与零变化基准 Brief Index
+
+状态:M0 = V5 第一步,只建"零变化标尺",不改任何业务代码。承接 `tech_documents/工程落地/14_V5阶段开发计划.md` §5 M0。
+
+## M0 目标
+
+为 V5 后续 M1-M4 的每一批改造提供一条**逐条可证明的零变化回归门**:抖音(三维)、快手(两维)两条 V4 金标准 corpus 离线回放后,scorecard 指纹必须与基线逐条一致——一致=零变化,不一致=回归当场拦下。
+
+## 子 brief 清单
+
+| Brief | 标题 | 职责 |
+|---|---|---|
+| M0A | 金标准 Corpus 与回放基线 | 固定两条 corpus、复用 `replay_harness` 离线复现、产出 scorecard 指纹基线快照 |
+| M0B | 零变化回归门与验收 | 把指纹比对接成回归 test + 验收命令 + sub-agent 审计口径,供 M1-M4 每批挂用 |
+
+> 子 brief 数量说明:V4 M0 是"合同冻结"涉及 DB/字段/walk/验收 5 块故拆 M0A-E;V5 M0 职责单一(建标尺),按内容拆 **2 个**,对齐 V4"每 brief 12 章细致度"但不硬凑 5 个(V3 M0 仅 3 个,数量按内容定有先例)。
+
+## 依赖
+
+- 上游:无(M0 是 V5 第一步)。
+- 下游:**被 M1-M4 全部依赖**——每批改完必须过此门;各 brief 的"验证命令"统一引用 M0B 的门命令。
+
+## 不在 M0 范围
+
+- 不改 `content_agent/` 任何业务代码(evaluator/walk_engine/policy_json 等)。
+- 不改 `tests/replay_harness.py`、`tests/replay_clients.py`、`tests/snapshot.py`(纯复用)。
+- 不动两条 corpus 的 input 文件(它们是不可变基准)。
+- 不接学习算法;不新增 DB 表;不动 schema。
+
+## 三条贯穿约束(来自用户)
+
+1. 不做无意义功能:M0 只建标尺,不凑子 brief 数量。
+2. 不改不需改的:回放/快照工具与 corpus 零改。
+3. 将来变动最少:复用现有 `_fingerprint`/`assert_matches` 模式,M1-M4 直接挂门。

+ 105 - 0
tech_documents/工程落地/v5_implementation_briefs/M0/M0A_Golden_Corpus_And_Replay_Baseline.md

@@ -0,0 +1,105 @@
+# M0A 金标准 Corpus 与回放基线 Implementation Brief
+
+> 本 brief 只覆盖:固定两条 V4 金标准 corpus、用 `replay_harness` 离线复现、产出 scorecard 指纹基线快照。回归 test 的挂载与验收口径在 M0B。
+
+## 目标
+
+- 把两条 corpus(抖音三维 / 快手两维)钉成 V5 全程不可变的零变化基准。
+- 复用 `replay_harness.replay_case()` 离线复现(不调真实爬虫/LLM),产出可逐条比对的 scorecard 指纹。
+- 指纹只取确定性打分字段、去掉 run_id/时间戳等易变项,保证可重放稳定。
+
+## 现有证据(file:line)
+
+- corpus 两条已进 git:
+  - `tests/fixtures/cases/v4_douyin_57663/input/`(13 文件,`rule_decisions.jsonl` 110 条决策四分支全:入池20/复看37/技术18/淘汰35;scorecard 三维含 50+)。
+  - `tests/fixtures/cases/v4_kuaishou_57758/input/`(13 文件,`rule_decisions.jsonl` 51 条决策四分支全:入池14/复看13/淘汰20/技术4;scorecard 两维无 50+)。
+- 回放框架 `tests/replay_harness.py`:
+  - `replay_case(case_id, *, runtime_root, cases_dir=CASES_DIR, config_overrides=None, gemini_video_client=None, run_id=None) -> RunArtifacts`(`replay_harness.py:63`)——用 corpus 离线复现一次 run。
+  - `load_corpus(case_id, cases_dir=CASES_DIR)`(`replay_harness.py:44`)——读 corpus input 的 13 个文件。
+  - `RunArtifacts`(`replay_harness.py:30`),含 `.summary`(`:36`,从 `final_output.json` 取 summary)、`.decisions`(`:40`,取 `rule_decisions.jsonl`)。
+  - 回灌不调真实接口:`CorpusPlatformClient`(`tests/replay_clients.py:31`)、`FakeQueryVariantClient`(`replay_harness.py:22`)、`FakeGeminiVideoClient`(`replay_harness.py:21`)。
+- 快照工具 `tests/snapshot.py`:`assert_matches(name, actual, *, subset_keys=None)`(`snapshot.py:55`)——快照缺失时 `UPDATE_SNAPSHOTS=1`(`snapshot.py:24`)生成,偏差报错。
+- 现成指纹模式 `tests/test_walk_profile_degradation.py`:`_fingerprint(walk_actions)`(`:19`,`sorted([...确定性字段...])` 忽略 wa_id/run_id);`test_replay_id45_walk_actions_fingerprint_is_stable`(`:27`,replay→指纹==快照)。基线放 `tests/fixtures/snapshots/real_id45/walk_actions_fingerprint.json`。
+- scorecard 真实结构(`rule_decisions.jsonl` 每条):`scorecard.{schema_version, total_score, query_relevance_score, platform_performance_score, fifty_plus_score, fifty_plus_status, score_thresholds, score_missing, final_status}`;decision 顶层 `{decision_target_id, decision_action, decision_reason_code, score, search_query_effect_status}`。
+
+## 修改范围(只新增,不改现有)
+
+- 新增 `tests/fixtures/snapshots/v4_douyin_57663/scorecard_fingerprint.json` 与 `tests/fixtures/snapshots/v4_kuaishou_57758/scorecard_fingerprint.json`(由 `UPDATE_SNAPSHOTS=1` 首跑生成)。
+- 新增纯函数 `_scorecard_fingerprint(decisions)`(放新 test 模块 `tests/test_v5_golden_corpus_baseline.py`,不污染业务)。
+
+## 不修改范围(红线)
+
+- 不改 `tests/replay_harness.py` / `tests/replay_clients.py` / `tests/snapshot.py`(零改、纯复用)。
+- 不改任何 `content_agent/` 业务代码。
+- 不改两条 corpus 的 input 文件(不可变基准)。
+- 不新增 DB 表 / 不动 schema。
+
+## 涉及文件 / 函数 / 类
+
+- `tests/replay_harness.py`:`replay_case()`(`:63`)、`load_corpus()`(`:44`)、`RunArtifacts`(`:30`)——只调用。
+- `tests/snapshot.py`:`assert_matches()`(`:55`)——只调用。
+- 新增 `tests/test_v5_golden_corpus_baseline.py`:`_scorecard_fingerprint()` + 两个基线 test。
+- 新增快照:`tests/fixtures/snapshots/v4_{douyin_57663,kuaishou_57758}/scorecard_fingerprint.json`。
+
+## 数据合同(指纹结构)
+
+`_scorecard_fingerprint(decisions)` 返回 `sorted([...])`,每条 = 元组(按 `decision_target_id` 稳定排序):
+
+```
+(decision_target_id,
+ decision_action,
+ decision_reason_code,
+ round(score, 2) | None,
+ round(scorecard.query_relevance_score, 2) | None,
+ round(scorecard.platform_performance_score, 2) | None,
+ round(scorecard.fifty_plus_score, 2) | None,
+ scorecard.fifty_plus_status,
+ scorecard.final_status)
+```
+
+- **取确定性打分字段,去掉**:run_id、policy_run_id、时间戳、wa_id、play_url 等易变/已脱敏项。
+- 抖音 case 指纹含 `fifty_plus_*`(三维);快手 case 指纹 `fifty_plus_score=None`/`fifty_plus_status=None`(两维)——两条覆盖两条打分路径。
+
+## 实施步骤(顺序不可颠倒)
+
+1. 写 `_scorecard_fingerprint()`(纯函数,输入 `RunArtifacts.decisions`)。
+2. 写两个基线 test(`replay_case` 两 case → 取 `.decisions` → `assert_matches` 指纹 == 快照)。
+3. `UPDATE_SNAPSHOTS=1 uv run pytest tests/test_v5_golden_corpus_baseline.py` 首跑生成两个 `scorecard_fingerprint.json`。
+4. 人工核对:抖音 110 条、快手 51 条、四种 `decision_action` 都在、分数与国内库一致。
+5. 去掉 `UPDATE_SNAPSHOTS` 再跑一次,确认稳定通过(可重放)。
+
+## 验证命令
+
+```bash
+# 首次生成基线
+UPDATE_SNAPSHOTS=1 uv run pytest tests/test_v5_golden_corpus_baseline.py -q
+# 复跑确认稳定(无 UPDATE,必须绿)
+uv run pytest tests/test_v5_golden_corpus_baseline.py -q
+# 核对指纹条数(期望 110 51)
+python3 -c "import json;print(len(json.load(open('tests/fixtures/snapshots/v4_douyin_57663/scorecard_fingerprint.json'))),len(json.load(open('tests/fixtures/snapshots/v4_kuaishou_57758/scorecard_fingerprint.json'))))"
+```
+
+## Unit Test
+
+- `test_scorecard_fingerprint_is_sorted_and_deterministic` — 同一 decisions 多次调用结果一致;按 `decision_target_id` 排序。
+- `test_scorecard_fingerprint_drops_volatile_fields` — 指纹不含 run_id/时间戳/play_url。
+- `test_scorecard_fingerprint_douyin_has_fifty_plus` — 抖音指纹条目 `fifty_plus_score` 非 None。
+- `test_scorecard_fingerprint_kuaishou_two_dim` — 快手指纹条目 `fifty_plus_score` 为 None(两维)。
+
+## Integrated Test
+
+- `test_v5_douyin_57663_scorecard_baseline` / `test_v5_kuaishou_57758_scorecard_baseline` — `replay_case` 两 case,指纹逐条 == 基线快照(本 brief 建基线,回归门挂载在 M0B)。
+- 暂不要求端到端真实跑(M0 只离线回放;真实跑在 14 号 M3 验收)。
+
+## 失败归因
+
+- 指纹每次不同 → 没去干净易变字段(run_id/时间戳)或排序键不稳。
+- replay 报错 → corpus 文件缺失/损坏,或 `replay_case` 参数错(对照 `tests/test_case_replay.py:81`)。
+- 指纹条数 ≠ 110/51 → corpus 被改过,或 replay 丢了决策。
+- 抖音指纹无 fifty_plus → 取错字段或 corpus 不是三维 case。
+
+## sub-agent 交叉验证要点
+
+- 代码 sub-agent:确认 `_scorecard_fingerprint` 只读 `RunArtifacts`、不碰业务代码;`git diff tests/replay_harness.py tests/snapshot.py` 为空。
+- 数据 sub-agent:抽查指纹里几条 `total_score`/`query_relevance_score`/`platform_performance_score` 与国内库 `content_agent_rule_decisions`(对应 run_id `v1_run_57663_douyin_1782560050` / `v1_run_57758_kuaishou_1782622792`)逐字段一致。
+- JSON sub-agent:两个 `scorecard_fingerprint.json` 无明文 sec_uid/play_url(grep `MS4w`/`http` 应 0);条数 110/51。

+ 77 - 0
tech_documents/工程落地/v5_implementation_briefs/M0/M0B_Zero_Change_Regression_Gate_And_Acceptance.md

@@ -0,0 +1,77 @@
+# M0B 零变化回归门与验收 Implementation Brief
+
+> 本 brief 只覆盖:把 M0A 的指纹基线接成"V5 每批改造的回归门" + 验收命令 + sub-agent 审计口径。不重复 M0A 的指纹生成。
+
+## 目标
+
+- 提供一条命令级回归门:V5 任一批改完,跑它 → 两条 corpus 的 scorecard 指纹必须逐条 == 基线,否则判回归、当场拦下。
+- 明确两类纪律的执行点:"行为中性的批必须零重钉快照"、"改行为的批先贴 diff 经批准再重钉"。
+- 给 M1-M4 每个 brief 统一引用的验收口径。
+
+## 现有证据(file:line)
+
+- M0A 产出:`_scorecard_fingerprint()` + 两个基线快照(`tests/fixtures/snapshots/v4_{douyin_57663,kuaishou_57758}/scorecard_fingerprint.json`)。
+- 现成回归 test 写法:`tests/test_walk_profile_degradation.py:27` `test_replay_id45_walk_actions_fingerprint_is_stable`(`replay_case` → `_fingerprint`(`:19`) → `assert ==` 快照);另 `tests/test_case_replay.py:81` `test_replay_id45_baseline_gemini_score`、`:135` `test_replay_id45_walk_obeys_decisions_after_m4` 是同款回放断言模板。
+- 快照工具语义:`tests/snapshot.py:24` 的 `UPDATE_SNAPSHOTS` 开关决定"生成/比对"两种模式——重钉基线必须显式带 `UPDATE_SNAPSHOTS=1`。
+- 14 号计划纪律:`tech_documents/工程落地/14_V5阶段开发计划.md` §4 受控原则"等价迁移优先 / 改行为先贴 diff";M1-M4 各 brief 验证段均引用此门。
+
+## 修改范围(只新增)
+
+- 在 `tests/test_v5_golden_corpus_baseline.py` 内补"回归门"语义说明 +(可选)两条 case 的 `walk_actions` 指纹基线(复用 `_fingerprint`,锁游走零变化)。
+- 新增一个聚合入口(pytest marker `golden` 或一条 uv 命令),M1-M4 一键跑回归门。
+
+## 不修改范围(红线)
+
+- 不改业务代码;不改 `replay_harness.py`/`snapshot.py`。
+- **不放宽指纹粒度**(必须逐条,不退化成计数 summary)。
+- 不把"改行为的批"偷偷重钉快照绕过门(必须人工贴 diff 确认)。
+
+## 涉及文件 / 函数 / 类
+
+- `tests/test_v5_golden_corpus_baseline.py`(M0A 建,M0B 补回归门 + 可选 walk 指纹)。
+- 可选 `tests/fixtures/snapshots/v4_{douyin_57663,kuaishou_57758}/walk_actions_fingerprint.json`(复用 `tests/test_walk_profile_degradation.py:19` 的 `_fingerprint`)。
+- 可选 `pyproject.toml`(注册 `golden` marker,不改业务)。
+
+## 数据合同(回归门口径)
+
+- 门 = `uv run pytest -m golden`(或指定 test 文件):两条 case 的 `scorecard_fingerprint` 逐条 ==,且(可选)`walk_actions_fingerprint` 逐条 ==。
+- 通过 = 零变化;失败 = 该批触碰了打分/游走行为,必须人工判定:
+  - "误伤" → 修回代码,门重新转绿;
+  - "有意改行为" → 贴 diff + 经用户批准后,用 `UPDATE_SNAPSHOTS=1` 显式重钉基线,commit message 写明原因。
+- **重钉基线只允许在"有意改行为且用户批准"路径**,严禁为了让门变绿而随手重钉。
+
+## 实施步骤
+
+1. 在 baseline test 模块加回归门注释 +(可选)`walk_actions` 指纹两条。
+2. (可选)`pyproject.toml` 注册 `golden` marker。
+3. 跑 `uv run pytest -m golden -q` 确认两 case 全绿。
+4. 在 M1-M4 各 brief 的"验证命令"统一写入这条门命令。
+
+## 验证命令
+
+```bash
+uv run pytest -m golden -q            # 或 uv run pytest tests/test_v5_golden_corpus_baseline.py -q
+# 演练"改坏即拦":临时把 evaluator 某权重改一位小数 → 跑门 → 必须 FAIL(证明门有效)→ 改回 → 门 PASS。
+```
+
+## Unit Test
+
+- `test_golden_gate_fails_on_score_drift` — 用一份"人为改过一条 total_score"的 decisions 喂 `_scorecard_fingerprint` 比对,断言 != 基线(证明门能抓到单条变化)。
+
+## Integrated Test
+
+- `test_v5_douyin_57663_scorecard_baseline` / `test_v5_kuaishou_57758_scorecard_baseline`(M0A)= 回归门主体。
+- (可选)`test_v5_douyin_57663_walk_actions_fingerprint_stable` / `test_v5_kuaishou_57758_walk_actions_fingerprint_stable` — 锁游走零变化。
+- M1-M4 每批改完都要跑这组(在各自 brief 验证段引用)。
+
+## 失败归因
+
+- 门跑过但 V5 改坏没抓到 → 指纹粒度退化成 summary(必须逐条),或漏了某打分字段。
+- 门一直 FAIL 但人没改打分 → 指纹含了易变字段(回 M0A 修指纹),或 corpus/基线被误改。
+- "改行为的批"被误判 → 该批确实有意改打分,应贴 diff、经批准后重钉基线,而非放宽门。
+
+## sub-agent 交叉验证要点
+
+- 代码 sub-agent:确认门只读两条 corpus、零改业务;`git diff content_agent/` 为空。
+- 验收 sub-agent:演练"改一位小数权重 → 门 FAIL → 改回 → 门 PASS",证明门真能拦零变化破坏。
+- 流程 sub-agent:确认重钉基线只在"有意改行为 + 用户批准"路径,commit message 写明原因。