Explorar el Código

docs: renumber V2 modules to sequential M0-M6

Test foundation (was Mt/M-test) -> M0; config infra -> M1; search intent
-> M2; judgment -> M3; walk -> M4; platform -> M5; observability -> M6.
Pure ID renumber via collision-safe placeholder swap; section numbers,
dependency graph, 止血 priority, A→M mapping table, appendices, and the web
plan's CFA references all updated consistently. No content change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sam Lee hace 1 mes
padre
commit
ce37fd8303

+ 61 - 61
tech_documents/工程落地/06_V2阶段开发计划.md

@@ -1,6 +1,6 @@
 # V2 阶段开发计划
 
-状态:V2 迭代输入文档(已评审通过)。第 4–10 节按 `04_V1阶段开发计划.md` 的格式与颗粒度,融合为业务模块(含测试地基模块 V2-Mt)。
+状态:V2 迭代输入文档(已评审通过)。第 4–10 节按 `04_V1阶段开发计划.md` 的格式与颗粒度,融合为业务模块(含测试地基模块 V2-M0)。
 
 更新时间:2026-06-09
 
@@ -37,35 +37,35 @@ V1 已按 P0–P8 打通「Pattern → 抖音 → Content 判断 → 游走 →
 
 ## 3. 模块总览与依赖
 
-V2 主题开发融合为 6 个解耦业务模块(M0–M5)。每个模块独立交付、独立验收,模块间只通过稳定接口(配置 JSON、`RuntimeStore`、`policy_bundle`)耦合。
+V2 主题开发融合为 6 个解耦业务模块(M1–M6)。每个模块独立交付、独立验收,模块间只通过稳定接口(配置 JSON、`RuntimeStore`、`policy_bundle`)耦合。
 
 | 模块 | 名称 | 承接原任务 | 五块需求 | 依赖 |
 |---|---|---|---|---|
-| **V2-Mt** | 真实 case 回放骨架与语料库(测试地基) | 测试策略讨论新增 | ②③④⑤ 的统一验证地基 | 复用 mock / DI 缝(地基) |
-| **V2-M0** | 配置基础设施模块 | V2-A / D / M | ② 档1 配置即真相 | 无(地基) |
-| **V2-M1** | 搜索意图模块(query prompt 配置化) | V2-G / H | ④ prompt 配置化 | M0 |
-| **V2-M2** | 判断规则与策略版本模块 | V2-E / K | ① 欠债2/4、③ 档2 dispatch | M0 |
-| **V2-M3** | 游走策略模块 | V2-B / F | ① 欠债3、③ 档2 binding | M2(rule_pack_id 写回部分);触发过滤可独立 |
-| **V2-M4** | 平台接入模块 | V2-L + 限流 | v1.1 优先级 3/4 | 无 |
-| **V2-M5** | 运行记录与可观测性模块 | V2-C / I / J | ⑤ 可观测性后端 | 无 |
+| **V2-M0** | 真实 case 回放骨架与语料库(测试地基) | 测试策略讨论新增 | ②③④⑤ 的统一验证地基 | 复用 mock / DI 缝(地基) |
+| **V2-M1** | 配置基础设施模块 | V2-A / D / M | ② 档1 配置即真相 | 无(地基) |
+| **V2-M2** | 搜索意图模块(query prompt 配置化) | V2-G / H | ④ prompt 配置化 | M1 |
+| **V2-M3** | 判断规则与策略版本模块 | V2-E / K | ① 欠债2/4、③ 档2 dispatch | M1 |
+| **V2-M4** | 游走策略模块 | V2-B / F | ① 欠债3、③ 档2 binding | M3(rule_pack_id 写回部分);触发过滤可独立 |
+| **V2-M5** | 平台接入模块 | V2-L + 限流 | v1.1 优先级 3/4 | 无 |
+| **V2-M6** | 运行记录与可观测性模块 | V2-C / I / J | ⑤ 可观测性后端 | 无 |
 
-**依赖顺序**:M-test 与 M0 同级先行(两条地基,可并行)→ M1 / M2 / M3 触发过滤可并行 → M3 binding 写回(依赖 M2 dispatch)→ M4 / M5 可并行 → 第 11 节端到端验收。**M-test 是 M2 画像降级 / M3 触发过滤 / M5 可观测三个止血项的前置**:止血改动用「真实 case 回放 + snapshot diff」验证,不再退回「人工真跑一次」。
+**依赖顺序**:M0 与 M1 同级先行(两条地基,可并行)→ M2 / M3 / M4 触发过滤可并行 → M4 binding 写回(依赖 M3 dispatch)→ M5 / M6 可并行 → 第 11 节端到端验收。**M0 是 M3 画像降级 / M4 触发过滤 / M6 可观测三个止血项的前置**:止血改动用「真实 case 回放 + snapshot diff」验证,不再退回「人工真跑一次」。
 
-**止血优先级(不依赖解耦,可最先交付)**:M3 游走触发过滤、M5 decode 可观测、M2 画像 hard gate 降级(三者均以 M-test 回放语料库为验证手段,故 M-test 须最先打通骨架)。
+**止血优先级(不依赖解耦,可最先交付)**:M4 游走触发过滤、M6 decode 可观测、M3 画像 hard gate 降级(三者均以 M0 回放语料库为验证手段,故 M0 须最先打通骨架)。
 
-**完整性映射**(原 V2-A…N 全部落点,无遗漏):A→M0、B→M3、C→M5、D→M0、E→M2、F→M3、G/H→M1、I/J→M5、K→M2、L→M4、M→M0、N→第 11 节;V2-Mt 来自测试策略讨论(非原 A…N 任务)。
+**完整性映射**(原 V2-A…N 全部落点,无遗漏):A→M1、B→M4、C→M6、D→M1、E→M3、F→M4、G/H→M2、I/J→M6、K→M3、L→M5、M→M1、N→第 11 节;V2-M0 来自测试策略讨论(非原 A…N 任务)。
 
 **测试交付约定**:各模块「测试用例」栏的单测 / 契约测在**本模块 PR 内随代码交付**(不滞后);跨模块的多案例真实回放、config×case 矩阵、live smoke 归第 11 节,是**补齐验收**而非首测。
 
-档 3 傻瓜配置体验由 M0(Excel 一键生成)+ M2(业务填 Excel 调画像门槛与评分)+ M0 一致性 CI 共同体现。
+档 3 傻瓜配置体验由 M1(Excel 一键生成)+ M3(业务填 Excel 调画像门槛与评分)+ M1 一致性 CI 共同体现。
 
 ---
 
-## 4. V2-Mt 真实 case 回放骨架与语料库模块(测试地基)
+## 4. V2-M0 真实 case 回放骨架与语料库模块(测试地基)
 
-> 别名 V2-M-test。来自「V2 测试是否应结合真实 case」的策略讨论结论:止血与解耦改动用「对真实案例离线重放 + 留底对照」验证,而非每次人工真跑。本模块与 M0 同为地基,须最先打通骨架。
+> 测试地基模块(旧称 V2-M-test / Mt,本轮统一编号为 M0)。来自「V2 测试是否应结合真实 case」的策略讨论结论:止血与解耦改动用「对真实案例离线重放 + 留底对照」验证,而非每次人工真跑。本模块与 M1(配置基础设施)同为地基,须最先打通骨架。
 
-目标:把上次真实 E2E 的真实接口响应收割成**脱敏 fixture 语料库**,搭一个**回放 harness** 让任意模块对真实案例离线、快速、可重复重放整链,并确立 **snapshot(留底对照)断言风格**,作为 M2 / M3 / M5 止血项的统一验证地基。
+目标:把上次真实 E2E 的真实接口响应收割成**脱敏 fixture 语料库**,搭一个**回放 harness** 让任意模块对真实案例离线、快速、可重复重放整链,并确立 **snapshot(留底对照)断言风格**,作为 M3 / M4 / M6 止血项的统一验证地基。
 
 当前真实状态:
 
@@ -85,7 +85,7 @@ V2 主题开发融合为 6 个解耦业务模块(M0–M5)。每个模块独
 - 脱敏沿用并**扩展** `FORBIDDEN_RAW_PAYLOAD_KEYS`(补 `sec_uid` / `account_id` 等),对**文件语料库同样强制**(补 DB 后端不覆盖的缺口)。
 - snapshot 存 `tests/fixtures/cases/{case_id}/expected/`,按记录类型分文件(rule_decisions / walk_actions / final_output 摘要),断言**关键字段子集**而非整文件 byte(避免易碎)。
 - 脱敏后的语料库与 snapshot **入库**(安全);`runtime/v1/` 仍 git-ignore。
-- **语料库多样性来源(已拍板,解「鸡生蛋」)**:基线用真实(`id=45 全 REJECT`);「入池」「待复看」两种结局用**手工合成案例**(构造带完整画像的内容)立刻凑齐三种结局,**不等 M2、不依赖远端导出**。M2 修好后真实「待复看」自然产生,可逐步用真实案例替换对应合成案例。
+- **语料库多样性来源(已拍板,解「鸡生蛋」)**:基线用真实(`id=45 全 REJECT`);「入池」「待复看」两种结局用**手工合成案例**(构造带完整画像的内容)立刻凑齐三种结局,**不等 M3、不依赖远端导出**。M3 修好后真实「待复看」自然产生,可逐步用真实案例替换对应合成案例。
 
 开工前必须拍板:
 
@@ -93,7 +93,7 @@ V2 主题开发融合为 6 个解耦业务模块(M0–M5)。每个模块独
 
 可先按当前默认值推进:
 
-- harness 与 snapshot helper 先用本地样本 run `v1_run_3649dccd84d8` 跑通骨架;真实多样案例随 M4 真实联调机会性补齐。
+- harness 与 snapshot helper 先用本地样本 run `v1_run_3649dccd84d8` 跑通骨架;真实多样案例随 M5 真实联调机会性补齐。
 
 本阶段不处理:
 
@@ -122,12 +122,12 @@ V2 主题开发融合为 6 个解耦业务模块(M0–M5)。每个模块独
 1. `harvest_case_corpus.py`:先从本地样本 run(`v1_run_3649dccd84d8`)收割打通格式,再接 DB / 远端拉 `id=45`;脱敏(扩展禁词表、对文件语料库强制)。
 2. `replay_harness.py`:用语料库真实响应装配 `Fake*` 假体(platform / decode / category / query),经 DI 缝跑 `start_run`,返回产物。
 3. `snapshot.py`:薄 golden helper(落 / diff / 更新)。
-4. `test_case_replay.py`:先用 1 个案例(id=45 现状基线)锁住「全 REJECT」snapshot;随 M2 改造后该 snapshot 受控更新为「画像缺失→待复看」。
-5. 接 config×case 矩阵骨架(同一 case 过不同 rule_pack / walk / prompt 配置看产物变化),供 M1 / M2 / M3 复用。
+4. `test_case_replay.py`:先用 1 个案例(id=45 现状基线)锁住「全 REJECT」snapshot;随 M3 改造后该 snapshot 受控更新为「画像缺失→待复看」。
+5. 接 config×case 矩阵骨架(同一 case 过不同 rule_pack / walk / prompt 配置看产物变化),供 M2 / M3 / M4 复用。
 
 达到效果:
 
-- M2 / M3 / M5 止血用「对真实案例重放 + snapshot diff」验证,不再人工真跑一次。
+- M3 / M4 / M6 止血用「对真实案例重放 + snapshot diff」验证,不再人工真跑一次。
 - 真实接口响应被脱敏固化,团队可离线、快速、可重复重放上次那条真实链路。
 - 改一处配置 / 规则,snapshot diff 立刻显示对真实案例的产物影响(傻瓜配置的安全网)。
 
@@ -145,13 +145,13 @@ V2 主题开发融合为 6 个解耦业务模块(M0–M5)。每个模块独
 
 - 收割脚本对含 `sec_uid` 的样本脱敏后,禁词校验通过。
 - 回放 id=45 现状语料库 → `decision_action_counts={"REJECT_CONTENT":4}`(基线 snapshot)。
-- M2 改造后回放同语料库 → 画像缺失走 `KEEP_CONTENT_FOR_REVIEW`,snapshot 受控更新。
+- M3 改造后回放同语料库 → 画像缺失走 `KEEP_CONTENT_FOR_REVIEW`,snapshot 受控更新。
 - 同 case 切到「画像门槛放宽」配置 → 入池数变化,snapshot diff 可读。
 - snapshot helper:首次生成 golden;diff 不符即 fail;`--update-snapshots` 重生。
 
 ---
 
-## 5. V2-M0 配置基础设施模块
+## 5. V2-M1 配置基础设施模块
 
 目标:让 Excel 成为业务可改的唯一真相源,一键生成 runtime JSON,一致性 CI 兜底漂移;runtime 行为零变化。
 
@@ -172,14 +172,14 @@ V2 主题开发融合为 6 个解耦业务模块(M0–M5)。每个模块独
 - `openpyxl` 仅作 `[excel]` optional 依赖组,不进 runtime。
 - `config_store` 抽象是纯重构,**不改 `load_policy_bundle` 返回 dict 形状**。
 
-开工前必须拍板(开发到 M0 时定,不阻塞前置模块):
+开工前必须拍板(开发到 M1 时定,不阻塞前置模块):
 
 - **byte-equal 还是语义等值(暗礁)**:转换器输出能否与现有 JSON **字节级相等**有真实风险——`policy_json.py:70` 用原始 JSON 文本算 `policy_bundle_hash`,而扁平 Excel→嵌套 JSON 经 `json.dumps` 后受 `ensure_ascii` / `indent` / key 顺序 / openpyxl 行序影响,未必字节相等。需拍板二选一:① 坚持 byte-equal(则锁定 `json.dumps` 参数 + 冻结 Excel 行号映射 + 一次性把现有 JSON 重写为转换器规范输出);② 放宽为「canonical / 语义等值」(`policy_bundle_hash` 改为对规范化后的结构取,不再依赖原始文本字节)。建议开工先做 round-trip 可行性 spike 再定。
 - **扁平行 ↔ 嵌套数组的映射算法**:`rule_packs[].hard_gates[].when.{field,op,value}`、`scorecard.dimensions[]`、`walk_rule_pack_binding[]`(含 `edge_id` 外键)如何在扁平 sheet 里表达与校验,须随「sheet↔JSON key 对照表」定稿(JSON 嵌套达 8 层,非平凡)。
 
 可先按当前默认值推进:
 
-- M0 完成前 JSON 仍可手改;完成后必须以 Excel→JSON 为唯一生成路径,CI 拦截手改导致的漂移。
+- M1 完成前 JSON 仍可手改;完成后必须以 Excel→JSON 为唯一生成路径,CI 拦截手改导致的漂移。
 
 本阶段不处理:
 
@@ -208,7 +208,7 @@ V2 主题开发融合为 6 个解耦业务模块(M0–M5)。每个模块独
 
 1. 抽 `config_store`:把 `policy_json.py` / `walk_strategy_json.py` 的读文件 + 解析 + 哈希集中,façade 不变(纯重构,现有测试兜底)。
 2. 写 `build_config_from_excel.py`:跳过 `备注:` 哨兵行、末列 `注释` → `.notes`、按「sheet↔JSON key 对照表」生成,输出与 repo JSON byte-equal。
-3. 补三个 validator:规则包 JSON 引用闭环、Excel↔JSON byte-equal 同步、query_prompts 配置(与 M1 协同)。
+3. 补三个 validator:规则包 JSON 引用闭环、Excel↔JSON byte-equal 同步、query_prompts 配置(与 M2 协同)。
 4. 把转换器 `--check` 与三个 validator 串进 CI 闸。
 
 达到效果:
@@ -238,7 +238,7 @@ V2 主题开发融合为 6 个解耦业务模块(M0–M5)。每个模块独
 
 ---
 
-## 6. V2-M1 搜索意图模块(query prompt 配置化)
+## 6. V2-M2 搜索意图模块(query prompt 配置化)
 
 目标:把「用哪个 search term、以什么形式生成 query」的 prompt 从硬编码改为可配置;按 platform / strategy_version 分组,默认零回归。
 
@@ -269,7 +269,7 @@ V2 主题开发融合为 6 个解耦业务模块(M0–M5)。每个模块独
 
 可先按当前默认值推进:
 
-- 现 env 配置继续有效;M1 完成后 prompt 文本与采样参数从 JSON profile 读。
+- 现 env 配置继续有效;M2 完成后 prompt 文本与采样参数从 JSON profile 读。
 
 本阶段不处理:
 
@@ -324,7 +324,7 @@ V2 主题开发融合为 6 个解耦业务模块(M0–M5)。每个模块独
 
 ---
 
-## 7. V2-M2 判断规则与策略版本模块
+## 7. V2-M3 判断规则与策略版本模块
 
 目标:dispatch 按 `target_entity` 参数化(解耦的根);画像缺失从 fatal 淘汰降级为待复看;scorecard 评分细则标定。
 
@@ -344,19 +344,19 @@ V2 主题开发融合为 6 个解耦业务模块(M0–M5)。每个模块独
 - dispatch 去 Content 硬编码,改 `select_dispatches(target_entity=...)`;`len(matches)` 约束改为「每个 entity 内恰好 1 个」(保留单包护栏),允许跨 entity 多个。
 - `load_policy_bundle` 新增 `rule_pack_by_entity` 映射,**保留顶层 `dispatch` / `target_entity` 指向 Content**(兼容 `evaluator` / `run_service` / `test_policy_dispatch`)。
 - 画像缺失不再 fatal REJECT:`missing_content_portrait` / `age_50_plus_weak` 在画像「缺失」时降级为 `KEEP_CONTENT_FOR_REVIEW`(走低预算),不直接淘汰(与第 2 节「待复看走低预算」一致)。
-- scorecard `scoring_rules` / `thresholds` 经 M0 由 Excel 维护标定;机制不动,数值业务填。
+- scorecard `scoring_rules` / `thresholds` 经 M1 由 Excel 维护标定;机制不动,数值业务填。
 - `evaluator` 区分「单维度缺失」与「全 scorecard 缺失」(当前 `not matched_scoring_rules` 触发 missing_score 太激进)。
 - 不做多包决策合并(V3);future 包仍 `dispatch_enabled=false`。
 - **解耦真伪防假成功(已拍板)**:顶层 dispatch 保留指向 Content 的兼容 shim(避免大改 `evaluator` / `run_service` / 测试),但**加一条非 Content 反证测试**——测试内临时启用一个非 Content(如 Author)dispatch,强制其走按 entity 分流并选到 Author 包,证明分流机制真生效、未回落 Content。
 
-开工前必须拍板(开发到 M2 时定):
+开工前必须拍板(开发到 M3 时定):
 
 - 画像 `missing` 与 `weak` 是否区别对待。建议:`missing`→待复看;`weak`→进 scorecard 按分;最终阈值由业务在 Excel 定。注意:现 `age_50_plus_weak` 把 `["weak","missing"]` 合成一条 gate(`douyin_rule_packs.v1.json`),要分裂行为须拆 gate 或在 `evaluator` 加 field-value 分支。
 - **dispatch「每 entity 最多 1 个」护栏的落点**:`policy_json.py:80-93` 现按 `target_entity=="Content"` 硬编码 + `len(matches)!=1` raise。参数化后该护栏放在 `_select_dispatch()` / `load_policy_bundle()` / `evaluator` 哪一层、以及多 entity 同时 enabled 时的报错口径,须在开发到该步时定。(顶层 dispatch 兼容 shim 掩盖解耦真伪的风险已拍板:**保留 shim + 加一条非 Content 反证测试**,见「已拍板」与「测试用例」。)
 
 可先按当前默认值推进:
 
-- 现 Content dispatch 继续;M2 完成前画像缺失仍按现行 REJECT(应尽快改,属止血项)。
+- 现 Content dispatch 继续;M3 完成前画像缺失仍按现行 REJECT(应尽快改,属止血项)。
 
 本阶段不处理:
 
@@ -368,7 +368,7 @@ V2 主题开发融合为 6 个解耦业务模块(M0–M5)。每个模块独
 
 - `content_agent/integrations/policy_json.py`
 - `content_agent/business_modules/rule_judgment/evaluator.py`
-- `product_documents/规则包/douyin_rule_packs.v1.json`(经 M0 由 Excel 维护)
+- `product_documents/规则包/douyin_rule_packs.v1.json`(经 M1 由 Excel 维护)
 
 主要函数 / 对象:
 
@@ -386,7 +386,7 @@ V2 主题开发融合为 6 个解耦业务模块(M0–M5)。每个模块独
 
 达到效果:
 
-- dispatch 不再写死 Content,可按 entity 取包(为 M3 的 binding 写回铺路)。
+- dispatch 不再写死 Content,可按 entity 取包(为 M4 的 binding 写回铺路)。
 - 画像缺失的内容进待复看而非全淘汰,真实 E2E 不再 4/4 REJECT。
 - 评分细则业务可在 Excel 调,不改代码。
 
@@ -412,7 +412,7 @@ V2 主题开发融合为 6 个解耦业务模块(M0–M5)。每个模块独
 
 ---
 
-## 8. V2-M3 游走策略模块
+## 8. V2-M4 游走策略模块
 
 目标:游走触发严格按规则决策过滤;`walk_rule_pack_binding` 按边选包并写回 `walk_actions.rule_pack_id`,统一两个 walk_action writer。
 
@@ -436,17 +436,17 @@ V2 主题开发融合为 6 个解耦业务模块(M0–M5)。每个模块独
 - 作者 / tag 边按 `decision_action` 过滤:`ADD_TO_CONTENT_POOL` 正常 / `KEEP_CONTENT_FOR_REVIEW` 走低预算 `budget_downgrade` / `REJECT_CONTENT`、`rule_blocked` 不扩。
 - `walk_rule_pack_binding` 按 edge 1:1 选包,写回 `walk_actions.rule_pack_id`;以 binding 为统一真相源,修正两 writer 不一致。
 - walk_strategy edge→pack 归属修正:`path_stop`→Path、`budget_downgrade`→Budget、`decision_to_asset`→Content。
-- future 包 `dispatch_enabled=false` 时只写「归属」不跑其 evaluator(依赖 M2 的按 entity dispatch)。
+- future 包 `dispatch_enabled=false` 时只写「归属」不跑其 evaluator(依赖 M3 的按 entity dispatch)。
 - 不做多包叠加(V3)。
 
-开工前必须拍板(开发到 M3 时定):
+开工前必须拍板(开发到 M4 时定):
 
 - **future 包(`dispatch_enabled=false`)的 `rule_pack_id` 写法**:`KEEP_CONTENT_FOR_REVIEW → budget_downgrade` 边绑定 Budget 包(`dispatch_enabled=false`),而 `walk_strategy.py:162` 写的 `decision["rule_pack_id"]` 来自 Content 评估。须拍板:该边 `walk_actions.rule_pack_id` 写 binding 的归属包(Budget)、写 NULL、还是跳过——即「归属包」与「实际跑的包」如何分别落库。
 - `KEEP_CONTENT_FOR_REVIEW` 走低预算扩展已拍板。
 
 可先按当前默认值推进:
 
-- M3 触发过滤(分页 / 作者 / tag)可先于 binding 写回交付,属止血项;`rule_pack_id` 写回依赖 M2 的 dispatch 参数化。
+- M4 触发过滤(分页 / 作者 / tag)可先于 binding 写回交付,属止血项;`rule_pack_id` 写回依赖 M3 的 dispatch 参数化。
 
 本阶段不处理:
 
@@ -502,7 +502,7 @@ V2 主题开发融合为 6 个解耦业务模块(M0–M5)。每个模块独
 
 ---
 
-## 9. V2-M4 平台接入模块
+## 9. V2-M5 平台接入模块
 
 目标:修 Crawapi 作者作品端点与参数;keyword 12 秒限流;分类树 v2 parser 稳定可测。
 
@@ -524,13 +524,13 @@ V2 主题开发融合为 6 个解耦业务模块(M0–M5)。每个模块独
 - 限流命中区分 `PLATFORM_RATE_LIMITED`(HTTP 429 / 业务 `code=22001`),不混入通用 `PLATFORM_REQUEST_FAILED`。
 - 分类树 v2 响应 `items[].matches[].path` / `matched_paths` 解析补测;文档明确 v2 为当前事实,旧 `data` 结构仅历史兼容。
 
-开工前必须拍板(开发到 M4 时定,且为阻塞项):
+开工前必须拍板(开发到 M5 时定,且为阻塞项):
 
 - **Crawapi `/blogger` 完整字段合同**(endpoint path、参数名、author id 映射、account id 格式)。这不只是测试建议而是**阻塞项**:`douyin.py:96` 现打到 `keyword_path`、`from_env`(L44-63)无 blogger path、参数映射未定,作者边在合同固定前无法实现。建议先做一次真实 smoke 固定参数名,再改代码。
 
 可先按当前默认值推进:
 
-- keyword 搜索继续可用;作者边在 M4 修好前可能空 / 异常,但 M3 已先按规则决策过滤,减少无效触发。
+- keyword 搜索继续可用;作者边在 M5 修好前可能空 / 异常,但 M4 已先按规则决策过滤,减少无效触发。
 
 本阶段不处理:
 
@@ -584,7 +584,7 @@ V2 主题开发融合为 6 个解耦业务模块(M0–M5)。每个模块独
 
 ---
 
-## 10. V2-M5 运行记录与可观测性模块
+## 10. V2-M6 运行记录与可观测性模块
 
 目标:补齐全流程细粒度日志(每环节耗时、decode 逐条进度、卡点),增强 timeline API;只做后端 + API,不写前端。
 
@@ -617,7 +617,7 @@ V2 主题开发融合为 6 个解耦业务模块(M0–M5)。每个模块独
 
 可先按当前默认值推进:
 
-- 现 timeline 继续可用;M5 为增量增强,不破坏现有 dashboard 输出。
+- 现 timeline 继续可用;M6 为增量增强,不破坏现有 dashboard 输出。
 
 本阶段不处理:
 
@@ -649,7 +649,7 @@ V2 主题开发融合为 6 个解耦业务模块(M0–M5)。每个模块独
 1. `graph` 节点计时装饰器,产出每节点 `duration_ms` / `stage`。
 2. `recorder` 事件加 duration / stage / stall 字段(进 `raw_payload`)。
 3. `decode` 加 `event_sink` 中间事件 + 短等待档注释 + **最小补跑**(单条超时落 `pending`、后台轮询续跑、不阻塞整条 run)。
-4. `errors` 加 `PLATFORM_RATE_LIMITED`(与 M4 协同)。
+4. `errors` 加 `PLATFORM_RATE_LIMITED`(与 M5 协同)。
 5. `timeline` 聚合 `duration` / `stalls` / `total_duration_ms`。
 6. 卡点标记(decode 慢 / 限流 / 高失败率)。
 
@@ -681,14 +681,14 @@ V2 主题开发融合为 6 个解耦业务模块(M0–M5)。每个模块独
 
 ## 11. 端到端验收(跨模块)
 
-> 端到端验收是**补齐验收,不是首测**。各模块「测试用例」栏的单测 / 契约测随**本模块 PR** 交付;M-test 的**真实 case 回放**在 M2 / M3 / M5 开发期就用于止血验证。本节只做三件早期给不了的事:跨模块汇总回归、案例多样性、真连接口探漂移。
+> 端到端验收是**补齐验收,不是首测**。各模块「测试用例」栏的单测 / 契约测随**本模块 PR** 交付;M0 的**真实 case 回放**在 M3 / M4 / M6 开发期就用于止血验证。本节只做三件早期给不了的事:跨模块汇总回归、案例多样性、真连接口探漂移。
 
-- **全量回归**:`uv run pytest -q`:188 基线全绿 + 各模块随 PR 落地的新测试(M3 触发用例;M2 / M3 `rule_pack_id` 写回非 NULL 且按边正确、画像缺失走 review;M1 默认零回归;M4 blogger / v2 parser;M5 duration / decode 中间事件;M-test 回放 + snapshot)。
+- **全量回归**:`uv run pytest -q`:188 基线全绿 + 各模块随 PR 落地的新测试(M4 触发用例;M3 / M4 `rule_pack_id` 写回非 NULL 且按边正确、画像缺失走 review;M2 默认零回归;M5 blogger / v2 parser;M6 duration / decode 中间事件;M0 回放 + snapshot)。
 - **CI 闸全过**:`validate_walk_strategy_config`、`validate_schema_registry`、`validate_content_agent_db`、`check_naming_standards` + 新 `validate_rule_pack_config` / `validate_config_excel_sync` / `validate_query_prompts_config` + `build_config_from_excel.py --check`。
-- **多案例语料库回放(M-test 语料库,离线、无外网)**:至少 3 个结局各一例——全 REJECT(id=45 真实基线)、含 ADD 入池、含 KEEP 待复看(入池 / 待复看初期用**合成案例**,真实案例机会性替换)——重放并以 snapshot 校验关键产物(`decision_action_counts`、`walk_actions.rule_pack_id` 按边、画像缺失分流、timeline `duration_ms`)。
+- **多案例语料库回放(M0 语料库,离线、无外网)**:至少 3 个结局各一例——全 REJECT(id=45 真实基线)、含 ADD 入池、含 KEEP 待复看(入池 / 待复看初期用**合成案例**,真实案例机会性替换)——重放并以 snapshot 校验关键产物(`decision_action_counts`、`walk_actions.rule_pack_id` 按边、画像缺失分流、timeline `duration_ms`)。
 - **config × case 矩阵**:同一案例过不同配置(默认包 / 放宽画像门槛 / 自定义 query profile / future 包归属),验证解耦——改配置只改产物、不崩链路、snapshot diff 可读。
 - **live smoke(真连一次,探上游漂移)**:`demand_content.id=45`、`seed_terms=["中医养生"]`、短等待 `MAX_WAIT=60` 真连 Crawapi / decode / 分类树跑一次,专抓「回放语料库照过、生产却炸」的接口契约漂移(抖音风控、分类树 v2 结构变更);**非每次 CI,收尾 + 定期手触**。验收点:初始 2 query 不错翻页、rejected 不扩边、`walk_actions.rule_pack_id` 非 NULL 按边正确、画像缺失 → 待复看不再全 REJECT、timeline 有 duration + 卡点、DB 不残留 `running`。
-- **诚实限制**:多样真实案例无法全提前——真连接口慢 / 不稳 / 需凭证,只能随真实联调机会性收割,收尾凑够 3–5 条做对照(已记于 M-test「可先按当前默认值推进」)。
+- **诚实限制**:多样真实案例无法全提前——真连接口慢 / 不稳 / 需凭证,只能随真实联调机会性收割,收尾凑够 3–5 条做对照(已记于 M0「可先按当前默认值推进」)。
 
 ## 12. 明确不做(V3 留白)
 
@@ -740,9 +740,9 @@ pooled_content_count = 0
 rejected_content_count = 4
 ```
 
-主因:4 条内容均缺内容画像,命中 `missing_content_portrait`(priority 50,fatal + stop_scoring)hard gate,进不到 scorecard。→ V2-M2
+主因:4 条内容均缺内容画像,命中 `missing_content_portrait`(priority 50,fatal + stop_scoring)hard gate,进不到 scorecard。→ V2-M3
 
-### A.2 AIGC decode 是当前真实链路最大耗时点(→ V2-M5
+### A.2 AIGC decode 是当前真实链路最大耗时点(→ V2-M6
 
 当前实现是串行处理视频解构:每条视频 `submit_decode → 轮询 get_decode_result → success / failed / timeout` 后才进入下一条。`AigcDecodeClient.submit_decode()` 请求体虽是 `posts` 数组,但每次只放 1 条。
 
@@ -758,9 +758,9 @@ CONTENTFIND_PATTERN_RECALL_POLL_INTERVAL_SECONDS=5
 
 结果 `pattern_recall_evidence = 4`、`recall_status_counts = {"matched": 1, "pending": 3}`。
 
-V2-M5 处理(已拍板):保留短等待调测模板(60–120s);增加 decode submit / poll 中间事件逐条进度落盘;做**最小补跑 / 不阻塞**(单条超时落 `pending`、后台续跑,不被串行墙卡死);完整批量 / 并发提交留 V3。
+V2-M6 处理(已拍板):保留短等待调测模板(60–120s);增加 decode submit / poll 中间事件逐条进度落盘;做**最小补跑 / 不阻塞**(单条超时落 `pending`、后台续跑,不被串行墙卡死);完整批量 / 并发提交留 V3。
 
-### A.3 P6 游走触发条件与业务策略不一致(→ V2-M3
+### A.3 P6 游走触发条件与业务策略不一致(→ V2-M4
 
 既定业务策略:只有有效 query 才能翻页;只有可信内容或明确允许的小预算待复看内容才能继续作者 / tag 扩展;`REJECT_CONTENT` / `rule_blocked` 只能 path_stop。
 
@@ -768,13 +768,13 @@ V2-M5 处理(已拍板):保留短等待调测模板(60–120s);增
 
 原因:`douyin_walk_strategy.v1.json` 写明 `query_next_page` 需要 `search_query_effect_status=success`,`05_阶段验收清单.md` 也写明只在 `success + has_more + next_cursor` 时生成,但 `walk_engine.py::_pagination_queries()` 实际只检查 `has_more` 和 `next_cursor`。同类问题:`_execute_author_edges()` 从 `discovered_content_items` 直接取作者,未按 `rule_decisions` 过滤,真实 run 中从已被规则淘汰的内容作者尝试 `author_to_works`,结果 `failed / RuntimeError`。
 
-V2-M3 修复:`query_next_page` 只允许 `success`;`author_to_works` 默认只允许 `ADD_TO_CONTENT_POOL`,`KEEP_CONTENT_FOR_REVIEW` 走低预算扩展(已拍板),`REJECT_CONTENT` / `rule_blocked` 不扩;`tag_query` 同样过滤;补 4 个测试用例。
+V2-M4 修复:`query_next_page` 只允许 `success`;`author_to_works` 默认只允许 `ADD_TO_CONTENT_POOL`,`KEEP_CONTENT_FOR_REVIEW` 走低预算扩展(已拍板),`REJECT_CONTENT` / `rule_blocked` 不扩;`tag_query` 同样过滤;补 4 个测试用例。
 
-### A.4 Crawapi 作者作品参数映射需复核(→ V2-M4
+### A.4 Crawapi 作者作品参数映射需复核(→ V2-M5
 
-抖音 keyword 搜索曾短暂返回 `code=22001 / msg=抖音搜索异常: 强制登录`,后续已恢复。作者作品接口 `/crawler/dou_yin/blogger` 手动实测显示:作者 `sec_uid` 放入 `account_id` 时可返回作品;当前代码 `fetch_author_works()`(`douyin.py:89-110`)第 96 行**打到 `self.keyword_path`(keyword 端点)而非 blogger 端点,根本没有 blogger path 配置**,且把默认 Crawapi 账号作为 `account_id`、作者 id 放 `sec_uid`,真实接口可能返回空或异常。V2-M4 用真实接口固定 `/blogger` 字段合同并更新参数映射。
+抖音 keyword 搜索曾短暂返回 `code=22001 / msg=抖音搜索异常: 强制登录`,后续已恢复。作者作品接口 `/crawler/dou_yin/blogger` 手动实测显示:作者 `sec_uid` 放入 `account_id` 时可返回作品;当前代码 `fetch_author_works()`(`douyin.py:89-110`)第 96 行**打到 `self.keyword_path`(keyword 端点)而非 blogger 端点,根本没有 blogger path 配置**,且把默认 Crawapi 账号作为 `account_id`、作者 id 放 `sec_uid`,真实接口可能返回空或异常。V2-M5 用真实接口固定 `/blogger` 字段合同并更新参数映射。
 
-### A.5 分类树 v2 响应结构已变化(→ V2-M4
+### A.5 分类树 v2 响应结构已变化(→ V2-M5
 
 真实 endpoint `https://library.aiddit.com/api/search/categories/match-paths/v2` 返回结构:
 
@@ -785,13 +785,13 @@ V2-M3 修复:`query_next_page` 只允许 `success`;`author_to_works` 默认
       "matched_paths": [] } ] }
 ```
 
-不是旧的 `data` 顶层结构。V2-M4 补 `items[].matches[].path` 与 `items[].matched_paths` 解析测试,并确认 `match_decode_terms()` 输出与 Pattern 回扣合同一致;文档明确 v2 为当前事实,旧 `data` 仅历史兼容。
+不是旧的 `data` 顶层结构。V2-M5 补 `items[].matches[].path` 与 `items[].matched_paths` 解析测试,并确认 `match_decode_terms()` 输出与 Pattern 回扣合同一致;文档明确 v2 为当前事实,旧 `data` 仅历史兼容。
 
-### A.6 抖音关键词搜索限流保护(→ V2-M4
+### A.6 抖音关键词搜索限流保护(→ V2-M5
 
 抖音关键词搜索接口限流 10 秒 1 次。为保护接口并降低风控概率,CFA 运行时最少间隔按 12 秒 1 次执行;`query_next_page`、`tag_query` 等所有复用 keyword 搜索的入口都必须遵守同一间隔。
 
-## 附录 B:规则包 dispatch 现状与「按边叠加」差距(并自 v1.1 §6 → V2-M2 / V2-M3
+## 附录 B:规则包 dispatch 现状与「按边叠加」差距(并自 v1.1 §6 → V2-M3 / V2-M4
 
 回答三个问题:现在有几个规则包、怎么应用到每条需要的边上、能否多个规则包自由叠加。结论基于真实代码与生产 DB 核查。
 
@@ -817,6 +817,6 @@ douyin_budget_observe_rule_pack_v1      Budget     2 hard gates  无 scorecard
 
 架构愿景支持叠加(ADR 0001、`PolicyBundle` 可组装 `RulePackVersion[]`),但 `_select_dispatch` 的 `len(matches)!=1: raise` 强制单包,`fallback_policy=fail_if_not_matched_or_multi_matched` 也明说多匹配即失败。当前是「一个实体一个包,且全局只真正跑 Content 一个包」。
 
-### B.4 V2 改造(= V2-M2 + V2-M3
+### B.4 V2 改造(= V2-M3 + V2-M4
 
-V2-M2 去 `_select_dispatch` 的 Content 硬编码、按 `target_entity` 参数化,放开单包强约束为「每实体恰好 1 个」;V2-M3 让 `walk_engine` 真正读 `walk_rule_pack_binding` 按边 dispatch 并写回 `walk_actions.rule_pack_id`;逐步启用 future 包。**多包决策合并留 V3**。
+V2-M3 去 `_select_dispatch` 的 Content 硬编码、按 `target_entity` 参数化,放开单包强约束为「每实体恰好 1 个」;V2-M4 让 `walk_engine` 真正读 `walk_rule_pack_binding` 按边 dispatch 并写回 `walk_actions.rule_pack_id`;逐步启用 future 包。**多包决策合并留 V3**。

+ 8 - 8
web/docs/03_技术开发规划.md

@@ -372,7 +372,7 @@ web/
 
 ## Web V2 迭代规划(消费 CFA V2 可观测性与配置即真相)
 
-> 本节为 Web 的 V2 轮规划,对应后端 `tech_documents/工程落地/06_V2阶段开发计划.md` 的 V2-M5(可观测性)/ M0·M2·M3(配置即真相 + 按边 `rule_pack_id`)/ M-test(真实 case 回放)。**后端 V2 只产出数据与 API,前端 V2 在此独立迭代**(由 web 项目单独排期)。CFA V2 锁定范围:单平台(抖音)单包(Content)跑通「解耦机制 + 配置即真相 + 可观测」;配置可视化「编辑」UI 不在 V2(含 web V2)。
+> 本节为 Web 的 V2 轮规划,对应后端 `tech_documents/工程落地/06_V2阶段开发计划.md` 的 V2-M6(可观测性)/ M1·M3·M4(配置即真相 + 按边 `rule_pack_id`)/ M0(真实 case 回放)。**后端 V2 只产出数据与 API,前端 V2 在此独立迭代**(由 web 项目单独排期)。CFA V2 锁定范围:单平台(抖音)单包(Content)跑通「解耦机制 + 配置即真相 + 可观测」;配置可视化「编辑」UI 不在 V2(含 web V2)。
 
 ### 现状对齐(与本文档第一版的差异)
 
@@ -383,7 +383,7 @@ web/
 - 即 Web-0…Web-5 的「骨架 + 基础消费」事实上已落地;Web V2 聚焦三块新消费。
 - **依赖关系**:Web V2 各项依赖 CFA V2 对应模块先落地(新增字段为增量,旧消费不破坏);CFA 未交付前,adapter 对缺字段降级为空状态,不白屏。
 
-### Web-V2-A:全流程时间线与卡点可视化(消费 CFA V2-M5
+### Web-V2-A:全流程时间线与卡点可视化(消费 CFA V2-M6
 
 职责:
 
@@ -393,26 +393,26 @@ web/
 
 依赖接口:
 
-- `GET /runs/{run_id}/timeline`(M5 后新增 `stage` / `started_at` / `ended_at` / `duration_ms` / `attempt` / `stall_flag` / `stall_reason` / `total_duration_ms` / `stalls[]`;这些字段当前代码均不存在,由 CFA V2-M5 补,属增量)。
+- `GET /runs/{run_id}/timeline`(M6 后新增 `stage` / `started_at` / `ended_at` / `duration_ms` / `attempt` / `stall_flag` / `stall_reason` / `total_duration_ms` / `stalls[]`;这些字段当前代码均不存在,由 CFA V2-M6 补,属增量)。
 
-### Web-V2-B:配置即真相只读视图(消费 CFA V2-M0 / M2 / M3
+### Web-V2-B:配置即真相只读视图(消费 CFA V2-M1 / M3 / M4
 
 职责:
 
 - 展示当前生效的规则包(Content 包 hard gates / scorecard / thresholds)与游走 `walk_rule_pack_binding`(边→包)。
-- 在 walk 视图按边展示 `walk_actions.rule_pack_id`(M3 后非 NULL 按边正确),让「哪条边跑了哪个包」可见;future 包(`dispatch_enabled=false`)标注为「已归属未运行」。
+- 在 walk 视图按边展示 `walk_actions.rule_pack_id`(M4 后非 NULL 按边正确),让「哪条边跑了哪个包」可见;future 包(`dispatch_enabled=false`)标注为「已归属未运行」。
 
 依赖接口:
 
 - `GET /runs/{run_id}/content-items` + `runtime-files/rule_decisions.jsonl`(已存在)。
-- `runtime-files/walk_actions.jsonl`(已存在;`rule_pack_id` 字段由 CFA V2-M3 补正)。
+- `runtime-files/walk_actions.jsonl`(已存在;`rule_pack_id` 字段由 CFA V2-M4 补正)。
 - 建议后端补一个只读「当前配置」摘要接口(暴露 rule_pack / binding 概要),避免前端直接解析 JSON 源文件。
 
-### Web-V2-C:真实 case 回放 diff 视图(消费 CFA V2-M-test
+### Web-V2-C:真实 case 回放 diff 视图(消费 CFA V2-M0
 
 职责:
 
-- 对接 M-test 语料库回放:同一 case、不同配置(config × case 矩阵)的产物对照。
+- 对接 M0 语料库回放:同一 case、不同配置(config × case 矩阵)的产物对照。
 - 复用现有 `data_origin` badge 区分「生产事实 / 回放导出」。
 - 展示 snapshot diff(改一档配置后入池 / 淘汰 / `rule_pack_id` 的变化)。