Parcourir la source

docs: update V5 implementation briefs

Sam Lee il y a 1 mois
Parent
commit
6aa050e91c
28 fichiers modifiés avec 4045 ajouts et 165 suppressions
  1. 148 59
      tech_documents/工程落地/14_V5阶段开发计划.md
  2. 8 7
      tech_documents/工程落地/v5_implementation_briefs/M0/00_M0_Brief_Index.md
  3. 115 65
      tech_documents/工程落地/v5_implementation_briefs/M0/M0A_Golden_Corpus_And_Replay_Baseline.md
  4. 57 34
      tech_documents/工程落地/v5_implementation_briefs/M0/M0B_Zero_Change_Regression_Gate_And_Acceptance.md
  5. 156 0
      tech_documents/工程落地/v5_implementation_briefs/M1/00_M1_Brief_Index.md
  6. 127 0
      tech_documents/工程落地/v5_implementation_briefs/M1/M1A_Profile_Edge_Status_And_Four_State_Contract.md
  7. 137 0
      tech_documents/工程落地/v5_implementation_briefs/M1/M1B_Profile_Normalization_And_All_Expansion.md
  8. 130 0
      tech_documents/工程落地/v5_implementation_briefs/M1/M1C_Walk_Edge_Catalog_And_Strategy_Decoupling.md
  9. 128 0
      tech_documents/工程落地/v5_implementation_briefs/M1/M1D_Rule_Pack_Dimension_Validator_Relaxation.md
  10. 156 0
      tech_documents/工程落地/v5_implementation_briefs/M1/M1E_Acceptance_Commands_And_Subagent_Audit.md
  11. 163 0
      tech_documents/工程落地/v5_implementation_briefs/M2/00_M2_Brief_Index.md
  12. 123 0
      tech_documents/工程落地/v5_implementation_briefs/M2/M2A_Policy_Bundle_Platform_Dispatch_Contract.md
  13. 132 0
      tech_documents/工程落地/v5_implementation_briefs/M2/M2B_Douyin_Rule_Pack_Standard_Scorecard_Contract.md
  14. 129 0
      tech_documents/工程落地/v5_implementation_briefs/M2/M2C_Config_Driven_V4_Score_Total_Contract.md
  15. 128 0
      tech_documents/工程落地/v5_implementation_briefs/M2/M2D_Validation_And_Flow_Ledger_Weight_Parity.md
  16. 187 0
      tech_documents/工程落地/v5_implementation_briefs/M2/M2E_Acceptance_Commands_And_Subagent_Audit.md
  17. 173 0
      tech_documents/工程落地/v5_implementation_briefs/M3/00_M3_Brief_Index.md
  18. 126 0
      tech_documents/工程落地/v5_implementation_briefs/M3/M3A_Three_Platform_Dispatch_And_Rule_Pack_Config.md
  19. 138 0
      tech_documents/工程落地/v5_implementation_briefs/M3/M3B_Kuaishou_Author_Works_Client_Contract.md
  20. 141 0
      tech_documents/工程落地/v5_implementation_briefs/M3/M3C_Profile_Edge_And_Walk_Runtime_Alignment.md
  21. 140 0
      tech_documents/工程落地/v5_implementation_briefs/M3/M3D_Source_Evidence_Registry_And_Capture_Reconciliation.md
  22. 184 0
      tech_documents/工程落地/v5_implementation_briefs/M3/M3E_Acceptance_Commands_And_Subagent_Audit.md
  23. 212 0
      tech_documents/工程落地/v5_implementation_briefs/M4/00_M4_Brief_Index.md
  24. 152 0
      tech_documents/工程落地/v5_implementation_briefs/M4/M4A_Excel_Runtime_Governance_And_Converter_Structure.md
  25. 194 0
      tech_documents/工程落地/v5_implementation_briefs/M4/M4B_Score_Weight_And_Threshold_Profile_Matrix.md
  26. 167 0
      tech_documents/工程落地/v5_implementation_briefs/M4/M4C_Walk_Budget_And_Platform_Edge_Matrix.md
  27. 182 0
      tech_documents/工程落地/v5_implementation_briefs/M4/M4D_Fake_Feedback_Join_And_Learning_Guardrail.md
  28. 212 0
      tech_documents/工程落地/v5_implementation_briefs/M4/M4E_Dead_Config_Archive_Acceptance_And_Subagent_Audit.md

+ 148 - 59
tech_documents/工程落地/14_V5阶段开发计划.md

@@ -11,13 +11,13 @@
 
 | 问题 | 真实证据 | 对策 |
 |---|---|---|
-| 只有抖音一本规则包,程序写死只翻抖音 | `policy_json.py:17`(路径写死)、`:95`(默认 platform=douyin)、`:121/154`(字面量 ==douyin) | 规则包按平台选(M2) |
-| 打分配比焊死且 3 处副本 | `evaluator.py:226/239/242`、`run_record/validation.py:969/971/973`、`flow_ledger_service.py:1500/1506` | 搬进每平台手册,三处联动(M2) |
+| 多平台规则包机制已通,但只接了抖音 dispatch | `policy_json.py:16/28/122-131/154-173` 已按 platform 派发;`douyin_rule_packs.v1.json:21-35` 仍只有 `platform=douyin` | M3 补快手/视频号 dispatch |
+| 打分权重已搬手册,但运营治理还没进 Excel 大矩阵 | `evaluator.py:274-308` 读 `score_weight_profiles`;`douyin_rule_packs.v1.json:348-362` 已有三套运行权重 | M4 把规则/权重/平台能力纳入 Excel 全平台矩阵 |
 | 评分维度被离线校验锁死恰两维 | `validate_rule_pack_config.py:99`、`validate_v4_config_contract.py:470` | 放开支持可变维度集(M1) |
 | 游走只认两态,非抖快视平台一跑即崩 | `walk_graph_json.py:156` 只认 supported/blocked;bilibili/youtube/weixin/github 写 unsupported/weak、toutiao/zhihu 写 `_all` | 扩四态 + 规范化(M1) |
 | 边目录绑死抖音 | `walk_strategy_json.py:87` 硬断言 strategy_id=douyin_walk_strategy_v1;`walk_graph_json.py:19` EDGE_CATALOG_PATH 抖音文件 | 解耦成平台无关总章程(M1) |
-| 小红书/公众号无 client | `content_agent/integrations/` 仅 douyin/kuaishou/shipinhao | 新建 client(M3) |
-| 学习算法要调权重,但权重焊代码、改要发版 | 同打分配比焊死 | 把权重/门槛搬进可写配置铺路(M2/M4) |
+| V5 运行平台需收口 | `run_service.py:568-577` real 模式只注册 douyin/kuaishou/shipinhao;国内库当前仅抖音/快手历史 run,`discovered_content_items` 均为 video | M3 只补三短视频平台规则与游走,不接图文/图片模态 |
+| 学习算法要调权重,但运营入口与回流闭环未完成 | 权重已配置化,但还未进入 Excel 全平台矩阵;`performance_feedback` 仍无写入器 | M4 做 Excel 治理与 fake 回流 join |
 
 ---
 
@@ -25,21 +25,21 @@
 
 | 子系统 | V5 要做 | V5 不做 |
 |---|---|---|
-| 打分顶层公式 | 维度集+权重搬进每平台手册;evaluator 只读不算 | 不改非抖音 0.5/0.5 行为(快照零变化);不改 AI 出契合分 |
+| 打分顶层公式 | 维度集+权重搬进每平台手册;evaluator 只读不算 | 不改非抖音 0.5/0.5 行为(金标准门零变化);不改 AI 出契合分 |
 | 评分卡 schema | 固定两维→按平台激活可变特征集 | 不做配置 DSL(不在配置写控制流) |
 | 规则包加载 | `load_policy_bundle` 加 platform、按平台选包 | 不强制每平台立刻拆包(抖音先维持) |
 | 游走边目录/四态 | 边目录解耦成总章程;校验扩四态 | 不打墙③(引擎即插即用大重构) |
 | 游走预算 | walk_policy 从全局改 default+平台段 | 不改抖音/视频号现有游走行为 |
-| 平台接入 | 公众号/小红书建 client;快手/视频号补齐手册 | 不接头条/知乎(平台侧坏) |
-| 作者作品 | 打通快手/小红书(client 加方法+profile 改 supported) | 视频号上游 blocked,保持空返回 |
+| 平台接入 | 收口 douyin/kuaishou/shipinhao 三短视频平台;补快手/视频号规则包 dispatch | 不接公众号/小红书/头条/知乎;不做图文/图片/文字存储路线 |
+| 作者作品 | M3 打通快手 `author_to_works`(client 加方法+profile 改 supported) | 视频号上游 blocked,保持空返回 |
 | 门槛 | 维持按平台分段 | 不做"平台×品类"细分 |
 | 学习算法 | 只铺路:权重门槛搬可写配置 + 回流 fake 验 join + 人审流程约定 | 本体留下期;不接真实回流源;不自动反写上线 |
-| 数据库 | 零改动(已真连库核对) | 不动表结构(仅 performance_feedback 按需补列) |
+| 数据库 | 零改动(已真连库核对) | 不动表结构;不新增图文/图片/文字 asset 表 |
 
 ### V4 复用边界
 
 一句话:V5 **复用 V4 的工程容器,不复用"抖音专属、焊死"那部分**。
-- 复用:graph/runtime/evaluator 框架、`rule_decisions.scorecard` JSON 列、`tests/replay_harness.py`、`score_thresholds.json` 按平台分段样板、`platform_profiles/*.json` 一平台一文件范式。
+- 复用:graph/runtime/evaluator 框架、`rule_decisions.scorecard` JSON 列、两条金标准 corpus、`score_thresholds.json` 按平台分段样板、`platform_profiles/*.json` 一平台一文件范式。
 - 不继承:抖音专属规则包路径写死、打分权重焊死、游走边目录绑死抖音、四态炸弹。
 
 ---
@@ -48,11 +48,11 @@
 
 | M | 名称 | 主要目标 | 依赖 |
 |---|---|---|---|
-| M0 | 安全网与零变化基准 | 在 v5 分支做;建金标准 case(已拉 v4_douyin_57663)+ 复用 replay_harness | 现状代码 + corpus |
+| M0 | 安全网与零变化基准 | 在 v5 分支做;用金标准 case 已存历史分项复刻 evaluator 判定 | 现状代码 + corpus |
 | M1 | 打地基 | 修四态炸弹 + 边目录解耦成总章程 + 放开评分两维限制 | M0 基准 |
 | M2 | 通机制 | 激活按平台选规则包 + 打分公式搬手册(3处联动)+ 抖音迁标准手册零变化 | M1 |
-| M3 | 填平台手册 | 公众号/小红书建 client+手册;快手/视频号补齐;打通快手/小红书作者作品 | M2 |
-| M4 | 收尾 | 清死配置 + Excel 每平台重组 + 学习算法铺入口 + 回流 fake 验 join | M1-M3 |
+| M3 | 三短视频平台规则与游走补齐 | 补快手/视频号 dispatch;打通快手 author_to_works;视频号保持 blocked | M2 |
+| M4 | 收尾 | 清死配置 + Excel 全平台大矩阵 + 学习算法铺入口 + 回流 fake 验 join | M1-M3 |
 
 ---
 
@@ -61,8 +61,8 @@
 | 原则 | 说明 |
 |---|---|
 | 先合同后代码 | 先定评分卡新 schema、手册字段、平台代号口径,再改逻辑 |
-| 等价迁移优先 | M2 先原样搬(数值不改),金标准快照逐条复现=零变化,再谈加平台 |
-| 改行为先贴 diff | 行为中性的批快照零重钉;改行为的批(如快手作者作品)先贴 diff 给用户 |
+| 等价迁移优先 | M2 先原样搬(数值不改),金标准 evaluator 门逐条复刻=零变化,再谈加平台 |
+| 改行为先贴 diff | 行为中性的批必须保持金标准门通过;改行为的批(如快手作者作品)先贴 diff 给用户 |
 | 平台差异显式声明 | 不靠 `if platform==douyin`;靠手册声明"有无该维度/线索" |
 | 删除先归档 | 删配置先 grep 确认无引用;删数据走归档脚本可回灌 |
 | 不打墙③/不上学习本体 | V5 不做游走引擎即插即用、不做学习算法本体,只铺路 |
@@ -76,19 +76,20 @@
 **当前真实状态**:
 - 已在 `v5` 分支(本地+`origin/v5`);`main`/`V4-已完成-已上线`=纯 V4(43d28d69)。
 - 金标准 case 已拉**两条**(均已脱敏、在 `tests/fixtures/cases/`):抖音 `v4_douyin_57663`(110 条决策四分支全,scorecard **三维**含 50+)+ 快手 `v4_kuaishou_57758`(51 条决策四分支全,scorecard **两维**无 50+);分别覆盖"三维/两维"两条打分路径。
-- 复用机制已存在:`tests/replay_harness.py`(`replay_case()` L63-104、`load_corpus()` L44-50、`CorpusPlatformClient`)、`tests/fixtures/snapshots/`(6 个决策摘要/指纹快照)。
+- 复刻机制已验证:从两条 `rule_decisions.jsonl` 的历史 `scorecard`/decision 字段重建 evaluator 最小 bundle,加载 `JsonPolicyBundleStore.load_policy_bundle("V4")`,直接调用 `evaluator.decide()` 可逐条复刻历史 decision;只读干跑抖音 110/110、快手 51/51 均 0 mismatch。
+- DB/JSON/Excel 三方证据已核对:国内库 `content_agent_rule_decisions` 与 fixture 关键字段 0 mismatch;`score_thresholds.json` 经 `policy_json.py` 注入 evaluator 是实际门槛来源;Excel 不参与 M0 复刻。
 
-**已拍板**:金标准用 V4 真实 case;只在打分逻辑层比对、不端到端重跑;V3 老快照作废、机制复用数据重建;**① 快手两维基准已配(`v4_kuaishou_57758`,2026-06-28 最新 success);② 金标准 corpus 进 git——抖音+快手各一条,已脱敏**。
+**已拍板**:金标准用 V4 真实 case;只在 evaluator 判定逻辑层比对、不端到端重跑;V3 老 replay 基线作废、机制用历史分项复刻数据重建;**① 快手两维基准已配(`v4_kuaishou_57758`,2026-06-28 最新 success);② 金标准 corpus 进 git——抖音+快手各一条,已脱敏**。
 
 **🟠 待拍板**:无(本阶段待拍板均已拍)。
 
 **本阶段不处理**:不改任何业务代码;不接学习算法。
 
-**涉及文件**:`tests/fixtures/cases/v4_douyin_57663/`、`tests/replay_harness.py`、`tests/fixtures/snapshots/`
+**涉及文件**:`tests/fixtures/cases/v4_douyin_57663/`、`tests/fixtures/cases/v4_kuaishou_57758/`、`content_agent/business_modules/rule_judgment/evaluator.py`(只调用)、`content_agent/integrations/policy_json.py`(只调用)、`tests/test_v5_golden_corpus_baseline.py`(新增)
 
-**开发顺序**:① 用两条 corpus + replay_harness 跑出抖音/快手基线快照并钉死;② 把"快照逐条比对"接成 M1-M3 每批的回归门。
+**开发顺序**:① 从两条 corpus 的历史 decision/scorecard 重建 evaluator 最小 bundle;② 直接调用 `evaluator.decide()` 逐条复刻历史 action/reason/score/scorecard/allow_walk;③ 把"历史 decision 逐条复刻"接成 M1-M4 每批的回归门。
 
-**验证**:`uv run pytest tests/ -k replay`;基线快照可重放、逐条稳定
+**验证**:`uv run pytest tests/test_v5_golden_corpus_baseline.py -q`;抖音 110 条、快手 51 条历史 decision 逐条稳定复刻
 
 ---
 
@@ -101,11 +102,9 @@
 - 边目录:`walk_strategy_json.py:87` 硬断言 `strategy_id=="douyin_walk_strategy_v1"`;`walk_graph_json.py:19` `EDGE_CATALOG_PATH` 指抖音文件。
 - 两维锁死:`validate_rule_pack_config.py:99`、`validate_v4_config_contract.py:470` 写死 `keys==["query_relevance","platform_performance"]`。
 
-**已拍板**:四态=能走/屏蔽(blocked)/没接口(no_interface)/将来做(planned);边目录解耦成平台无关全集;数据库零改动。
+**已拍板**:四态命名固定为 `supported`(能走)/`blocked`(屏蔽)/`no_interface`(没接口)/`planned`(将来做);`edge_supported` 语义固定为只有 `supported` 才走,其余三态都跳过并保留状态原因;toutiao/zhihu 的 `_all` 简写不保留,改成逐边显式声明;边目录解耦成平台无关全集;数据库零改动。
 
-**🟠 待拍板**:① 四态的具体命名(no_interface/planned 还是别的词);② `edge_supported` 对 no_interface/planned 的语义(都按"不走"处理?);③ toutiao/zhihu 的 `_all` 简写是改成逐边声明还是保留并在校验器特判。
-
-**可先按当前默认值推进**:四态语义=只有 supported 才走,其余三态都跳过留痕(与现状兼容)。
+**🟠 待拍板**:无(本阶段待拍板均已拍)。
 
 **本阶段不处理**:不动 evaluator 打分逻辑;不接新平台 client;不改抖快视行为。
 
@@ -113,11 +112,11 @@
 
 **字段合同**:profile `edges.<edge>.status` 取值域 supported|blocked|no_interface|planned;边目录全集新增"平台无关"标识,去 douyin strategy_id 硬绑(放宽成 `*_walk_strategy_v1` 或独立 catalog)。
 
-**开发顺序**:① 校验器状态白名单 2→4 + 处理 `_all`;② 规范化 7 个 profile 的非法值;③ 放宽 strategy_id 断言/解耦 EDGE_CATALOG_PATH;④ 放开两维校验脚本;⑤ 跑金标准快照零变化 + 全测试绿。
+**开发顺序**:① 校验器状态白名单 2→4 + 将 `_all` 展开为逐边显式声明;② 规范化 7 个 profile 的非法值;③ 放宽 strategy_id 断言/解耦 EDGE_CATALOG_PATH;④ 放开两维校验脚本;⑤ 跑金标准 evaluator 门零变化 + 全测试绿。
 
-**验证**:`uv run pytest`;`load_profile("bilibili")` 不再崩;抖音金标准快照逐条零变化。
+**验证**:`uv run pytest`;`load_profile("bilibili")` 不再崩;金标准 evaluator 门逐条零变化。
 
-**失败归因**:profile 校验仍崩→白名单/`_all` 没覆盖;抖音快照变了→误碰了打分(本阶段不该碰)。
+**失败归因**:profile 校验仍崩→白名单/`_all` 没覆盖;金标准 evaluator 门失败→误碰了打分/门槛/判定(本阶段不该碰)。
 
 ---
 
@@ -126,9 +125,10 @@
 **目标**:激活"按平台选规则包"内建开关;打分配比从代码搬进手册;抖音先迁成第一本标准手册且**行为零变化**。
 
 **当前真实状态**:
-- 规则包写死:`policy_json.py:17`(路径)、`:95`(默认 douyin)、`:121/154`(字面量)。多平台派发机制已内建:`_select_dispatch`/`_enabled_dispatches`(`policy_json.py:88-126`),规则包 `rule_pack_dispatch` 段已带 platform 字段但仅 1 条 enabled。
-- 打分配比焊死 3 处:`evaluator.py:226`(非抖音 0.5/0.5)、`:239`(抖音 0.35/0.35/0.30)、`:242`(抖音不完整 35/35);`validation.py:969/971/973`(跑验反算副本);`flow_ledger_service.py:1500/1506`(前端展示副本)。
-- 门槛已配置化样板:`score_thresholds.json`(default+douyin 段),`evaluator.py:292-300` `_v4_thresholds` 合并。
+- M2 机制已落地:`policy_json.py:16` `load_policy_bundle(strategy_version, platform="douyin")`;`:28/122-131/154-173` 按 platform 选 dispatch 并构建 `rule_pack_by_entity`。
+- 规则包内容仍只有抖音一本:`douyin_rule_packs.v1.json:21-35` `rule_pack_dispatch` 只有 `platform=douyin`;这正是 M3 需要补快手/视频号 dispatch 的入口。
+- 打分配比已搬到手册:`evaluator.py:274-308` 从 `score_weight_profiles` 读取权重;`douyin_rule_packs.v1.json:348-362` 定义 default two-dimension、douyin 50+ ok、douyin 50+ not_attempted 三套 profile。
+- 门槛已配置化样板:`score_thresholds.json`(default+douyin 段),`evaluator.py:341-349` `_v4_thresholds` 合并。
 
 **已拍板**:抖音 35/35/30、非抖音 0.5/0.5 原样搬配置(数值不改);抖音迁移行为零变化作基线;3 处副本联动改。
 
@@ -140,69 +140,158 @@
 
 **字段合同**:规则包 scorecard 段新增每维 `weight`/`active`/`platform_scope`;`load_policy_bundle(strategy_version, platform)` 加参;dispatch 按 platform 选。
 
-**开发顺序**:① `load_policy_bundle` 加 platform、路径按平台解析(缺则回退默认);② dispatch 去 douyin 字面量;③ 抖音规则包补 weight/active 字段;④ `_v4_scorecard_total` 改"读维度列表按权重合成";⑤ 同步改 validation/flow_ledger 两副本;⑥ 跑抖音金标准快照**逐条零变化**(核心验收)。
+**开发顺序**:① `load_policy_bundle` 加 platform、路径按平台解析(缺则回退默认);② dispatch 去 douyin 字面量;③ 抖音规则包补 weight/active 字段;④ `_v4_scorecard_total` 改"读维度列表按权重合成";⑤ 同步改 validation/flow_ledger 两副本;⑥ 跑金标准 evaluator 门**逐条零变化**(核心验收)。
 
 **达到效果**:能按平台选规则包;抖音决策与 V4 分毫不差;改权重只改手册不改引擎。
 
-**验证**:`uv run pytest`;抖音金标准 110 条决策 scorecard/decision 逐条复现;`grep "0.35\|0.30\|platform==" evaluator.py` 打分主路径清零。
+**验证**:`uv run pytest`;金标准 evaluator 门 161 条历史 decision 逐条复刻;`grep "0.35\|0.30\|platform==" evaluator.py` 打分主路径清零。
 
 **失败归因**:抖音分数变了→搬配置时数值/分流写错;跑验失败→validation 副本没同步。
 
 ---
 
-## 8. M3 填平台手册(公众号/小红书优先 + 快手/视频号补齐 + 作者作品打通)
+## 8. M3 三短视频平台规则与游走补齐
 
-**目标**:多平台真正铺开。新建公众号/小红书 client+手册;快手/视频号补齐手册;打通快手/小红书作者作品
+**目标**:把 V5 运行范围收口到抖音、快手、视频号三短视频平台。补齐快手/视频号规则包 dispatch,并把快手 `author_to_works` 正式打开;不接公众号/小红书,不做图文/图片/文字资产路线,不改 DB
 
 **当前真实状态**:
-- client 仅 3 个:`douyin.py:143`/`kuaishou.py:121`/`shipinhao.py:127`;小红书/公众号无 client。平台工厂 `run_service.py:572-576` `real_clients` dict 三平台。
-- 7 个 profile 缺 `observable_performance`(打分参数):bilibili/github/toutiao/weixin/xiaohongshu/youtube/zhihu。
-- 作者作品:`kuaishou.py` 无 `fetch_author_works`;`shipinhao.py:340-342` 硬返回 `[]`;抖音 `fetch_author_works`(`douyin.py:143`)是模板。实测(2026-06-26)快手/小红书 blogger 上游可用(各 20 条)。
-- 引擎侧 author_to_works 边 + `_expand_authors` 已就绪,加 client 方法+profile 改 supported 即可,引擎零改。
+- 三短视频 client 已是运行边界:`run_service.py:568-577` real 模式只注册 `douyin/kuaishou/shipinhao`;国内 DB 当前只有抖音 14 run、快手 13 run、视频号 0 run,`discovered_content_items`/`content_media_records` 均为 video。
+- 三平台视频字段已存在:抖音 raw `video.play_addr`/detail `video_url_list`,快手 `video_url_list`,视频号 `video_url_list`;本阶段不新增图片/文字媒体链路。
+- M2 机制已具备:`JsonPolicyBundleStore.load_policy_bundle(strategy_version, platform)` 已按 platform 选 dispatch;但 `douyin_rule_packs.v1.json` 当前 `rule_pack_dispatch` 仍只有 `platform=douyin`,所以快手/视频号会缺规则包 dispatch。
+- 快手作者作品是"上游已证实、代码未打开":`walk_engine.py:437-506` 已在 profile gate 通过后调用 `fetch_author_works`;`kuaishou.py` 目前有 search/detail/account_info,但无 `fetch_author_works`;`tests/test_kuaishou_client.py` 仍断言没有 author works client。
+- 新证据推翻旧 registry:`product_documents/全平台游走策略.md:28-31` 记录 2026-06-26 实测 `/crawler/kuai_shou/blogger` 返回 20 条;旧 `crawler_endpoints.registry.json:344` "未抓到独立作者作品列表接口"需在 M3 实现时同步更正。
+- profile 当前仍阻断快手作者边:`platform_profiles/kuaishou.json:117-123` `author_to_works`/`author_work_to_content` 为 blocked;视频号 `platform_profiles/shipinhao.json:47-48` 保持 blocked;抖音 `platform_profiles/douyin.json:56-57` 已 supported。
 
-**已拍板**:公众号/小红书优先;打通快手/小红书作者作品;视频号作者保持空(上游 blocked);头条/知乎不接。
+**已拍板**:V5 runtime 只启用抖音/快手/视频号三短视频平台;快手 `author_to_works` 纳入 M3 core 现在打开;视频号作者作品保持 blocked;公众号/小红书/图文/图片/文字存储不进 V5
 
-**🟠 待拍板**:① 公众号/小红书谁先做(都标优先,需排序);② 快手/小红书作者作品打通是否算"运作规则变化"需单独贴 diff 确认(方案 A 严格零变化 vs 方案 B 评分零变化+游走增强,前述已倾向 B);③ 公众号图文牵动视频判定链,判定环节怎么降级(图文无视频)
+**🟠 待拍板**:无。快手 `author_to_works` 属于已确认的有意游走增强,不再拆 M3X
 
-**本阶段不处理**:不接头条/知乎;不做合集/原声/地理/相似新游走(上游无接口)
+**本阶段不处理**:不接公众号/小红书/头条/知乎;不做图文 Gemini 判定;不新增图片/文字下载与 OSS 存储;不改 DB schema;不改 evaluator 打分行为
 
-**涉及文件**:新建 `integrations/xiaohongshu.py`/`weixin.py`(参照 kuaishou.py ~300 行);`run_service.py:572`(工厂加分支);新增/补 `platform_profiles/{xiaohongshu,weixin}.json` 的 observable_performance;`kuaishou.py`/`xiaohongshu.py` 加 `fetch_author_works`;`platform_profiles/{kuaishou,xiaohongshu}.json` author_to_works→supported;各平台规则包手册 `rule_packs/{平台}.json`。
+**涉及文件**:`product_documents/规则包/douyin_rule_packs.v1.json`(补 `kuaishou/shipinhao` dispatch 或等价三平台 dispatch 配置)、`content_agent/integrations/kuaishou.py`(新增 `fetch_author_works`)、`tech_documents/数据接口与来源/platform_profiles/kuaishou.json`(`author_to_works`/`author_work_to_content` 改 supported)、`tech_documents/数据接口与来源/platform_profiles/shipinhao.json`(保持 blocked)、`tests/test_policy_dispatch.py`、`tests/test_kuaishou_client.py`、`tests/test_walk_graph_config.py`、`tests/test_walk_engine_deep_frontier.py`。
 
-**开发顺序**:① 快手 client 加 fetch_author_works + profile 改 supported(最小、引擎零改);② 小红书建 client + fetch_author_works + profile;③ 公众号建 client(图文,接判定链);④ 补 7 平台 observable_performance JSON;⑤ 各平台写打分+游走手册;⑥ 验证三平台(抖快视)快照零变化 + 新平台跑通
+**开发顺序**:① 补快手/视频号 dispatch,确保三平台都能 `load_policy_bundle("V4", platform=...)`;② `CrawapiKuaishouClient.fetch_author_works()` 接 `/crawler/kuai_shou/blogger`,复用快手 canonical normalizer,输出 video item 且标记 `previous_discovery_step=author_works`/`content_metadata_source=kuaishou_blogger`;③ 快手 profile 两条作者边改 supported,视频号维持 blocked;④ 更新旧 registry/接口文档中快手作者作品旧口径;⑤ 跑三平台 smoke 与 M0 golden gate
 
-**达到效果**:公众号/小红书能跑完整流程;快手/小红书能顺着作者挖;各平台用各自手册互不影响
+**达到效果**:三短视频平台都能走 M2 平台 dispatch;快手能顺着作者继续挖作品;抖音/快手 evaluator 金标准保持零变化;视频号按当前上游能力只走 search/detail 可用路径
 
-**验证**:`uv run pytest`;抖快视金标准零变化;新平台 smoke(真实接口单发,间隔 15s)
+**验证**:`uv run pytest tests/test_policy_dispatch.py -q`;`uv run pytest tests/test_kuaishou_client.py tests/test_douyin_client.py tests/test_shipinhao_client.py -q`;`uv run pytest tests/test_walk_graph_config.py tests/test_walk_engine_deep_frontier.py -q`;`uv run pytest tests/test_v5_golden_corpus_baseline.py -q`。快手真实 run 的 walk 行为允许因 `author_to_works` 打开而变化,但必须贴 walk action diff 说明;抖音/快手 evaluator decision 仍要求零变化
 
-**失败归因**:新平台字段映射错→normalize 没按平台 raw 结构;老平台快照变→新平台手册污染了共享层
+**失败归因**:快手作者作品无新增→client 请求/normalizer/profile gate 三处排查;快手决策分数变了→误碰 evaluator/threshold/rule pack 权重;视频号误走作者边→profile 没保持 blocked;Excel/config gate 失败→M3 误进 M4 范围
 
 ---
 
-## 9. M4 收尾(清死配置 + Excel 重组 + 学习算法铺入口 + 回流 fake)
+## 9. M4 收尾(清死配置 + Excel 全平台大矩阵 + 学习算法铺入口 + 回流 fake)
 
-**目标**:清 V3 死配置;Excel 改成每平台可维护;给学习算法把"可写入口"留好;回流用假数据验通 join 链路。
+**目标**:清 V3/旧平台死配置;把 Excel/JSON 配置治理重组成"全平台大矩阵";给学习算法把"可写入口"留好;回流用假数据验通 join 链路。这里的"全平台"是配置台账与未来能力位,不代表 V5 runtime 接入公众号/小红书或图文。
 
 **当前真实状态**:
-- 死配置:`douyin_rule_packs.v1.json` scorecard.dimensions 恰两维(50/50)、scoring_rules、thresholds 为 V3 残留(V4 实走 score_thresholds.json + 硬编码权重,搬配置后这些应清)。
-- Excel:`规则包映射配置表.xlsx`(`build_config_from_excel.py:60-73` 8 sheet,dispatch/meta 有 platform 列)、`游走策略配置表.xlsx`(`:78-84` 4 sheet 无 platform 列)。坑:`build_config_from_excel.py:155` 新增行被静默跳过(`if obj is not None`)。
-- 回流:`performance_feedback` 表(`sql/content_agent_schema.sql:496-526` 完整字段)0 写入、无写入器;`database_runtime.py:301` 只读;`rule_decisions.scorecard JSON`(`:158`)已存特征。`learning_review.py` 只统计+待批建议、不反喂。
-- 数据库:真连库核对零改动、无 ENUM/CHECK。
+- 死配置需逐项证明再删:M2 后运行权重已在规则手册 `score_weight_profiles`,但 `score_thresholds.json` 仍是 evaluator 运行门槛真源(`policy_json.py:37-40` 加载,`evaluator.py` 合并 default+platform),不能当死配置误删。
+- Excel 是两本固定 workbook: `规则包映射配置表.xlsx` 当前 13 张 sheet;`游走策略配置表.xlsx` 当前 4 张 sheet。M4 采用现有 workbook 路线,在 sheet 内按 platform 做矩阵,不拆每平台独立 Excel。
+- 转换器当前不能承接结构新增:`build_config_from_excel.py:60-84` 固定 sheet spec;`:155/161` 只 overlay 已有 JSON 对象,Excel 新增行会因 `if obj is not None` 被静默跳过。当前规则包 workbook 里 `rule_package_meta/rule_pack_catalog/input_contract_fields/decision_action_catalog/字段注释` 是 no-op/台账 sheet,Excel 出现不等于 runtime 启用。
+- 权重 profile 仍是 M2 过渡态:`evaluator.py:267-288` 还按 `default_two_dimension/douyin_fifty_plus_ok/douyin_fifty_plus_not_attempted` 三个固定名字选权重;`validate_rule_pack_config.py:27-31/196-198` 与 `validate_v4_config_contract.py:44-48/611-613` 仍强制要求这三套旧 profile 存在。M4 要把它们降级为历史兼容别名,长期改成通用 profile 矩阵。
+- 门槛仍是独立运行真源:`score_thresholds.json` 当前只有 `default + douyin` 段;`policy_json.py:37` 加载后由 `evaluator.py:_v4_thresholds` 合并。M4 不能把审批字段直接塞进现有门槛 JSON 段,要先用 `score_threshold_profiles` 承接 Excel 治理,再生成运行态 JSON。
+- 配置门已包含 Excel byte-equal:`run_config_gate.py:21-23` 串 `build_config_from_excel.py --check` 与 `validate_config_excel_sync.py`;所以 M4 重组必须同步改 Excel、JSON、转换器和校验器。
+- 回流:`performance_feedback` 表(`sql/content_agent_schema.sql:496-526` 完整字段)0 写入、无写入器;`database_runtime.py:301` 只读;`rule_decisions.scorecard JSON`(`:158`)已存特征。`learning_review.py` 只统计+待批建议、不反喂;现有测试已防止 `auto_apply/rule_pack_patch/walk_strategy_patch/budget_patch` 这类自动反写。
+- 数据库:真连国内库 MySQL 8.0.46 只读核对,无 config/rule_pack/profile/weight/threshold 配置表;`content_agent_rule_decisions=2929`,`content_agent_walk_actions=7615`,`content_agent_performance_feedback=0`;`rule_decisions` 与 `discovered_content_items` 可用 `run_id + policy_run_id + platform_content_id` 2929/2929 对账;不改 DB schema、不改历史字段。
+
+**已拍板**:M4 继续做 Excel 重组和死配置清理;Excel 做全平台大矩阵;Excel 里出现的平台不等于 runtime 启用;平台边状态固定四态 `supported / blocked / no_interface / planned`;规则包没有匹配时不允许 fallback 到抖音;学习算法本体 V5 不做、只铺路;fake 回流只做 join 验证和策略复盘证据,不自动学习、不自动改规则;权重和门槛都进 Excel 治理,但只有 `approval_status=approved` 且 `runtime_enabled=true` 才能生成运行 JSON;审批状态固定五态 `draft / waiting_for_human_reivew / approved / rejected / deprecated`(永久接受已拍板拼写 `waiting_for_human_reivew`);审批审计字段只保留 `approval_status + last_reviewed_at + owner`;门槛矩阵采用一行一个 `threshold_key + threshold_value`;游走预算按 `platform + edge_id` 配置,先把现有 `walk_policy.json` 全局/edge 预算原样迁成 `default`,仅给快手 `author_to_works` 单独显式一行且不趁 M4 调大预算;死配置归档目录进入 git,后续是否删除由人工手动决定;权重 profile 长期按 `platform_scope + applies_when + dimension_key + weight_percent + priority` 选择,不再靠 profile 名写死平台或维度数。
+
+**🟠 待拍板**:无方向性待拍板。后续 M4 implementation brief 需把本文已拍板字段拆成施工步骤,细化每张 sheet 的迁移顺序、转换器新增行策略、validator 失败提示和归档回滚流程。
+
+**本阶段不处理**:不接真实回流源;不做跨 run 学习器;不做自动反写;不因"全平台大矩阵"启用公众号/小红书 runtime;不改 DB schema。
+
+**涉及文件**:`douyin_rule_packs.v1.json`/后续平台规则包 JSON(清死配置前先 grep+validator 证明不读)、两个 Excel + `build_config_from_excel.py`(先支持结构新增,防静默跳过)、`scripts/validate_rule_pack_config.py`/`scripts/validate_v4_config_contract.py`/`scripts/run_config_gate.py`(矩阵校验)、`performance_feedback` 写入器(新,fake)、join 校验脚本(新)、`learning_review.py`(可选扩跨 run,留下期)。
+
+**开发顺序**:① 逐项 grep/validator 确认死配置无运行时读取,区分 `score_thresholds.json` 这类仍在读的真源;② 先建 `archive/v5_m4_dead_config_archive/<timestamp>/...` 归档目录和 manifest,死配置只先迁走不直接删除,归档目录进 git;③ 改 `build_config_from_excel.py` 让 Excel 新增 platform 行/矩阵行能生成 JSON,不再静默跳过;④ 在 `规则包映射配置表.xlsx` 13 sheet 与 `游走策略配置表.xlsx` 4 sheet 内做全平台矩阵重组,同步 JSON;⑤ 扩校验器覆盖"新增行不能丢、只有 approved+runtime_enabled 才进运行 JSON、平台矩阵字段合法、runtime 启用仍只限三短视频";⑥ 写 fake performance_feedback 写入器,样本从 M0 golden corpus 和当前 DB 各选,同时覆盖稳定复刻与真实 join;⑦ 验证假成绩能 join 回 `rule_decisions.scorecard`(命中率报告);⑧ 约定"改权重/门槛→人审→写 JSON 生效"流程文档。
+
+**达到效果**:运营能在 Excel 看到全平台规则/游走能力矩阵,并通过转换器生成真实 JSON;V5 runtime 仍只启用三短视频平台;死配置清干净但不误删运行真源;假数据能对回打分记录,学习算法入口就绪。
+
+**验证**:`uv run --with openpyxl python scripts/build_config_from_excel.py --check`;`uv run python scripts/validate_rule_pack_config.py`;`uv run python scripts/validate_v4_config_contract.py`;`python3 scripts/run_config_gate.py`;新增/扩展测试覆盖 Excel 新增行不会被静默跳过;`git diff -- sql` 为空;fake join 命中率达标。
+
+**失败归因**:Excel 改了不生效→新行仍被 overlay-only 逻辑跳过;全平台矩阵导致 runtime 接入越界→dispatch/profile enabled 口径没分清"台账"和"启用";未知平台被错判→dispatch miss 被错误 fallback 到抖音;未来四维/五维扩展失败→仍按三套旧 profile 名写死选择逻辑;误删后 golden gate 失败→把 `score_thresholds.json` 或权重真源当死配置删了;join 对不上→content_id 关联键口径不一致;游走预算超限展示不清→只记录了 `budget_exhausted/budget_tier=blocked`,没有给运营统一展示成 `budget_blocked`。
 
-**已拍板**:学习算法本体 V5 不做、只铺路;回流先 fake 验 join;门槛只到平台层;权重门槛改了人审才生效。
+### M4 Excel Sheet 字段整理草案(关键口径已拍板)
 
-**🟠 待拍板**:① Excel 组织——每平台一分页 vs 每平台一独立 Excel(对应草稿待拍 ③);② 死配置是删还是留空壳(建议删,先确认 evaluator V4 路径不读);③ 回流 fake 的 case 用哪条(可复用 M0 的 v4_douyin_57663 的 content_id 造假成绩)。
+**大白话解释**:这里先不直接改 Excel。这个表只是把"以后运营到底在哪张表里改什么"摊开。`sheet` 可以理解成 Excel 里的一个标签页;M4 不建议上来就删整张标签页,而是先分三类:① 运行真的会读、要保留并接进转换器;② 现在只是说明/旧台账,要么改成真能生成 JSON,要么迁走;③ 当前 JSON 已经在运行读,但还没进 Excel,必须新增标签页承接。Excel 是"配置台账 + 可生成 JSON 的治理入口",不是"出现即上线";平台是否上线看 `runtime_enabled/dispatch_enabled/profile status`,不是看 Excel 有没有一行。权重 profile 也不能继续叫 `douyin_xxx`:平台是字段,条件是 `applies_when`,维度和权重是行;这样未来三维、四维、五维都只是加矩阵行,不用每次给 evaluator 加新 if
 
-**本阶段不处理**:不接真实回流源;不做跨 run 学习器;不做自动反写。
+**已确认的真实证据**:
+- 当前转换器只认固定 sheet spec:`build_config_from_excel.py:60-84`;新增 Excel 行会因只 overlay 已有 JSON 对象而被跳过(`_set_if_exists` / `if obj is not None` 口径)。
+- 当前配置门已全绿:`build_config_from_excel.py --check` 与 `run_config_gate.py` 均通过,说明现状 Excel↔JSON byte-equal,但也说明新增结构必须先改转换器。
+- 权重 profile 真实读 JSON 但仍是旧三名:`evaluator.py:267-288` 写死三套 profile 选择;`douyin_rule_packs.v1.json:376-390` 也只有这三套。M4 附录只把旧名标历史兼容,不把它们作为未来命名规范。
+- DB 不承接配置:国内库 MySQL 8.0.46,无 config/rule_pack/profile/excel/weight/threshold 配置表;`content_agent_performance_feedback` 已存在但 0 写入;`rule_decisions` 和 `discovered_content_items` 2929/2929 可对账,所以 M4 不做 migration、不改历史字段,只做 fake 写入和 join 验证。
 
-**涉及文件**:`douyin_rule_packs.v1.json`(清 V3 段)、两个 Excel + `build_config_from_excel.py`(先改底稿防静默跳过)、`performance_feedback` 写入器(新,fake)、join 校验脚本(新)、`learning_review.py`(可选扩跨 run,留下期)。
+**状态说明**:
+- `converter_read`:当前 Excel 转换器已经读取这个 sheet。
+- `runtime_required`:运行或校验强依赖的合同。
+- `runtime_compat_copy`:兼容副本,短期保留,迁移完成后可删除。
+- `ledger_only`:台账/说明 sheet,当前不等于 runtime 启用。
 
-**开发顺序**:① 确认 V3 死配置无运行时读取→清掉;② Excel 底稿改造支持加平台行/分页;③ 写 fake performance_feedback 写入器 + 灌假数据;④ 验证假成绩能 join 回 `rule_decisions.scorecard`(命中率报告);⑤ 约定"改权重→人审→写手册生效"流程文档。
+**审批和运行启用统一规则**:
+- 允许的 `approval_status` 只有五个:`draft / waiting_for_human_reivew / approved / rejected / deprecated`;`waiting_for_human_reivew` 作为长期枚举值保留,不再纠正拼写。
+- 只有 `approval_status=approved` 且 `runtime_enabled=true` 的行才能进入运行 JSON;其他行可以留在 Excel 台账里,但只能作为草稿、待审、废弃或未来能力位。
+- 审批审计字段只保留 `approval_status`、`last_reviewed_at`、`owner`;M4 不新增 `created_by/approved_by/approved_at` 这类更重的流程字段。
+- `applies_when` 只能用可审计表达式,不写任意代码。M4 先支持 `always`、`no_extra_dimension_available`、`field_exists:<path>`、`field_missing:<path>`、`field_equals:<path>:<value>`。
+- 权重默认同一个 profile 加总必须等于 100;只有 `normalization_policy=raw_sum` 允许不等于 100,且必须填写 `normalization_reason`。
+- `missing_dimension_policy=technical_retry` 表示关键维度缺失时判技术失败,并记录缺少的 `dimension_key`、`score_source_path` 和失败原因;不要新造平行失败字段,优先落到现有 `scorecard.failure_type/exception_type/final_status/missing_observable_fields` 与 flow ledger 技术详情。
 
-**达到效果**:运营能在 Excel 改任一平台手册并生效;死配置清干净;假数据能对回打分记录,学习算法入口就绪。
+#### A. `规则包映射配置表.xlsx`
 
-**验证**:`uv run pytest`;`build_config_from_excel.py --check` 字节等价;fake join 命中率达标。
+| sheet | 状态 | M4 动作 | 当前字段 | M4 建议字段 | 说明 |
+|---|---|---|---|---|---|
+| `rule_package_meta` | `ledger_only` | 保留为元信息;若要可编辑,需接入转换器 | `package_id,schema_version,package_name,platform,strategy_id,strategy_version,effective_from,effective_to,status,owner,description,source_json_path,注释` | 追加 `platform_scope,runtime_enabled,approval_status,last_reviewed_at,target_json_path` | 区分"有台账"和"运行启用";V5 runtime 仍只启用三短视频。 |
+| `rule_pack_catalog` | `ledger_only` | 保留;补平台与审批字段 | `rule_pack_id,rule_pack_version,pack_name,target_entity,enabled,runtime_stage,dispatch_enabled,input_node,decision_node,decision_output_keys,description,注释` | 追加 `platform,target_platforms,profile_key,score_schema_version,walk_strategy_id,runtime_enabled,approval_status,last_reviewed_at,owner` | 每个平台的规则手册从这里看总目录;Excel 出现不代表已派发。 |
+| `rule_pack_dispatch` | `converter_read/runtime_required` | 保留并扩成全平台矩阵 | `dispatch_id,platform,runtime_stage,strategy_version,target_entity,content_format,rule_pack_id,rule_pack_version,dispatch_enabled,priority,fallback_policy,注释` | 追加 `runtime_enabled,platform_status,profile_path,dispatch_scope,rollout_status,approval_status,last_reviewed_at,owner` | M2/M3 已按 platform 选 dispatch;`fallback_policy` 只管 dispatch 缺失/冲突处理,unknown platform 不允许 fallback 到抖音。 |
+| `input_contract_fields` | `ledger_only` | 保留;M4 必须决定是接入转换器还是改只读 | `contract_id,rule_pack_id,rule_pack_version,field_path,field_label,required,nullable,missing_policy,default_value,evidence_source,注释` | 追加 `platform,target_entity,input_node,json_type,allowed_values,profile_dependency,enabled` | 多平台输入差异要显式列出,否则以后又会回到代码里写死。 |
+| `hard_gate_rules` | `converter_read/runtime_required` | 保留;清死配置行 | `gate_id,rule_pack_id,rule_pack_version,priority,gate_label,field_path,operator,expected_value,severity,stop_scoring,decision_action,decision_reason_code,effect_status,enabled,注释` | 追加 `platform_scope,target_platforms,profile_key,runtime_enabled,approval_status,deprecated_reason` | hard gate 是运行合同,不能删 sheet;只能删经证明不用的行。 |
+| `scorecard_dimensions` | `converter_read/runtime_required` | 保留;补平台/权重 profile 关联 | `dimension_id,rule_pack_id,rule_pack_version,dimension_key,dimension_label,max_score,weight_percent,enabled,runtime_status,evidence_paths,description,注释` | 追加 `platform,target_platforms,active,weight_profile_id,feature_source,score_source_path,profile_dependency,learning_tunable,approval_status` | 基础两维 `query_relevance/platform_performance` 必须保留;允许额外 active dimension;`fifty_plus` 当前只代表抖音 50+ 画像维度,不是跨平台通用老年维度名。 |
+| `scorecard_scoring_rules` | `converter_read/runtime_required` | 保留;补公式版本与平台范围 | `scoring_rule_id,dimension_id,rule_pack_id,field_path,operator,expected_value,score_value,score_formula,missing_policy,priority,enabled,注释` | 追加 `dimension_key,platform_scope,score_profile_id,formula_version,normalization_policy,learning_tunable,approval_status` | 管每个维度怎么算分;未来多平台差异不能靠代码分支。 |
+| `threshold_actions` | `converter_read/runtime_required` | 保留;但运行门槛真源另列新 sheet | `threshold_id,rule_pack_id,rule_pack_version,min_score,max_score,score_missing_policy,decision_action,decision_reason_code,effect_status,priority,enabled,注释` | 追加 `platform,threshold_profile_id,pool_query,review_query,walk_query,walk_platform,walk_total,learning_tunable,approval_status` | 规则包内 thresholds 不是当前唯一运行门槛;`score_thresholds.json` 仍是真源,见新增 `score_threshold_profiles`。 |
+| `decision_reason_codes` | `converter_read/runtime_required` | 保留;支持旧码退场 | `decision_reason_code,reason_label,reason_category,business_explanation,is_hard_gate,is_primary_reason_candidate,reportable,recommended_action,owner,status,注释` | 追加 `platform_scope,target_entity,severity,deprecated_reason,replacement_code` | reason code 是报表/复盘口径,必须统一治理。 |
+| `decision_action_catalog` | `ledger_only` | 保留为动作台账;M4 判断是否接入转换器 | `decision_action,action_label,target_entity,business_meaning,runtime_effect,is_terminal,is_deprecated,allowed_in_v1,注释` | 追加 `allowed_platforms,allowed_runtime_stages,effect_status_default,walk_permission_effect,deprecated_reason` | 当前含 future action,不能误当运行启用。 |
+| `effect_status_mapping` | `converter_read/runtime_required` | 保留 | `mapping_id,target_level,decision_action,reason_category,is_hard_gate,content_effect_status,query_effect_status,priority,enabled,注释` | 追加 `platform_scope,target_entity,runtime_enabled,aggregation_policy,approval_status` | content/query 四态映射是 dashboard 和 golden gate 基础。 |
+| `query_effect_aggregation` | `converter_read/runtime_required` | 保留 | `aggregation_id,condition_label,success_count_rule,pending_count_rule,failed_count_rule,rule_blocked_count_rule,query_effect_status,walk_next_step,priority,enabled,注释` | 追加 `platform_scope,walk_policy_ref,next_edge_policy,runtime_enabled` | query 结果会影响后续游走,要和平台边能力矩阵挂上。 |
+| `字段注释` | `ledger_only` | 保留 | `sheet_name,field_name,字段中文名,注释` | 追加 `json_path,required,owner,last_reviewed_at` | 字段变多后,这是运营读懂表格的说明书。 |
+| `score_weight_profiles` | **新增/runtime_required** | 新增通用权重 profile 矩阵 | 无 | `profile_id,profile_label,historical_alias_ids,rule_pack_id,rule_pack_version,platform_scope,content_format,applies_when,dimension_key,weight_percent,normalization_policy,normalization_reason,missing_dimension_policy,critical_dimension,runtime_enabled,approval_status,priority,fallback_profile_id,learning_tunable,注释` | 当前 `score_weight_profiles` 已被 evaluator 真实读取,但还在 JSON-only;长期按 `platform_scope + applies_when + dimension_key + weight_percent + priority` 选 profile,支持三维/四维/五维扩展。 |
+| `score_threshold_profiles` | **新增/runtime_required** | 新增门槛 profile 矩阵 | 无 | `profile_id,platform_scope,content_format,threshold_key,threshold_value,applies_when,runtime_enabled,approval_status,last_reviewed_at,owner,priority,learning_tunable,注释` | 承接 `score_thresholds.json`;一行只放一个 `threshold_key + threshold_value`,权重和门槛都进 Excel,但只有人审通过且启用的行才能生成运行门槛 JSON。 |
+| `learning_feedback_join_contract` | **新增/ledger_only** | 新增 fake 回流 join 合同 | 无 | `join_contract_id,platform,feedback_table,decision_table,join_keys,scorecard_fields,metric_fields,fake_case_id,expected_match_rate,approval_status,注释` | M4 只做 fake 回流 join,不做学习算法本体;先把 join 键拍清楚。 |
 
-**失败归因**:Excel 改了不生效→新行被静默跳过(没先改底稿);join 对不上→content_id 关联键口径不一致。
+**权重 profile 历史兼容别名**:`default_two_dimension`、`douyin_fifty_plus_ok`、`douyin_fifty_plus_not_attempted` 只作为 M2/M0 golden gate 兼容事实保留,不是长期命名规范。长期可分别映射为 `base_video_two_dimension_v1`、`audience_50plus_available_v1`、`audience_50plus_skipped_by_query_gate_v1`;平台范围写进 `platform_scope`,触发条件写进 `applies_when`,不要把平台名写进 profile 名。
+
+**规则包表 no-op 处理口径**:本阶段不建议直接删整张 sheet。`rule_package_meta`、`rule_pack_catalog`、`decision_action_catalog`、`字段注释` 先明确保留为 `ledger_only`,只负责让运营看懂配置和未来能力,不直接进运行 JSON;`input_contract_fields` 有业务合同价值,后续 M4 brief 优先判断是否接入转换器,若暂不接入也必须标成只读台账。总原则是:能解释业务的 sheet 留作台账,会影响运行的 sheet 才接转换器,两者都不能靠"Excel 里有一行"直接上线。
+
+#### B. `游走策略配置表.xlsx`
+
+| sheet | 状态 | M4 动作 | 当前字段 | M4 建议字段 | 说明 |
+|---|---|---|---|---|---|
+| `walk_edge_catalog` | `converter_read/runtime_compat_copy` | **迁出后删除候选** | `edge_id,edge_type,from_node_type,to_node_type,edge_label,enabled,runtime_stage,creates_new_node,can_loop,priority,notes,注释` | 迁入平台无关单源后字段为 `edge_id,edge_type,from_node_type,to_node_type,edge_label,enabled,runtime_stage,creates_new_node,can_loop,priority,catalog_scope,source_path_type,endpoint_role,gate,extract_rule,deprecated_reason,注释` | M1 已把边目录从抖音策略中解耦;M4 应避免 `douyin_walk_strategy` 和 `walk_edge_catalog.json` 双源。 |
+| `walk_rule_pack_binding` | `converter_read/runtime_required` | 保留;补平台维度 | `binding_id,edge_id,target_entity,rule_pack_id,rule_pack_version,required,dispatch_policy,notes,注释` | 追加 `platform,platform_scope,runtime_enabled,edge_status_required,rule_pack_platform,approval_status` | 说明每条边要不要绑定规则包;以后不同平台不能混在一行里。 |
+| `v4_walk_gate` | `converter_read/runtime_required` | 保留;建议拆成 edge 行 | `gate_id,requires_allow_walk,source_field,deny_reason_code,applies_to_edges,raw_payload_fields,notes,注释` | `gate_id,platform_scope,edge_id,requires_allow_walk,source_field,deny_reason_code,gate_mode,raw_payload_fields,raw_payload_schema,runtime_enabled,approval_status,注释` | 当前 `applies_to_edges` 是数组;矩阵化后每条边一行,更容易校验。 |
+| `walk_fact_contract` | `converter_read/runtime_required` | 保留 | `runtime_file,db_table,required_fields,json_fields,unique_key,notes,注释` | 追加 `platform_scope,record_schema_version,writer,reader,validation_scope,owner` | DB 不改,但事实层合同要覆盖三平台和 fake 回流验证。 |
+| `platform_edge_matrix` | **新增/runtime_required** | 新增全平台边能力矩阵 | 无 | `platform,edge_id,status,status_reason,runtime_enabled,approval_status,profile_path,endpoint_ref,can_walk,blocked_reason,evidence_ref,last_verified_at,注释` | `status` 固定四态 `supported / blocked / no_interface / planned`;`can_walk` 是派生字段,只有 `supported` 才为 true;Excel 有平台行不等于 runtime 启用。 |
+| `walk_policy_budget_matrix` | **新增/runtime_required** | 新增预算矩阵 | 无 | `platform,edge_id,budget_tier,max_total_actions,max_per_content,max_works_per_author,max_pages,budget_exhausted_reason_code,operator_display_status,runtime_enabled,approval_status,last_reviewed_at,owner,注释` | 承接 `walk_policy.json` 的预算/权限;先把现有全局/edge 预算原样迁成 `default`,再只给快手 `author_to_works` 单独显式一行;预算按 `platform + edge_id` 配,超限运行原因保留 `budget_exhausted`,运营展示统一为 `budget_blocked`。 |
+
+**游走表删除口径**:`walk_edge_catalog` 是唯一整张 sheet 删除候选,但前提是 M4 先把它迁到平台无关单源并让转换器/validator 覆盖 `walk_edge_catalog.json`。迁移前不删。
+
+#### C. Fake 回流 case 选择口径
+
+| 项 | 建议 |
+|---|---|
+| 是否改 DB schema | 不改。`content_agent_performance_feedback` 已存在,当前 0 写入;不改历史 DB 字段。 |
+| fake 写入目标 | 只写现有 `content_agent_performance_feedback` 或本地 runtime fake store;不新增表,不新增 config/weight/profile DB 表。 |
+| join 键 | `run_id + policy_run_id + platform + platform_content_id`;`platform_content_id` 对应 `rule_decisions.decision_target_id`。 |
+| fake 字段 | `feedback_id=fake_m4_{policy_run_id}_{platform_content_id}`,`feedback_source=m4_fake_feedback`,`feedback_status=available`,`completion_rate`,`share_rate`,`average_watch_seconds`,`play_count`,`like_count`,`comment_count`,`share_count`,`collect_count`,`impression_count`。 |
+| raw_payload 必放 | `run_id,policy_run_id,decision_id,rule_pack_id,rule_pack_version,score,scorecard_snapshot,fake_case_version`。 |
+| fake 样本类型 | 固定选五类:高分入池、人工复核、低分拒绝、技术失败、快手 `author_to_works` 游走增强样本;样本来源从 M0 golden corpus 和当前 DB 各选,保证稳定复刻和真实 join 都覆盖。 |
+| 验收 | 选定 fake 样本 join 命中率 100%;`learning_review` 只产建议,不自动改权重/门槛。 |
+
+#### D. 死配置归档 manifest
+
+| 项 | 口径 |
+|---|---|
+| 归档目录 | `archive/v5_m4_dead_config_archive/<timestamp>/...`,目录进入 git。 |
+| 删除策略 | M4 只归档不自动删除;后续是否物理删除由人工手动决定。 |
+| manifest 字段 | `archive_id,source_path,archive_path,archived_at,archived_by,proof_no_runtime_read,restore_instruction,stable_until,delete_after,approval_status,注释`。 |
+| 验收 | 每个归档项必须有 grep/validator 证据证明 runtime 不读;必须能按 `restore_instruction` 回灌。 |
 
 ---
 

+ 8 - 7
tech_documents/工程落地/v5_implementation_briefs/M0/00_M0_Brief_Index.md

@@ -4,14 +4,14 @@
 
 ## M0 目标
 
-为 V5 后续 M1-M4 的每一批改造提供一条**逐条可证明的零变化回归门**:抖音(三维)、快手(两维)两条 V4 金标准 corpus 离线回放后,scorecard 指纹必须与基线逐条一致——一致=零变化,不一致=回归当场拦下。
+为 V5 后续 M1-M4 的每一批改造提供一条**逐条可证明的零变化回归门**:抖音(三维)、快手(两维)两条 V4 金标准 corpus 用历史 `rule_decisions.jsonl` 已存分项重建 evaluator 最小输入,直接调用当前 `evaluator.decide()` 重算后,必须与历史 decision 逐条一致——一致=零变化,不一致=回归当场拦下。
 
 ## 子 brief 清单
 
 | Brief | 标题 | 职责 |
 |---|---|---|
-| M0A | 金标准 Corpus 与回放基线 | 固定两条 corpus、复用 `replay_harness` 离线复现、产出 scorecard 指纹基线快照 |
-| M0B | 零变化回归门与验收 | 把指纹比对接成回归 test + 验收命令 + sub-agent 审计口径,供 M1-M4 每批挂用 |
+| M0A | 金标准 Corpus 与历史 evaluator 判定复刻基线 | 固定两条 corpus、定义 `_bundle_from_historical_decision()` 数据合同、证明当前 evaluator 可逐条复刻历史 decision |
+| M0B | 零变化回归门与验收 | 把历史 decision 复刻比对接成回归 test + 验收命令 + sub-agent 审计口径,供 M1-M4 每批挂用 |
 
 > 子 brief 数量说明:V4 M0 是"合同冻结"涉及 DB/字段/walk/验收 5 块故拆 M0A-E;V5 M0 职责单一(建标尺),按内容拆 **2 个**,对齐 V4"每 brief 12 章细致度"但不硬凑 5 个(V3 M0 仅 3 个,数量按内容定有先例)。
 
@@ -23,12 +23,13 @@
 ## 不在 M0 范围
 
 - 不改 `content_agent/` 任何业务代码(evaluator/walk_engine/policy_json 等)。
-- 不改 `tests/replay_harness.py`、`tests/replay_clients.py`、`tests/snapshot.py`(纯复用)。
+- 不改 `tests/replay_harness.py`、`tests/replay_clients.py`、`tests/snapshot.py`(M0 不走 RunService replay,也不新增 snapshot 基线)。
 - 不动两条 corpus 的 input 文件(它们是不可变基准)。
-- 不接学习算法;不新增 DB 表;不动 schema。
+- 不改 Excel;不接学习算法;不新增 DB 表;不动 schema。
+- 不调用 Gemini、爬虫、热点宝等实时外部接口;M0 复刻的是历史 run 已落库/入 corpus 的派生分项。
 
 ## 三条贯穿约束(来自用户)
 
 1. 不做无意义功能:M0 只建标尺,不凑子 brief 数量。
-2. 不改不需改的:回放/快照工具与 corpus 零改。
-3. 将来变动最少:复用现有 `_fingerprint`/`assert_matches` 模式,M1-M4 直接挂门。
+2. 不改不需改的:evaluator 业务代码、回放/快照工具、Excel 与 corpus 零改。
+3. 将来变动最少:只新增一个 golden test 文件,从历史 decision 重建 bundle 后调用 `evaluator.decide()`,M1-M4 直接挂门。

+ 115 - 65
tech_documents/工程落地/v5_implementation_briefs/M0/M0A_Golden_Corpus_And_Replay_Baseline.md

@@ -1,105 +1,155 @@
-# M0A 金标准 Corpus 与回放基线 Implementation Brief
+# M0A 金标准 Corpus 与历史 evaluator 判定复刻基线 Implementation Brief
 
-> 本 brief 只覆盖:固定两条 V4 金标准 corpus、用 `replay_harness` 离线复现、产出 scorecard 指纹基线快照。回归 test 的挂载与验收口径在 M0B。
+> 本 brief 只覆盖:固定两条 V4 金标准 corpus、从历史 `rule_decisions.jsonl` 重建 evaluator 最小输入、调用当前 `evaluator.decide()` 逐条复刻历史 decision。回归 test 的挂载与验收口径在 M0B。
 
 ## 目标
 
 - 把两条 corpus(抖音三维 / 快手两维)钉成 V5 全程不可变的零变化基准。
-- 复用 `replay_harness.replay_case()` 离线复现(不调真实爬虫/LLM),产出可逐条比对的 scorecard 指纹
-- 指纹只取确定性打分字段、去掉 run_id/时间戳等易变项,保证可重放稳定
+- 不走 `RunService` 全链路 replay,不调 Gemini/爬虫/热点宝;只复刻历史 run 已落库/入 corpus 的派生分项
+- 证明当前 evaluator 对两条历史 case 的 `decision_action`、`decision_reason_code`、`search_query_effect_status`、`score`、`scorecard`、`allow_walk` 可逐条重算一致
 
 ## 现有证据(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/fixtures/cases/v4_douyin_57663/input/rule_decisions.jsonl` 是 JSON array,110 条决策四分支全:入池20/复看37/技术18/淘汰35;`fifty_plus_status`:ok 89/not_attempted 16/unavailable 5,覆盖抖音三维与画像失败路径。
+  - `tests/fixtures/cases/v4_kuaishou_57758/input/rule_decisions.jsonl` 是 JSON array,51 条决策四分支全:入池14/复看13/技术4/淘汰20;51 条均无 `fifty_plus_*`,覆盖非抖音两维路径。
+- DB sub-agent 只读连接国内 MySQL(`content-deconstruction-supply`,MySQL 8.0.46,`START TRANSACTION READ ONLY`)后确认:
+  - `v1_run_57663_douyin_1782560050`:DB `content_agent_rule_decisions` 110 条,fixture 110 条,关键字段 0 mismatch。
+  - `v1_run_57758_kuaishou_1782622792`:DB `content_agent_rule_decisions` 51 条,fixture 51 条,关键字段 0 mismatch。
+  - 两条 run 均为真实 V4 run;`rule_pack_version=4.0.0`;`policy_bundle_hash=b2f0f72c90a35f7721c72f95a3e285dc68be03348a244ee425a67bc373fbec3f`。
+- JSON/evaluator sub-agent 与本地只读干跑均确认:
+  - 从两条 `rule_decisions.jsonl` 反构造最小 bundle,加载 `JsonPolicyBundleStore(Path('.')).load_policy_bundle('V4')`,直接调用 `evaluator.decide()`。
+  - 抖音 110/110、快手 51/51 的 action/reason/effect/score/scorecard/replay_marker 全部 0 mismatch。
+- evaluator 真实读取路径:
+  - `decide(run_id, policy_run_id, index, bundle, policy_bundle)` 签名在 `content_agent/business_modules/rule_judgment/evaluator.py:46`。
+  - V4 主路径 `_decide_v4()` 在 `evaluator.py:159`:先 `_v4_scorecard_total(bundle)`,再 `_v4_thresholds(policy_bundle, content.platform)`,再 `_v4_decision()`/`_v4_allow_walk()`。
+  - `_v4_scorecard_total()` 在 `evaluator.py:209`:读取 `pattern_match_result.query_relevance_score`、`content_engagement_metrics.platform_performance`、可选 `content_audience_50plus`。
+  - 非抖音/无 50+ 走 `0.5 query + 0.5 platform`(`evaluator.py:223-229`);抖音 `status=ok` 走 `0.35 query + 0.35 platform + 0.30 fifty_plus`(`evaluator.py:231-240`)。
+  - 技术字段 `failure_type/exception_type/http_status_code/retry_count/final_status` 从 `pattern_match_result` 透传(`evaluator.py:313-321`)。
+- policy/threshold 真实来源:
+  - `JsonPolicyBundleStore.load_policy_bundle('V4')` 固定读取 `product_documents/规则包/douyin_rule_packs.v1.json` 并注入 `score_thresholds.json`(`content_agent/integrations/policy_json.py:16-79`)。
+  - evaluator 实际判定/游走门槛来自 `tech_documents/数据接口与来源/score_thresholds.json`,不是 Excel `threshold_actions`。
+- Excel/config sub-agent 确认:
+  - `规则包映射配置表.xlsx` 当前仍是 V4 M3 两维维护表(query/platform 50/50),只用于配置台账与后续 M4 重组,不是 M0 历史三维复刻的数据源。
+  - `游走策略配置表.xlsx` 无 platform 列;`v4_walk_gate` 只声明 `allow_walk` 消费合同;M0 不改 Excel。
+
+## 修改范围(只新增测试,不改现有业务)
+
+- 新增 `tests/test_v5_golden_corpus_baseline.py`。
+- 新增纯函数 `_bundle_from_historical_decision(decision, platform)` 与行为指纹函数(均放 test 模块内,不污染业务代码)。
+- 不新增 snapshot JSON;历史 corpus decision 本身就是 golden baseline。
 
 ## 不修改范围(红线)
 
-- 不改 `tests/replay_harness.py` / `tests/replay_clients.py` / `tests/snapshot.py`(零改、纯复用)。
-- 不改任何 `content_agent/` 业务代码。
-- 不改两条 corpus 的 input 文件(不可变基准)。
-- 不新增 DB 表 / 不动 schema。
+- 不改 `content_agent/` 业务代码,尤其不改 `evaluator.py`、`policy_json.py`。
+- 不改 `tests/replay_harness.py` / `tests/replay_clients.py` / `tests/snapshot.py`。
+- 不改两条 corpus 的 input 文件。
+- 不改 Excel、不新增 DB 表、不动 schema。
+- 不调用 Gemini、爬虫、热点宝等实时外部接口。
 
 ## 涉及文件 / 函数 / 类
 
-- `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` 稳定排序):
+- `content_agent/business_modules/rule_judgment/evaluator.py`:只调用 `decide()`。
+- `content_agent/integrations/policy_json.py`:只调用 `JsonPolicyBundleStore(Path('.')).load_policy_bundle('V4')`。
+- 新增 `tests/test_v5_golden_corpus_baseline.py`:
+  - `_load_historical_decisions(case_id)`
+  - `_platform_for_case(case_id)`
+  - `_bundle_from_historical_decision(decision, platform)`
+  - `_decision_behavior_fingerprint(decision)`
+  - 两条 case 的 exact replay test + 技术失败/漂移检测 unit test。
+
+## 数据合同:`_bundle_from_historical_decision(decision, platform)`
+
+输入为一条历史 `rule_decisions.jsonl` decision,输出最小 evaluator bundle:
+
+| evaluator bundle 输入 | corpus JSON 来源 |
+|---|---|
+| `content.platform` | case id / run_id 推断:`v4_douyin_57663` → `douyin`,`v4_kuaishou_57758` → `kuaishou` |
+| `content.decision_target_type` | `decision.decision_target_type` |
+| `content.decision_target_id` | `decision.decision_target_id` |
+| `content.platform_content_id` | 用 `decision.decision_target_id` 回填,避免误触发 `missing_platform_content_id` hard gate |
+| `pattern_match_result.query_relevance_score` | `decision.scorecard.query_relevance_score` |
+| `pattern_match_result.judge_status` | `decision_action=TECHNICAL_RETRY_REQUIRED` 且 `decision_reason_code=v4_technical_retry_needed` 时填 `failed`,其余填 `ok` |
+| `pattern_match_result.failure_type/exception_type/http_status_code/retry_count/final_status` | `decision.scorecard` 同名字段 |
+| `content_engagement_metrics.platform_performance.platform_performance_score` | `decision.scorecard.platform_performance_score` |
+| `content_engagement_metrics.platform_performance.platform_performance_components` | `decision.scorecard.platform_performance_components` |
+| `content_engagement_metrics.platform_performance.missing_observable_fields` | `decision.scorecard.missing_observable_fields` |
+| `content_audience_50plus.status` | 抖音:`decision.scorecard.fifty_plus_status`;快手不放该块 |
+| `content_audience_50plus.score` | 抖音:`decision.scorecard.fifty_plus_score` |
+| `content_audience_50plus.components` | 抖音:`decision.scorecard.fifty_plus_components` |
+| `content_audience_50plus.failure_reason` | 抖音:`decision.scorecard.fifty_plus_failure_reason` |
+| `run_context.decision_input_snapshot_id` | `decision.decision_input_snapshot_id` |
+| `source_evidence` | `decision.source_evidence` |
+
+## 行为指纹合同
+
+逐条比较历史 decision 与重算 decision,至少包含:
 
 ```
-(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)
+decision_target_id
+decision_action
+decision_reason_code
+search_query_effect_status
+round(score, 2) | None
+scorecard.schema_version
+round(scorecard.total_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.score_missing
+scorecard.failure_type
+scorecard.final_status
+scorecard.score_thresholds
+decision_replay_data.allow_walk
+decision_replay_data.allow_walk_reason
+decision_replay_data.walk_gate_snapshot
+triggered_blocking_rules
 ```
 
-- **取确定性打分字段,去掉**:run_id、policy_run_id、时间戳、wa_id、play_url 等易变/已脱敏项。
-- 抖音 case 指纹含 `fifty_plus_*`(三维);快手 case 指纹 `fifty_plus_score=None`/`fifty_plus_status=None`(两维)——两条覆盖两条打分路径。
+- 必须比对 `scorecard.score_thresholds`,防止 `score_thresholds.json` 漂移造成假零变化。
+- 不比对 run_id、policy_run_id、policy_bundle_hash、时间戳、raw_payload 中可随迁移变化的 metadata。
+- 比对保留逐条粒度,不得退化成总数/分布 summary。
 
 ## 实施步骤(顺序不可颠倒)
 
-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` 再跑一次,确认稳定通过(可重放)。
+1. 写 `_bundle_from_historical_decision()` 与 `_decision_behavior_fingerprint()`
+2. 写 `test_v5_douyin_57663_historical_decisions_replay_exactly`:加载 110 条历史 decision,逐条重建 bundle → `decide()` → 行为指纹一致
+3. 写 `test_v5_kuaishou_57758_historical_decisions_replay_exactly`:加载 51 条历史 decision,逐条重建 bundle → `decide()` → 行为指纹一致
+4. 写 `test_bundle_reconstruction_handles_technical_failures`:覆盖 `v4_technical_retry_needed` 与 `portrait_unavailable` 两类技术失败
+5. 写 `test_golden_gate_detects_score_drift`:内存改一条 query/platform/50+ 分数后,行为指纹必须不一致
 
 ## 验证命令
 
 ```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'))))"
+git diff -- content_agent tests/replay_harness.py tests/snapshot.py
 ```
 
+期望:
+
+- pytest 全绿。
+- 第二条 diff 为空,证明 M0 没改业务代码和回放/快照工具。
+
 ## 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(两维)。
+- `test_bundle_reconstruction_handles_technical_failures` — `v4_technical_retry_needed` 通过 `judge_status=failed` 复刻;`portrait_unavailable` 通过 `content_audience_50plus.status=unavailable` 复刻。
+- `test_golden_gate_detects_score_drift` — 内存改一条分数,指纹不一致,证明门能抓单条漂移。
 
 ## Integrated Test
 
-- `test_v5_douyin_57663_scorecard_baseline` / `test_v5_kuaishou_57758_scorecard_baseline` — `replay_case` 两 case,指纹逐条 == 基线快照(本 brief 建基线,回归门挂载在 M0B)。
-- 暂不要求端到端真实跑(M0 只离线回放;真实跑在 14 号 M3 验收)。
+- `test_v5_douyin_57663_historical_decisions_replay_exactly` — 110 条全部复刻,覆盖抖音三维(ok/not_attempted/unavailable)。
+- `test_v5_kuaishou_57758_historical_decisions_replay_exactly` — 51 条全部复刻,覆盖快手两维(无 `content_audience_50plus`)。
 
 ## 失败归因
 
-- 指纹每次不同 → 没去干净易变字段(run_id/时间戳)或排序键不稳
-- replay 报错 → corpus 文件缺失/损坏,或 `replay_case` 参数错(对照 `tests/test_case_replay.py:81`)
-- 指纹条数 ≠ 110/51 → corpus 被改过,或 replay 丢了决策
-- 抖音指纹无 fifty_plus → 取错字段或 corpus 不是三维 case
+- action/reason 不一致 → evaluator 公式、门槛、hard gate 或 effect mapping 被改坏
+- score/scorecard 不一致 → `_bundle_from_historical_decision()` 映射错,或 V5 改动破坏了 V4 权重/门槛
+- `score_thresholds` 不一致 → `score_thresholds.json` 或 policy loading 行为漂移
+- 技术失败不一致 → `judge_status`/`failure_type`/50+ status 复刻规则错
 
 ## 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
+- DB sub-agent:抽查两条 run 的 DB `content_agent_rule_decisions` 与 fixture 关键字段仍 0 mismatch
+- JSON/evaluator sub-agent:复跑 161 条内存干跑,确认 `evaluator.decide()` 0 mismatch
+- Excel/config sub-agent:确认 M0 没改 Excel;实际门槛仍来自 `score_thresholds.json` 经 `JsonPolicyBundleStore` 注入

+ 57 - 34
tech_documents/工程落地/v5_implementation_briefs/M0/M0B_Zero_Change_Regression_Gate_And_Acceptance.md

@@ -1,77 +1,100 @@
 # M0B 零变化回归门与验收 Implementation Brief
 
-> 本 brief 只覆盖:把 M0A 的指纹基线接成"V5 每批改造的回归门" + 验收命令 + sub-agent 审计口径。不重复 M0A 的指纹生成
+> 本 brief 只覆盖:把 M0A 的历史 decision 复刻基线接成"V5 每批改造的回归门" + 验收命令 + sub-agent 审计口径。不重复 M0A 的 bundle 映射细节
 
 ## 目标
 
-- 提供一条命令级回归门:V5 任一批改完,跑它 → 两条 corpus 的 scorecard 指纹必须逐条 == 基线,否则判回归、当场拦下。
-- 明确两类纪律的执行点:"行为中性的批必须零重钉快照"、"改行为的批先贴 diff 经批准再重钉"
+- 提供一条命令级回归门:V5 任一批改完,跑它 → 两条 corpus 的历史 decision 必须可由当前 evaluator 逐条复刻,否则判回归、当场拦下。
+- 明确纪律:历史 corpus decision 本身就是基线,不靠 snapshot 重钉
 - 给 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 验证段均引用此门。
+- M0A 产出:`_bundle_from_historical_decision()` + `_decision_behavior_fingerprint()` + 两条 exact replay test。
+- JSON/evaluator sub-agent 已用当前 `evaluator.decide()` 只读干跑:
+  - `v4_douyin_57663`:110/110,0 mismatch。
+  - `v4_kuaishou_57758`:51/51,0 mismatch。
+- DB sub-agent 已确认两条真实 run 与 fixture 关键字段 0 mismatch:
+  - `v1_run_57663_douyin_1782560050`:DB 110 条,fixture 110 条。
+  - `v1_run_57758_kuaishou_1782622792`:DB 51 条,fixture 51 条。
+- Excel/config sub-agent 已确认:
+  - M0 不改 Excel。
+  - evaluator 实际门槛来自 `tech_documents/数据接口与来源/score_thresholds.json`,由 `JsonPolicyBundleStore.load_policy_bundle('V4')` 注入。
+- 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 一键跑回归门。
+- 新增 `tests/test_v5_golden_corpus_baseline.py`。
+- 不新增 snapshot JSON。
+- 不注册 pytest marker;直接用明确文件命令作为 golden gate,避免多余配置。
 
 ## 不修改范围(红线)
 
 - 不改业务代码;不改 `replay_harness.py`/`snapshot.py`。
-- **不放宽指纹粒度**(必须逐条,不退化成计数 summary)。
-- 不把"改行为的批"偷偷重钉快照绕过门(必须人工贴 diff 确认)。
+- 不改两条 corpus。
+- 不改 Excel。
+- 不把历史基线漂移藏进 snapshot 重钉;若有意改变行为,必须另建新基线或更新 corpus,并先贴 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,不改业务)。
+- `tests/test_v5_golden_corpus_baseline.py`(M0A 建,M0B 作为回归门入口)。
+- `content_agent.business_modules.rule_judgment.evaluator.decide()`(只调用)。
+- `content_agent.integrations.policy_json.JsonPolicyBundleStore.load_policy_bundle('V4')`(只调用)。
 
 ## 数据合同(回归门口径)
 
-- 门 = `uv run pytest -m golden`(或指定 test 文件):两条 case 的 `scorecard_fingerprint` 逐条 ==,且(可选)`walk_actions_fingerprint` 逐条 ==。
-- 通过 = 零变化;失败 = 该批触碰了打分/游走行为,必须人工判定:
-  - "误伤" → 修回代码,门重新转绿;
-  - "有意改行为" → 贴 diff + 经用户批准后,用 `UPDATE_SNAPSHOTS=1` 显式重钉基线,commit message 写明原因。
-- **重钉基线只允许在"有意改行为且用户批准"路径**,严禁为了让门变绿而随手重钉。
+- 门 = `uv run pytest tests/test_v5_golden_corpus_baseline.py -q`。
+- 通过 = 两条 corpus 的历史 decision 均可被当前 evaluator 逐条复刻:
+  - 抖音 110 条,覆盖三维公式、画像 ok/not_attempted/unavailable、技术失败。
+  - 快手 51 条,覆盖非抖音两维公式、技术失败。
+- 失败 = 当前代码/config 已影响历史 evaluator 行为,必须人工判定:
+  - "误伤" → 修回代码/config,门重新转绿。
+  - "有意改行为" → 贴 action/reason/score/threshold/allow_walk diff 给用户;确认后另建新基线或更新 corpus,不得偷偷重钉。
 
 ## 实施步骤
 
-1. 在 baseline test 模块加回归门注释 +(可选)`walk_actions` 指纹两条
-2. (可选)`pyproject.toml` 注册 `golden` marker
-3. 跑 `uv run pytest -m golden -q` 确认两 case 全绿
-4. 在 M1-M4 各 brief 的"验证命令"统一写入这条门命令
+1. 完成 M0A test 文件
+2. 跑 `uv run pytest tests/test_v5_golden_corpus_baseline.py -q`,确认 161 条 exact replay 全绿
+3. 在 M1-M4 各 brief 的"验证命令"统一写入这条门命令
+4. 若后续 M1-M4 某批改完此门失败,先输出逐条 diff,再判断误伤还是有意行为变化
 
 ## 验证命令
 
 ```bash
-uv run pytest -m golden -q            # 或 uv run pytest tests/test_v5_golden_corpus_baseline.py -q
-# 演练"改坏即拦":临时把 evaluator 某权重改一位小数 → 跑门 → 必须 FAIL(证明门有效)→ 改回 → 门 PASS。
+uv run pytest tests/test_v5_golden_corpus_baseline.py -q
+git diff -- content_agent tests/replay_harness.py tests/snapshot.py
+```
+
+门有效性演练(只在本地临时改动,不得提交):
+
+```bash
+# 临时把 evaluator 某个 V4 权重或 score_thresholds.json 某个门槛改一位
+uv run pytest tests/test_v5_golden_corpus_baseline.py -q  # 必须 FAIL
+# 改回后
+uv run pytest tests/test_v5_golden_corpus_baseline.py -q  # 必须 PASS
 ```
 
 ## Unit Test
 
-- `test_golden_gate_fails_on_score_drift` — 用一份"人为改过一条 total_score"的 decisions 喂 `_scorecard_fingerprint` 比对,断言 != 基线(证明门能抓到单条变化)。
+- `test_bundle_reconstruction_handles_technical_failures` — 证明技术失败复刻不是只靠 happy path。
+- `test_golden_gate_detects_score_drift` — 证明门能抓单条 score drift,不退化成统计分布。
 
 ## 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` — 锁游走零变化
+- `test_v5_douyin_57663_historical_decisions_replay_exactly` — M0 回归门主体之一
+- `test_v5_kuaishou_57758_historical_decisions_replay_exactly` — M0 回归门主体之二
 - M1-M4 每批改完都要跑这组(在各自 brief 验证段引用)。
 
 ## 失败归因
 
-- 门跑过但 V5 改坏没抓到 → 指纹粒度退化成 summary(必须逐条),或漏了某打分字段。
-- 门一直 FAIL 但人没改打分 → 指纹含了易变字段(回 M0A 修指纹),或 corpus/基线被误改。
-- "改行为的批"被误判 → 该批确实有意改打分,应贴 diff、经批准后重钉基线,而非放宽门。
+- 门跑过但 V5 改坏没抓到 → 行为指纹漏字段,尤其漏了 `scorecard.score_thresholds` 或 `decision_replay_data.allow_walk`。
+- 门一直 FAIL 但人没改打分 → bundle 重建映射错,或 policy loading/threshold 配置发生漂移。
+- 只有 metadata/hash 不一致 → 不应纳入门;本门关注行为字段,不是迁移中可变 metadata。
+- "改行为的批"被误判 → 该批确实有意改打分/门槛/游走,应贴 diff、经用户批准后新建基线,而非放宽门。
 
 ## sub-agent 交叉验证要点
 
-- 代码 sub-agent:确认门只读两条 corpus、零改业务;`git diff content_agent/` 为空。
-- 验收 sub-agent:演练"改一位小数权重 → 门 FAIL → 改回 → 门 PASS",证明门真能拦零变化破坏。
-- 流程 sub-agent:确认重钉基线只在"有意改行为 + 用户批准"路径,commit message 写明原因。
+- 代码 sub-agent:确认 test 只读 corpus + 调用 evaluator/policy store,`git diff content_agent/ tests/replay_harness.py tests/snapshot.py` 为空。
+- 数据 sub-agent:确认两条真实 run 与 fixture `content_agent_rule_decisions` 关键字段仍 0 mismatch。
+- 验收 sub-agent:演练"改一位权重/门槛 → 门 FAIL → 改回 → 门 PASS",证明门真能拦零变化破坏。
+- 流程 sub-agent:确认没有 snapshot 重钉路径。

+ 156 - 0
tech_documents/工程落地/v5_implementation_briefs/M1/00_M1_Brief_Index.md

@@ -0,0 +1,156 @@
+# V5 M1 打地基 Brief Index
+
+状态:本目录覆盖 V5 M1——修四态炸弹、把游走边目录从抖音策略文件解耦成平台无关总章程、放开 V4 规则包 validator 的两维硬锁。M1 只做配置/loader/validator 地基,不改 evaluator 判定、RunService、新平台 client、DB schema 或 Excel。
+
+## 目标定位
+
+M1 的目标是让后续 M2-M4 可以加平台和迁移规则包,而不会被当前配置地基卡死:
+
+1. `platform_profiles/*.json` 支持四态:`supported` / `blocked` / `no_interface` / `planned`。
+2. `edge_supported()` 语义固定为只有 `supported` 才走,其余三态都不走但保留状态原因。
+3. `toutiao` / `zhihu` 不再使用 `_all` 简写,所有平台 profile 都逐边显式声明。
+4. `walk_edge_catalog` 从抖音策略文件语义中解耦,成为平台无关边目录。
+5. 规则包 validator 不再把 V4 active scorecard 维度锁死为两维,但当前真实 V4 评分行为保持零变化。
+
+## 子 brief 执行口径(A->E 线性)
+
+| 子 brief | 文件 | 核心交付 | 依赖 |
+|---|---|---|---|
+| M1A | `M1A_Profile_Edge_Status_And_Four_State_Contract.md` | 四态合同、`edge_supported()` 语义、profile validator 白名单 | M0 golden gate |
+| M1B | `M1B_Profile_Normalization_And_All_Expansion.md` | 10 个 profile 逐边显式声明、移除 `_all`、非法状态规范化 | M1A |
+| M1C | `M1C_Walk_Edge_Catalog_And_Strategy_Decoupling.md` | `walk_edge_catalog` 平台无关化、去抖音路径/identity 硬绑 | M1A-M1B |
+| M1D | `M1D_Rule_Pack_Dimension_Validator_Relaxation.md` | 放开两维 validator,为 M2 维度/权重搬手册铺路 | M1A |
+| M1E | `M1E_Acceptance_Commands_And_Subagent_Audit.md` | 验收命令、Unit/Integrated Test、DB 只读审计、M0 门挂载 | M1A-M1D |
+
+## 不处理范围(红线)
+
+- 不修改 `content_agent/business_modules/rule_judgment/evaluator.py` 的 V4 打分、门槛、`allow_walk` 判定。
+- 不修改 `content_agent/integrations/policy_json.py` 的 policy bundle 装配。
+- 不修改 `content_agent/run_service.py` 和任何平台 client。
+- 不新增 DB 表、列、索引或 migration;数据库零改动。
+- 不改 Excel;M1 不做每平台 Excel 重组。
+- 不改 `douyin_rule_packs.v1.json` 的实际评分维度、权重、thresholds。
+- 不接新平台真实接口,不做 smoke 调用。
+- 不放宽 M0 golden gate;M1 后必须继续逐条复刻 161 条历史 decision。
+
+## 现有证据(共享,子 brief 各自细引)
+
+- 代码证据:
+  - `content_agent/integrations/walk_graph_json.py:19` 的 `EDGE_CATALOG_PATH` 仍指向 `product_documents/抖音游走策略/douyin_walk_strategy.v1.json`。
+  - `walk_graph_json.py:47-49` 的 `edge_supported()` 只判断 `status == "supported"`,且 profile 缺边默认 supported。
+  - `walk_graph_json.py:151-158` 的 `_validate_profile()` 只接受 `{supported, blocked}`。
+  - `content_agent/integrations/walk_strategy_json.py:11` 的 `WALK_STRATEGY_PATH` 固定抖音策略文件。
+  - `walk_strategy_json.py:82-92` 的 `_check_identity()` 硬断言 `strategy_id == douyin_walk_strategy_v1`。
+  - `scripts/validate_rule_pack_config.py:97-103` 与 `scripts/validate_v4_config_contract.py:468-475` 锁死 V4 active dimensions 为 `query_relevance/platform_performance`。
+- JSON 证据:
+  - `tech_documents/数据接口与来源/walk_graph.json` 有 9 条真实边。
+  - `product_documents/抖音游走策略/douyin_walk_strategy.v1.json` 同样含 9 条 `walk_edge_catalog`,但带抖音命名。
+  - `bilibili/youtube/weixin/github` profile 存在非法 `unsupported/weak`。
+  - `toutiao/zhihu` 的 `edges._all` 是字符串,当前会触发 `AttributeError`。
+  - `product_documents/规则包/douyin_rule_packs.v1.json` 当前 V4 scorecard 仍是两维 50/50。
+- DB 证据:
+  - DB 只读连接 `content-deconstruction-supply` 成功,MySQL `8.0.46`,事务只读 `@@transaction_read_only=1`。
+  - `information_schema` 未发现 profile/config/walk graph 配置表;M1 不需要 DB migration。
+  - 抖音 golden run:110 条 rule decisions、265 条 walk actions、`allow_walk=true` 12 / false 98。
+  - 快手 golden run:51 条 rule decisions、142 条 walk actions、`allow_walk=true` 6 / false 45。
+  - 快手 `author_to_works` 被 `edge_blocked_by_platform_profile` 跳过 44 条,说明 profile gate 已是运行事实。
+
+## 数据合同(汇总)
+
+### Profile Edge Status
+
+`platform_profiles/{platform}.json`:
+
+```json
+{
+  "edges": {
+    "author_to_works": {
+      "status": "no_interface",
+      "reason": "platform api does not expose author works endpoint"
+    }
+  }
+}
+```
+
+状态语义:
+
+| status | runtime 是否走 | 含义 |
+|---|---|---|
+| `supported` | 是 | 当前平台可执行该边 |
+| `blocked` | 否 | 业务或策略明确屏蔽 |
+| `no_interface` | 否 | 平台/API 当前没有接口 |
+| `planned` | 否 | 后续规划支持,当前不执行 |
+
+### Walk Edge Catalog
+
+平台无关边全集固定为 9 条:
+
+`search_page_to_content`, `video_to_author`, `author_to_works`, `author_work_to_content`, `video_to_hashtag`, `hashtag_to_query`, `decision_to_asset`, `path_stop`, `budget_downgrade`。
+
+### Rule Pack Dimension Validator
+
+M1 后 validator 规则:
+
+- V4 scorecard active dimensions 必须包含 `query_relevance` 与 `platform_performance`。
+- 允许出现额外 active dimension,供 M2/M3 后续平台化。
+- scoring rule 仍必须引用已声明 dimension。
+- 当前真实 V4 规则包仍保持两维 50/50,不改行为。
+
+## M1 已拍板口径
+
+- 四态命名固定为 `supported / blocked / no_interface / planned`。
+- `edge_supported()` 只有 `supported` 返回 true。
+- `blocked/no_interface/planned` 都返回 false,但保留状态原因供日志/诊断消费。
+- `toutiao/zhihu` 的 `_all` 不保留,改成逐边显式声明。
+- 边目录解耦成平台无关全集。
+- 数据库零改动。
+
+## 验证命令(汇总)
+
+```bash
+uv run pytest tests/test_walk_graph_config.py tests/test_walk_strategy_config.py -q
+uv run pytest tests/test_config_tooling.py tests/test_v4_rule_pack_contract.py tests/test_v4_validator_contract.py -q
+uv run pytest tests/test_v5_golden_corpus_baseline.py -q
+uv run pytest -q
+git diff -- content_agent/business_modules/rule_judgment/evaluator.py content_agent/run_service.py content_agent/integrations/policy_json.py sql
+```
+
+## Unit Test(汇总)
+
+- `tests/test_walk_graph_config.py`
+  - `_validate_profile()` 接受四态,拒绝未知状态。
+  - `edge_supported()` 只有 `supported` 为 true。
+  - `blocked/no_interface/planned` 均为 false。
+  - 平台无关控制边缺省 supported 的旧语义保留。
+- `tests/test_walk_strategy_config.py`
+  - edge catalog 迁出后 graph 仍能校验 9 条边。
+  - strategy identity 不再把 edge catalog 合法性绑死到抖音 strategy。
+  - `walk_rule_pack_binding`、`v4_walk_gate` 仍引用合法 edge。
+- `tests/test_config_tooling.py` / `tests/test_v4_rule_pack_contract.py`
+  - validator 允许第三 active dimension。
+  - validator 仍要求基础两维存在。
+  - scoring rule 引用未知 dimension 仍失败。
+  - 当前真实 douyin V4 pack 仍通过。
+
+## Integrated Test
+
+- 所有 `platform_profiles/*.json` 都能被 `WalkGraphStore.load_profile()` 加载。
+- `toutiao/zhihu` 不再有 `_all`,而是逐边声明。
+- `bilibili/youtube/weixin/github` 不再因 `unsupported/weak` 加载失败。
+- M0 golden gate 继续通过,证明 M1 没误伤 evaluator 行为。
+- `git diff -- content_agent/business_modules/rule_judgment/evaluator.py content_agent/run_service.py content_agent/integrations/policy_json.py sql` 为空。
+
+## 失败归因(汇总)
+
+- profile 加载仍崩:先查 `_validate_profile()` 白名单与 profile 是否仍含 `_all` / `unsupported` / `weak`。
+- 旧平台 author edge 行为变了:先查 `edge_supported()` 是否改变 `blocked` 语义。
+- graph 校验失败:先查 edge catalog 迁出后 9 条边是否与 `walk_graph.json.edges` 完全一致。
+- strategy 校验失败:先查 `walk_rule_pack_binding`、`v4_walk_gate` 是否仍引用合法 edge。
+- rule pack validator 失败:先查是否误删基础两维或 scoring rule 引用未声明 dimension。
+- M0 golden gate 失败:说明误碰 evaluator、threshold、policy loading 或行为字段,本阶段不应发生。
+
+## sub-agent 交叉验证要点
+
+- 代码 sub-agent:确认 M1 文档引用的 loader、validator、测试入口和“不碰业务代码”边界成立。
+- JSON sub-agent:确认非法 profile 状态、`_all`、9 条边、两维 scorecard 证据成立。
+- DB sub-agent:确认 DB 无 profile/config/walk graph 配置表、历史 run 的 `allow_walk` 与 profile gate 已有运行证据。

+ 127 - 0
tech_documents/工程落地/v5_implementation_briefs/M1/M1A_Profile_Edge_Status_And_Four_State_Contract.md

@@ -0,0 +1,127 @@
+# M1A Profile Edge Status 与四态合同 Implementation Brief
+
+状态:本 brief 覆盖 M1 的 profile edge 四态合同、`edge_supported()` 运行语义、profile validator 白名单。M1A 不改 profile JSON 内容本身,JSON 规范化在 M1B。
+
+## 目标
+
+把平台游走边的状态从旧二态扩成四态,并让运行语义足够简单:
+
+1. `supported` 表示当前可走。
+2. `blocked` / `no_interface` / `planned` 当前都不走。
+3. 三种不走原因必须保留在 profile 中,供日志、诊断和后续平台排期使用。
+4. M1A 不改变 walk engine 的扩展流程,只让 loader/validator 能识别新状态并维持旧平台行为。
+
+## 现有证据
+
+- `content_agent/integrations/walk_graph_json.py:47-49`:
+  - `edge_supported(profile, edge_id)` 当前只判断 `status == "supported"`。
+  - profile 缺边时默认 `{"status": "supported"}`。
+- `walk_graph_json.py:74-80`:
+  - `WalkGraphStore.load_profile(platform)` 读取 `tech_documents/数据接口与来源/platform_profiles/{platform}.json`。
+  - 加载时会调用 `_validate_profile()`。
+- `walk_graph_json.py:151-158`:
+  - `_validate_profile()` 当前只接受 `{supported, blocked}`。
+- `scripts/validate_v4_config_contract.py:30`:
+  - `PROFILE_EDGE_STATUSES = {"supported", "blocked"}`。
+- JSON sub-agent 只读验证:
+  - `bilibili/youtube/weixin/github` 中存在 `unsupported` / `weak`。
+  - `toutiao/zhihu` 中 `edges._all` 是字符串,不是 edge object。
+  - 当前 `WalkGraphStore().load_profile("bilibili")` 等会抛 `ValueError profile_edge_status`;`toutiao/zhihu` 会抛 `AttributeError 'str' object has no attribute 'get'`。
+- DB sub-agent 只读验证:
+  - DB 没有 profile/config/walk graph 配置表,M1 只改文件配置和 validator。
+  - 快手 golden run 中 `author_to_works` 被 `edge_blocked_by_platform_profile` 跳过 44 条,证明 profile gate 是运行事实。
+
+## 修改范围
+
+- 后续实现可改 `content_agent/integrations/walk_graph_json.py`:
+  - 新增模块常量 `PROFILE_EDGE_STATUSES = {"supported", "blocked", "no_interface", "planned"}`。
+  - `_validate_profile()` 使用四态白名单。
+  - `edge_supported()` 保持只在 `status == "supported"` 时返回 true。
+- 后续实现可改 `scripts/validate_v4_config_contract.py`:
+  - 同步四态白名单。
+  - 对 profile status 的校验与 runtime loader 保持一致。
+- 后续实现可扩展 `tests/test_walk_graph_config.py`。
+
+## 不修改范围
+
+- 不改 `walk_engine.py` 的边扩展流程。
+- 不改 evaluator 的 `allow_walk` 生产逻辑。
+- 不改 `platform_profiles/*.json`;M1B 才做 profile 内容规范化。
+- 不改 DB schema。
+- 不接新平台 client。
+
+## 涉及文件 / 函数 / 类
+
+- `content_agent/integrations/walk_graph_json.py`
+  - `edge_supported(profile, edge_id) -> bool`
+  - `WalkGraphStore.load_profile(platform) -> dict`
+  - `_validate_profile(profile, edge_ids) -> list[dict]`
+  - `_raise_on_fail(findings, label)`
+- `scripts/validate_v4_config_contract.py`
+  - `PROFILE_EDGE_STATUSES`
+  - profile 校验逻辑
+- `tests/test_walk_graph_config.py`
+
+## 数据合同
+
+`profile.edges.<edge_id>`:
+
+```json
+{
+  "status": "no_interface",
+  "reason": "platform api does not expose author works endpoint"
+}
+```
+
+合法 status:
+
+| status | `edge_supported()` | 说明 |
+|---|---:|---|
+| `supported` | true | 当前可走 |
+| `blocked` | false | 明确屏蔽 |
+| `no_interface` | false | 平台/API 无接口 |
+| `planned` | false | 已规划但当前不走 |
+
+## 实施步骤
+
+1. 在 `walk_graph_json.py` 定义四态常量。
+2. `_validate_profile()` 改用四态常量,未知状态继续 fail。
+3. `edge_supported()` 明确只认 `supported` 为 true。
+4. 保留 profile 缺边默认 supported 的终端/控制边兼容语义,但在 M1B 要求平台业务边逐边显式声明。
+5. `validate_v4_config_contract.py` 同步四态白名单。
+6. 扩展 `tests/test_walk_graph_config.py` 的四态单测。
+
+## 验证命令
+
+```bash
+uv run pytest tests/test_walk_graph_config.py -q
+uv run pytest tests/test_v4_validator_contract.py -q
+uv run pytest tests/test_v5_golden_corpus_baseline.py -q
+```
+
+## Unit Test
+
+- `_validate_profile()` 接受 `supported/blocked/no_interface/planned`。
+- `_validate_profile()` 遇到 `unsupported` / `weak` / 任意未知状态必须 fail。
+- `edge_supported({"edges": {"author_to_works": {"status": "supported"}}}, "author_to_works") is True`。
+- `blocked/no_interface/planned` 均返回 False。
+- profile 未列出的控制边仍保持旧兼容语义:返回 True。
+
+## Integrated Test
+
+- M1B 规范化后,所有 `platform_profiles/*.json` 均可由 `WalkGraphStore().load_profile(platform)` 加载。
+- 快手/视频号现有 blocked author edge 行为不变。
+- M0 golden gate 继续通过。
+
+## 失败归因
+
+- 新状态仍报错:白名单没有同步到 runtime loader 或 config validator。
+- no_interface/planned 被执行:说明 `edge_supported()` 写成了“非 blocked 都可走”。
+- 旧终端边测试失败:缺边默认 supported 的兼容语义被误删。
+- M0 golden gate 失败:误碰 evaluator 或 policy loading。
+
+## sub-agent 交叉验证要点
+
+- 代码 sub-agent:复查 `edge_supported()` 只有 `supported` 返回 true。
+- JSON sub-agent:复查非法状态全部由 M1B 规范化,不在 M1A 中偷改 JSON。
+- DB sub-agent:复查 DB 无 profile 配置表,M1A 不需要 migration。

+ 137 - 0
tech_documents/工程落地/v5_implementation_briefs/M1/M1B_Profile_Normalization_And_All_Expansion.md

@@ -0,0 +1,137 @@
+# M1B Profile 规范化与 `_all` 展开 Implementation Brief
+
+状态:本 brief 覆盖 `platform_profiles/*.json` 的内容规范化。目标是让所有平台 profile 都逐边显式声明,不再使用 `_all` 简写,并把历史非法状态映射到 M1A 拍板的四态。
+
+## 目标
+
+1. 所有平台 profile 都能被 `WalkGraphStore.load_profile(platform)` 加载。
+2. `toutiao/zhihu` 的 `_all` 简写消失,改成逐边显式声明。
+3. `unsupported/weak` 等旧临时状态消失,统一映射到 `no_interface` 或 `planned`。
+4. 不改变已能加载平台的现有行为:抖音 supported、快手/视频号作者边 blocked、小红书现状保留。
+
+## 现有证据
+
+- JSON sub-agent 只读统计:
+  - `douyin/kuaishou/shipinhao/xiaohongshu/bilibili/youtube/weixin/github` 当前都有 6 条业务边:
+    - `search_page_to_content`
+    - `video_to_author`
+    - `author_to_works`
+    - `author_work_to_content`
+    - `video_to_hashtag`
+    - `hashtag_to_query`
+  - `toutiao/zhihu` 当前只有 `edges._all`。
+- 非法状态:
+  - `bilibili`: `author_to_works=unsupported`, `author_work_to_content=unsupported`。
+  - `youtube`: `author_to_works=unsupported`, `author_work_to_content=unsupported`, `video_to_hashtag=weak`。
+  - `weixin`: `author_to_works/author_work_to_content/video_to_hashtag/hashtag_to_query=unsupported`。
+  - `github`: `author_to_works=unsupported`, `author_work_to_content=unsupported`, `video_to_hashtag=weak`。
+  - `toutiao/zhihu`: `edges._all` 为字符串。
+- `tech_documents/数据接口与来源/walk_graph.json` 有 9 条边。
+- `content_agent/integrations/walk_graph_json.py:47-49` 当前缺边默认 supported,所以 `_all` 若展开不完整会有误放行风险。
+
+## 修改范围
+
+- 后续实现可改 `tech_documents/数据接口与来源/platform_profiles/*.json`:
+  - 规范化非法 edge status。
+  - 删除 `_all`。
+  - 为 `toutiao/zhihu` 写出 9 条显式 edge。
+- 后续实现可扩展 `tests/test_walk_graph_config.py` 或新增 profile contract test。
+
+## 不修改范围
+
+- 不改 `walk_engine.py`。
+- 不改平台 client。
+- 不改 DB。
+- 不改 Excel。
+- 不新增 profile 以外的业务配置。
+
+## 涉及文件 / 函数 / 类
+
+- `tech_documents/数据接口与来源/platform_profiles/*.json`
+- `content_agent/integrations/walk_graph_json.py`
+  - `WalkGraphStore.load_profile(platform)`
+  - `_validate_profile(profile, edge_ids)`
+  - `edge_supported(profile, edge_id)`
+- `tests/test_walk_graph_config.py`
+- 可新增 `tests/test_v5_m1_profile_edge_status_contract.py`
+
+## 数据合同
+
+### `_all` 展开边全集
+
+`toutiao/zhihu` 的 `_all` 必须展开为 9 条:
+
+```text
+search_page_to_content
+video_to_author
+author_to_works
+author_work_to_content
+video_to_hashtag
+hashtag_to_query
+decision_to_asset
+path_stop
+budget_downgrade
+```
+
+### 状态映射建议
+
+| 旧值 | 新值 | 原因 |
+|---|---|---|
+| `unsupported` | `no_interface` | 表示当前平台/API 无该边接口 |
+| `weak` | `planned` | 表示可能可做但当前不纳入执行 |
+| `_all: blocked(...)` | 逐边 `blocked` | 表示整平台所有边阻断,必须显式声明避免缺省 supported 泄漏 |
+
+### 平台业务边与控制边
+
+- 6 条业务边:搜索页、视频作者、作者作品、作者作品内容、视频标签、标签回搜。
+- 3 条控制边:`decision_to_asset`, `path_stop`, `budget_downgrade`。
+- 为避免 `_all` 语义歧义,`toutiao/zhihu` 9 条都显式声明。
+
+## 实施步骤
+
+1. 读取 `walk_graph.json` 的 9 条 edge_id 作为展开全集。
+2. 将 `toutiao.json` / `zhihu.json` 的 `_all` 删除,写成 9 条 edge object。
+3. 将 `unsupported` 映射为 `no_interface`。
+4. 将 `weak` 映射为 `planned`。
+5. 保留 `douyin/kuaishou/shipinhao/xiaohongshu` 当前可加载行为。
+6. 增加全 profile 加载测试。
+
+## 验证命令
+
+```bash
+uv run python - <<'PY'
+from content_agent.integrations.walk_graph_json import WalkGraphStore
+for platform in ["douyin","kuaishou","shipinhao","xiaohongshu","bilibili","youtube","weixin","github","toutiao","zhihu"]:
+    WalkGraphStore().load_profile(platform)
+print("all profiles loaded")
+PY
+uv run pytest tests/test_walk_graph_config.py -q
+uv run pytest tests/test_v5_golden_corpus_baseline.py -q
+```
+
+## Unit Test
+
+- `bilibili/youtube/weixin/github` 不再包含 `unsupported` / `weak`。
+- `toutiao/zhihu` 不再包含 `_all`。
+- `toutiao/zhihu` 的 edge key 集合等于 9 条真实边。
+- 所有 profile 的 status 均在四态白名单内。
+
+## Integrated Test
+
+- 所有 profile 均可加载。
+- 快手/视频号 blocked author edge 仍返回 False。
+- 抖音核心业务边仍返回 True。
+- M0 golden gate 继续通过。
+
+## 失败归因
+
+- toutiao/zhihu 仍 AttributeError: `_all` 没删干净或仍是字符串。
+- 新平台仍 ValueError:还有 `unsupported/weak` 或白名单未同步。
+- 业务边被误放行:逐边声明缺失导致缺省 supported 泄漏。
+- 旧平台行为变了:检查映射时是否把 blocked 改成了 supported。
+
+## sub-agent 交叉验证要点
+
+- JSON sub-agent:复查每个 profile 的 edge/status 表。
+- 代码 sub-agent:复查 loader 实际加载全部平台。
+- DB sub-agent:确认这些 profile 不来自 DB,M1B 无 migration。

+ 130 - 0
tech_documents/工程落地/v5_implementation_briefs/M1/M1C_Walk_Edge_Catalog_And_Strategy_Decoupling.md

@@ -0,0 +1,130 @@
+# M1C Walk Edge Catalog 与策略解耦 Implementation Brief
+
+状态:本 brief 覆盖 `walk_edge_catalog` 从抖音策略文件语义中解耦,成为平台无关边目录。M1C 不重写 walk engine,不改变 `allow_walk` 判定,不改运行时 walk action schema。
+
+## 目标
+
+1. `WalkGraphStore.load_graph()` 不再依赖抖音命名的策略文件作为 edge catalog 真源。
+2. `walk_edge_catalog` 作为平台无关总章程,与 `walk_graph.json.edges` 保持 9 条边一致。
+3. `WalkStrategyStore` 继续负责 V4 walk gate、rule pack binding、fact contract。
+4. 保留 `v4_walk_gate` 对 `author_to_works` / `hashtag_to_query` 的 allow_walk 消费合同。
+
+## 现有证据
+
+- `content_agent/integrations/walk_graph_json.py:19`:
+  - `EDGE_CATALOG_PATH = Path("product_documents/抖音游走策略/douyin_walk_strategy.v1.json")`。
+- `walk_graph_json.py:56-62`:
+  - `load_graph()` 读取 `walk_graph.json`,再从 `EDGE_CATALOG_PATH` 读取 `walk_edge_catalog` 做 edge 校验。
+- `content_agent/integrations/walk_strategy_json.py:11`:
+  - `WALK_STRATEGY_PATH` 固定抖音策略文件。
+- `walk_strategy_json.py:17-22`:
+  - `REQUIRED_SECTIONS` 当前包含 `walk_edge_catalog`, `walk_rule_pack_binding`, `v4_walk_gate`, `walk_fact_contract`。
+- `walk_strategy_json.py:82-92`:
+  - `_check_identity()` 硬断言 `strategy_id == douyin_walk_strategy_v1` 与 source_of_truth。
+- JSON 证据:
+  - `walk_graph.json` 有 9 条边。
+  - `douyin_walk_strategy.v1.json` 的 `walk_edge_catalog` 也是 9 条,但文件路径、strategy_id、platform 都是抖音命名。
+- DB 证据:
+  - DB 不存 walk graph/profile/config 表,这些配置来自本地 JSON。
+
+## 修改范围
+
+- 后续实现可新增或调整平台无关 edge catalog 文件,建议放在 `tech_documents/数据接口与来源/` 下。
+- 后续实现可改 `walk_graph_json.EDGE_CATALOG_PATH` 指向平台无关 catalog。
+- 后续实现可改 `walk_strategy_json.validate_walk_strategy_config()`:
+  - strategy 文件不再必须承载 edge catalog。
+  - `walk_rule_pack_binding`、`v4_walk_gate` 校验使用平台无关 catalog edge ids。
+- 后续实现可扩展 `tests/test_walk_strategy_config.py`。
+
+## 不修改范围
+
+- 不改 `content_agent/business_modules/walk_engine.py`。
+- 不改 `content_agent/business_modules/rule_judgment/evaluator.py`。
+- 不改 `content_agent/integrations/policy_json.py`,除非只是读取已有 walk strategy source ref。
+- 不改 DB schema。
+- 不改变 `v4_walk_gate` 的业务语义。
+
+## 涉及文件 / 函数 / 类
+
+- `content_agent/integrations/walk_graph_json.py`
+  - `EDGE_CATALOG_PATH`
+  - `WalkGraphStore.load_graph()`
+  - `_validate_graph(graph, catalog_ids)`
+- `content_agent/integrations/walk_strategy_json.py`
+  - `WALK_STRATEGY_PATH`
+  - `REQUIRED_SECTIONS`
+  - `WalkStrategyStore.load_walk_strategy()`
+  - `validate_walk_strategy_config(...)`
+  - `_check_identity(...)`
+  - `_check_edge_refs(...)`
+  - `_check_v4_walk_gate(...)`
+- `tech_documents/数据接口与来源/walk_graph.json`
+- `product_documents/抖音游走策略/douyin_walk_strategy.v1.json`
+- `tests/test_walk_strategy_config.py`
+- `tests/test_walk_graph_config.py`
+
+## 数据合同
+
+平台无关 edge catalog 必须包含:
+
+| edge_id | 类型 |
+|---|---|
+| `search_page_to_content` | 平台业务边 |
+| `video_to_author` | 平台业务边 |
+| `author_to_works` | 平台业务边 |
+| `author_work_to_content` | 平台业务边 |
+| `video_to_hashtag` | 平台业务边 |
+| `hashtag_to_query` | 平台业务边 |
+| `decision_to_asset` | 控制边 |
+| `path_stop` | 控制边 |
+| `budget_downgrade` | 控制边 |
+
+保留策略内合同:
+
+- `walk_rule_pack_binding`:edge -> rule pack 绑定。
+- `v4_walk_gate`:消费 `rule_decisions.jsonl[].decision_replay_data.allow_walk`。
+- `walk_fact_contract`:runtime 文件唯一键与字段合同。
+
+## 实施步骤
+
+1. 从 `douyin_walk_strategy.v1.json.walk_edge_catalog` 复制 9 条边到平台无关 catalog。
+2. 更新 `walk_graph_json.EDGE_CATALOG_PATH` 指向平台无关 catalog。
+3. `WalkGraphStore.load_graph()` 继续用 catalog ids 校验 `walk_graph.json.edges`。
+4. `walk_strategy_json` 保留 strategy identity 校验,但不要让 graph catalog 合法性依赖抖音 strategy identity。
+5. 如迁出后 strategy 不再含 `walk_edge_catalog`,调整 `REQUIRED_SECTIONS` 和相关测试。
+6. 更新测试,证明 graph 和 strategy 均引用同一组合法 edge ids。
+
+## 验证命令
+
+```bash
+uv run pytest tests/test_walk_graph_config.py tests/test_walk_strategy_config.py -q
+uv run python scripts/validate_walk_strategy_config.py
+uv run pytest tests/test_v5_golden_corpus_baseline.py -q
+```
+
+## Unit Test
+
+- `WalkGraphStore.load_graph()` 读取平台无关 catalog 后仍通过。
+- graph edges 与 edge catalog ids 完全一致。
+- strategy 中 `walk_rule_pack_binding` 引用的 edge 都在 catalog 中。
+- `v4_walk_gate.applies_to_edges` 仍覆盖 `hashtag_to_query` 与 `author_to_works`。
+- strategy identity 不再决定 graph catalog 是否合法。
+
+## Integrated Test
+
+- 全量 walk config gate 通过。
+- M1B 后所有 profile 可加载。
+- 快手/抖音 golden run 的 evaluator gate 继续通过。
+
+## 失败归因
+
+- graph edge_not_in_catalog:平台无关 catalog 与 `walk_graph.json.edges` 不一致。
+- strategy section_missing:迁出 `walk_edge_catalog` 后 validator 仍把它当 required。
+- v4_walk_gate_edges 失败:gate 仍应覆盖 author/tag 两条扩展边。
+- M0 golden gate 失败:误碰 evaluator 或 policy loading。
+
+## sub-agent 交叉验证要点
+
+- 代码 sub-agent:复查 graph loader 不再依赖抖音 catalog path。
+- JSON sub-agent:复查平台无关 catalog 与 9 条 graph edge 一致。
+- DB sub-agent:复查 DB 不存 edge catalog,M1C 无 migration。

+ 128 - 0
tech_documents/工程落地/v5_implementation_briefs/M1/M1D_Rule_Pack_Dimension_Validator_Relaxation.md

@@ -0,0 +1,128 @@
+# M1D Rule Pack Dimension Validator 放开 Implementation Brief
+
+状态:本 brief 覆盖 V4 规则包 validator 的两维硬锁放开。M1D 只改 validator/配置合同,不改当前规则包评分语义,不改 evaluator 公式。
+
+## 目标
+
+1. 让后续 M2/M3 可以为不同平台声明不同 scorecard 维度。
+2. 当前真实 V4 规则包仍保持 `query_relevance/platform_performance` 两维 50/50。
+3. validator 从“active dimensions 必须精确等于两维”改为“基础两维必须存在,额外维度允许但必须合法”。
+4. scoring rule 引用未知 dimension 仍然失败。
+
+## 现有证据
+
+- `scripts/validate_rule_pack_config.py:97-103`:
+  - active dimensions keys 必须等于 `["query_relevance", "platform_performance"]`。
+  - 每个维度必须 `max_score=50` 且 `weight_percent=50`。
+- `scripts/validate_v4_config_contract.py:468-475`:
+  - 重复锁死 active keys 精确等于两维。
+- `product_documents/规则包/douyin_rule_packs.v1.json:292-348`:
+  - 当前 `scorecard.schema_version = v4_scorecard.v1`。
+  - active dimensions 为 `query_relevance` 和 `platform_performance`,各 50/50。
+  - scoring_rules 只有这两维。
+- `tech_documents/工程落地/14_V5阶段开发计划.md` M1 已明确本阶段“不动 evaluator 打分逻辑”。
+- M0 golden gate 已能复刻抖音三维与快手两维历史 decision,用于保护 M1 不误伤 evaluator 行为。
+
+## 修改范围
+
+- 后续实现可改 `scripts/validate_rule_pack_config.py`:
+  - `_check_v4_rule_pack()` 的维度校验从 exact list 改为包含基础两维 + 维度合法性。
+- 后续实现可改 `scripts/validate_v4_config_contract.py`:
+  - `_check_v4_rule_pack_contract()` 同步放开 exact two-dimension 判断。
+- 后续实现可扩展 `tests/test_config_tooling.py` 与 `tests/test_v4_rule_pack_contract.py`。
+
+## 不修改范围
+
+- 不改 `content_agent/business_modules/rule_judgment/evaluator.py`。
+- 不改当前 `douyin_rule_packs.v1.json` 的 scorecard dimensions、scoring_rules、thresholds。
+- 不改 `score_thresholds.json`。
+- 不改 `run_record/validation.py` 或 `flow_ledger_service.py`;这些属于 M2 权重搬手册联动。
+- 不改 Excel。
+
+## 涉及文件 / 函数 / 类
+
+- `scripts/validate_rule_pack_config.py`
+  - `_check_v4_rule_pack(findings, pack)`
+  - `validate_rule_pack_config(pkg)`
+- `scripts/validate_v4_config_contract.py`
+  - `_check_v4_rule_pack_contract(findings, pkg)`
+  - `validate_v4_config_contract(root)`
+- `tests/test_config_tooling.py`
+- `tests/test_v4_rule_pack_contract.py`
+- `product_documents/规则包/douyin_rule_packs.v1.json`(只作为真实输入验证,不改)
+
+## 数据合同
+
+M1 后 V4 scorecard validator:
+
+```json
+{
+  "scorecard": {
+    "schema_version": "v4_scorecard.v1",
+    "dimensions": [
+      {"key": "query_relevance", "runtime_status": "active"},
+      {"key": "platform_performance", "runtime_status": "active"},
+      {"key": "fifty_plus", "runtime_status": "active"}
+    ],
+    "scoring_rules": [
+      {"dimension_key": "query_relevance"},
+      {"dimension_key": "platform_performance"},
+      {"dimension_key": "fifty_plus"}
+    ]
+  }
+}
+```
+
+校验要求:
+
+- `schema_version` 仍必须是 `v4_scorecard.v1`。
+- active dimensions 必须包含 `query_relevance` 与 `platform_performance`。
+- 允许额外 active dimension。
+- 每条 scoring rule 的 `dimension_key` 必须存在于 dimensions。
+- 当前真实 douyin V4 pack 仍通过。
+
+## 实施步骤
+
+1. 在 `validate_rule_pack_config.py` 中替换 exact keys 校验。
+2. 移除逐行 50/50 硬校验,改为基础维度存在与 scoring rule 引用合法。
+3. 在 `validate_v4_config_contract.py` 同步同一口径。
+4. 增加 validator 允许第三维的单测。
+5. 增加删除基础维度仍失败的单测。
+6. 跑当前真实 rule pack validator,确认仍 pass。
+7. 跑 M0 golden gate,确认 evaluator 行为零变化。
+
+## 验证命令
+
+```bash
+uv run pytest tests/test_config_tooling.py tests/test_v4_rule_pack_contract.py tests/test_v4_validator_contract.py -q
+uv run python scripts/validate_rule_pack_config.py
+uv run python scripts/validate_v4_config_contract.py
+uv run pytest tests/test_v5_golden_corpus_baseline.py -q
+```
+
+## Unit Test
+
+- 当前真实 `douyin_rule_packs.v1.json` validator pass。
+- 在内存复制 pack,添加第三 active dimension + scoring rule,validator pass。
+- 删除 `query_relevance` 或 `platform_performance`,validator fail。
+- scoring rule 引用未知 dimension,validator fail。
+- `schema_version` 非 `v4_scorecard.v1`,validator fail。
+
+## Integrated Test
+
+- `scripts/run_config_gate.py` 继续通过。
+- M0 golden gate 继续通过。
+- `git diff -- content_agent/business_modules/rule_judgment/evaluator.py` 为空。
+
+## 失败归因
+
+- 当前真实 pack fail:放开逻辑误伤两维现状。
+- 第三维仍 fail:exact list 判断未删干净。
+- 未知 dimension 没 fail:scoring rule FK 校验被误删。
+- golden gate fail:误碰 evaluator/threshold/policy loading,本阶段不应发生。
+
+## sub-agent 交叉验证要点
+
+- 代码 sub-agent:复查两个 validator 的维度口径一致。
+- JSON sub-agent:复查当前真实规则包仍是两维 50/50,没有被 M1D 修改。
+- DB sub-agent:复查 scorecard 在 DB 中是 JSON 行为字段,M1D 不需要 migration。

+ 156 - 0
tech_documents/工程落地/v5_implementation_briefs/M1/M1E_Acceptance_Commands_And_Subagent_Audit.md

@@ -0,0 +1,156 @@
+# M1E 验收命令与 Subagent 审计 Implementation Brief
+
+状态:本 brief 汇总 V5 M1 的测试矩阵、验收命令、DB 只读证据、失败归因和 sub-agent 审计口径。M1E 不新增业务功能。
+
+## 目标
+
+- 证明 M1 修掉 profile 加载炸弹。
+- 证明边目录已从抖音命名策略中解耦为平台无关总章程。
+- 证明 rule pack validator 已为多维铺路,但当前 V4 行为零变化。
+- 证明 M1 没改 DB schema、evaluator、RunService、policy_json 或新平台 client。
+
+## 现有证据
+
+- M0 golden gate 已新增 `tests/test_v5_golden_corpus_baseline.py`,能复刻抖音 110 条、快手 51 条历史 decision。
+- DB sub-agent 只读确认:
+  - `content-deconstruction-supply`,MySQL `8.0.46`,事务只读。
+  - 没有 profile/config/walk graph 配置表。
+  - 抖音 golden run 有 265 条 walk actions;快手 golden run 有 142 条 walk actions。
+  - 快手 `author_to_works` 被 profile gate 跳过 44 条。
+- 代码 sub-agent 确认:
+  - M1 不应改 evaluator、policy_json、RunService、新平台 client、DB schema。
+- JSON sub-agent 确认:
+  - 当前非法 profile 与 `_all` 问题都来自本地 JSON。
+  - 当前规则包仍是两维 50/50。
+
+## 修改范围
+
+- 后续实现可新增/扩展 M1 相关 tests。
+- 后续实现可更新 config gate 命令覆盖 M1 校验。
+- 文档阶段只新增本 M1 brief 目录,不改代码或配置。
+
+## 不修改范围
+
+- 不改业务代码。
+- 不改 DB schema。
+- 不改 Excel。
+- 不调用真实平台接口。
+- 不更新 M0 golden corpus。
+
+## 涉及文件 / 函数 / 类
+
+- `tests/test_walk_graph_config.py`
+- `tests/test_walk_strategy_config.py`
+- `tests/test_config_tooling.py`
+- `tests/test_v4_rule_pack_contract.py`
+- `tests/test_v4_validator_contract.py`
+- `tests/test_v5_golden_corpus_baseline.py`
+- `scripts/validate_rule_pack_config.py`
+- `scripts/validate_v4_config_contract.py`
+- `scripts/run_config_gate.py`
+
+## 测试矩阵
+
+| 层级 | 测试 | 验证点 |
+|---|---|---|
+| Unit | walk graph config | 四态、`edge_supported()`、profile 缺边控制边兼容 |
+| Unit | walk strategy config | edge catalog 解耦、strategy binding/gate/fact contract 合法 |
+| Unit | rule pack validator | 允许额外维度、基础两维必需、未知维度失败 |
+| Integrated | profile 全平台加载 | 10 个 profile 都能 load,无 `_all` / `unsupported` / `weak` |
+| Integrated | config gate | validator 与真实 JSON 全通过 |
+| Regression | M0 golden gate | 161 条历史 decision 行为零变化 |
+| Guardrail | git diff | evaluator/RunService/policy_json/sql 无改动 |
+
+## 验证命令
+
+```bash
+# M1 unit/config
+uv run pytest tests/test_walk_graph_config.py tests/test_walk_strategy_config.py -q
+uv run pytest tests/test_config_tooling.py tests/test_v4_rule_pack_contract.py tests/test_v4_validator_contract.py -q
+
+# V5-M0 golden evaluator gate
+uv run pytest tests/test_v5_golden_corpus_baseline.py -q
+
+# Config validators
+uv run python scripts/validate_rule_pack_config.py
+uv run python scripts/validate_v4_config_contract.py
+python3 scripts/run_config_gate.py
+
+# Full regression
+uv run pytest -q
+
+# Guardrail:these should stay empty
+git diff -- content_agent/business_modules/rule_judgment/evaluator.py content_agent/run_service.py content_agent/integrations/policy_json.py sql
+```
+
+## Unit Test
+
+- `tests/test_walk_graph_config.py`
+  - 四态白名单。
+  - unknown status fail。
+  - `supported` true,其他三态 false。
+  - terminal/control edge 缺省 supported 兼容。
+- `tests/test_walk_strategy_config.py`
+  - catalog 9 条边。
+  - graph edges 与 catalog 一致。
+  - strategy binding/gate/fact contract 引用合法 edge。
+- `tests/test_config_tooling.py` / `tests/test_v4_rule_pack_contract.py`
+  - third active dimension pass。
+  - missing base dimension fail。
+  - unknown dimension reference fail。
+
+## Integrated Test
+
+- 所有 platform profile 加载通过。
+- `toutiao/zhihu` 无 `_all`。
+- `bilibili/youtube/weixin/github` 无 `unsupported/weak`。
+- M0 golden gate 通过。
+- Full pytest 通过。
+
+## DB / 只读验收
+
+DB 不进入常规 pytest/CI,只作为审计证据:
+
+- CI 不应依赖内网 MySQL、`.env` 密钥或真实外部状态。
+- M1 目标是本地配置/validator 变更,不是 DB 在线验证。
+- M0 golden gate 已把关键 evaluator 行为锁到本地 corpus。
+
+可选只读审计 SQL 摘要:
+
+```sql
+SET SESSION TRANSACTION READ ONLY;
+START TRANSACTION;
+SELECT DATABASE(), VERSION(), @@transaction_read_only;
+SELECT table_name
+FROM information_schema.tables
+WHERE table_schema = DATABASE()
+  AND (LOWER(table_name) LIKE '%profile%'
+       OR LOWER(table_name) LIKE '%walk%graph%'
+       OR LOWER(table_name) LIKE '%walk%config%'
+       OR LOWER(table_name) LIKE '%config%');
+ROLLBACK;
+```
+
+期望:只读事务成立;profile/config/walk graph 配置表为空。
+
+## 失败归因
+
+- profile 全平台加载失败:查 M1A/M1B 白名单与 JSON 规范化。
+- strategy config 失败:查 M1C catalog 迁出与 `REQUIRED_SECTIONS`。
+- validator 失败:查 M1D 是否仍有 exact two-dimension 判断。
+- M0 golden gate 失败:查是否误碰 evaluator、threshold、policy loading。
+- full pytest 失败:先按测试名归到 M1A-D 对应 brief,不要放宽 golden gate。
+- guardrail diff 非空:误改了 M1 红线文件,应撤回相关改动。
+
+## sub-agent 交叉验证要点
+
+- 代码 sub-agent:
+  - 复查 loader、validator、测试入口与文档一致。
+  - 复查 evaluator、RunService、policy_json 无改动。
+- JSON sub-agent:
+  - 复查所有 profile 状态合法且无 `_all`。
+  - 复查 edge catalog 9 条边一致。
+  - 复查真实规则包仍两维 50/50。
+- DB sub-agent:
+  - 复查 M1 无 DB migration。
+  - 复查 golden runs 的 `allow_walk` 与 profile gate 证据仍成立。

+ 163 - 0
tech_documents/工程落地/v5_implementation_briefs/M2/00_M2_Brief_Index.md

@@ -0,0 +1,163 @@
+# V5 M2 通机制 Brief Index
+
+状态:本目录覆盖 V5 M2——激活按平台选规则包、把 V4 打分权重从三处硬编码搬进规则手册、把抖音迁成第一本标准手册并保持行为零变化。M2 只做机制,不接新平台、不改门槛数值、不改游走预算、不重组 Excel、不做 DB migration。
+
+## 目标定位
+
+M2 的目标是把 V4 当前已经跑通的行为变成可配置、可按平台派发的规则包机制:
+
+1. `PolicyBundleStore.load_policy_bundle()` 增加 `platform` 参数,默认仍为 `douyin`,减少老调用点改动。
+2. `graph.load_policy()` 从 `state["platform"]` 透传 platform,让运行链路具备按平台选规则包的入口。
+3. `JsonPolicyBundleStore` 去掉 dispatch 与 `rule_pack_by_entity` 中的抖音字面量,按 platform 选择 enabled dispatch。
+4. `douyin_rule_packs.v1.json` 补齐标准 scorecard 权重字段,明确当前抖音历史行为不是 JSON 已表达三维,而是 evaluator 代码硬编码产生。
+5. `evaluator._v4_scorecard_total()` 从规则包读取权重,保留无 50+ 块 50/50、抖音 ok 35/35/30、not_attempted 35/35、unavailable/incomplete 技术失败。
+6. `run_record/validation.py` 与 `flow_ledger_service.py` 跟 evaluator 使用同一权重来源,不能继续各自维护硬编码副本。
+7. M0 golden evaluator gate 必须继续 161 条逐条通过,作为 M2 行为零变化门。
+
+## 子 brief 执行口径(A->E 线性)
+
+| 子 brief | 文件 | 核心交付 | 依赖 |
+|---|---|---|---|
+| M2A | `M2A_Policy_Bundle_Platform_Dispatch_Contract.md` | `load_policy_bundle(strategy_version, platform)`、graph 透传 platform、dispatch 按平台选择 | M1 rule pack validator 放开 |
+| M2B | `M2B_Douyin_Rule_Pack_Standard_Scorecard_Contract.md` | 抖音规则包补标准 scorecard 权重/作用域字段,但数值语义零变化 | M2A |
+| M2C | `M2C_Config_Driven_V4_Score_Total_Contract.md` | evaluator 打分公式从硬编码搬到规则包,保留历史三维/两维行为 | M2B |
+| M2D | `M2D_Validation_And_Flow_Ledger_Weight_Parity.md` | validation 与 flow ledger 去掉权重副本,跟 evaluator 权重来源一致 | M2C |
+| M2E | `M2E_Acceptance_Commands_And_Subagent_Audit.md` | 验收命令、测试矩阵、DB 只读证据、guardrail 与失败归因 | M2A-M2D |
+
+## 不处理范围(红线)
+
+- 不接新平台 client,不改 `content_agent/integrations/douyin.py` / `kuaishou.py` / `shipinhao.py`。
+- 不改 `content_agent/run_service.py` 的平台工厂与真实接口调用方式。
+- 不改 `content_agent/business_modules/walk_engine.py`、walk budget、walk profile 或 walk strategy 行为。
+- 不改 `tech_documents/数据接口与来源/score_thresholds.json` 的门槛数值。
+- 不改 Excel 或 `tech_documents/规则包映射`。
+- 不新增 DB 表、列、索引或 migration。
+- 不改 M0 golden corpus、fixture、历史 `rule_decisions.jsonl`。
+- 不做 M3 平台手册填充;M2 只把机制打通,新平台规则包留给 M3。
+
+## 现有证据(共享,子 brief 各自细引)
+
+- 代码证据:
+  - `content_agent/integrations/policy_json.py:16-30` 的 `load_policy_bundle(strategy_version)` 无 platform 参数,规则包路径写死 `product_documents/规则包/douyin_rule_packs.v1.json`。
+  - `policy_json.py:88-126` 的 `_select_dispatch()` / `_enabled_dispatches()` 已有 platform 过滤机制,但默认值仍是 `douyin`。
+  - `policy_json.py:149-156` 的 `_build_rule_pack_by_entity()` 仍用 `dispatch.get("platform") == "douyin"`。
+  - `content_agent/graph.py:144-145` 的 `load_policy()` 只传 `state["strategy_version"]`,未传 `state["platform"]`。
+  - `content_agent/interfaces.py:84-85` 与 `content_agent/business_modules/policy_version.py:8-9` 的协议仍只接 `strategy_version`。
+  - `content_agent/business_modules/rule_judgment/evaluator.py:209-259` 的 `_v4_scorecard_total()` 硬编码非 50+ 50/50、抖音 ok 35/35/30、not_attempted 35/35。
+  - `content_agent/business_modules/run_record/validation.py:963-974` 按同一套硬编码反算分数。
+  - `content_agent/flow_ledger_service.py:1494-1506` 展示层再维护一份 50/50 与 35/35/30。
+- JSON/fixture 证据:
+  - `product_documents/规则包/douyin_rule_packs.v1.json` 只有一条 enabled dispatch:`dispatch_content / platform=douyin`。
+  - 当前 scorecard 只有 `query_relevance` 与 `platform_performance` 两个 active dimensions,各 `weight_percent=50`。
+  - `tech_documents/数据接口与来源/score_thresholds.json` 已有 `default` + `douyin`,门槛配置化不是 M2 主工作。
+  - `tests/fixtures/cases/v4_douyin_57663/input/rule_decisions.jsonl` 有 110 条抖音历史 decisions,覆盖 `fifty_plus_status=ok/not_attempted/unavailable`。
+  - `tests/fixtures/cases/v4_kuaishou_57758/input/rule_decisions.jsonl` 有 51 条快手历史 decisions,无 `fifty_plus_status`,走两维路径。
+- DB 证据:
+  - DB sub-agent 只读连接 `content-deconstruction-supply` 成功,MySQL `8.0.46`,事务只读。
+  - `information_schema` 未发现 profile/config/rule_pack/threshold/weight/walk config 表;M2 无 migration。
+  - 抖音 golden run:110 decisions、265 walk actions、可算 92 条按 35/35/30 或 35/35 反算 `0 mismatch`。
+  - 快手 golden run:51 decisions、142 walk actions、可算 47 条按 50/50 反算 `0 mismatch`。
+
+## 数据合同(汇总)
+
+### Policy Bundle Platform
+
+```python
+PolicyBundleStore.load_policy_bundle(strategy_version: str, platform: str = "douyin") -> dict[str, Any]
+policy_version.run(strategy_version: str, policy_store: PolicyBundleStore, platform: str = "douyin") -> dict[str, Any]
+```
+
+M2 保留 `platform="douyin"` 默认值,用于兼容现有测试和脚本。`graph.load_policy()` 必须从运行 state 读取 platform 并透传。
+
+### Dispatch Selection
+
+dispatch 选择条件保持既有五元组并加入调用方 platform:
+
+- `dispatch_enabled == true`
+- `dispatch.platform == platform`
+- `dispatch.runtime_stage == "V1.0"`
+- `dispatch.strategy_version == strategy_version`
+- `dispatch.target_entity/content_format` 匹配调用参数
+
+未知平台没有 enabled dispatch 时必须清晰失败,不能静默落到抖音规则包。
+
+### Score Weight Contract
+
+规则包 scorecard 的 dimension 继续使用现有 `key` / `weight_percent` / `runtime_status`,M2 可补充最小字段:
+
+- `platform_scope`:该维度适用的平台列表或 `["*"]`。
+- `active`:布尔兼容字段,与 `runtime_status=active` 语义一致。
+- `score_source_path`:运行输入里取分位置,只作为配置说明与 validator 合同,不引入复杂引擎。
+
+M2 不改门槛数值,不把 `score_thresholds.json` 合进规则包。
+
+### V4 Historical Behavior
+
+- 无 `content_audience_50plus` 块:两维 50/50。
+- 抖音 `fifty_plus_status=ok`:三维 35/35/30。
+- 抖音 `fifty_plus_status=not_attempted`:只算 query/platform 两项 35/35,不归一化。
+- 抖音 `unavailable/incomplete` 或 50+ 缺分:仍为技术失败,`total_score=None`。
+
+## M2 已拍板口径
+
+- 抖音 35/35/30 与非抖音 50/50 数值原样搬配置,不改算法结果。
+- 抖音是第一本标准手册;其他平台手册填充留给 M3。
+- profile 与策略手册继续分离;M1 的 platform profile 不合并进规则包。
+- 规则包路径/命名在 M2 只实现平台参数和默认兼容,不批量创建新平台包。
+- validation 与 flow ledger 必须跟 evaluator 同源,不能继续各自写权重常量。
+
+## 验证命令(汇总)
+
+```bash
+uv run pytest tests/test_policy_dispatch.py -q
+uv run pytest tests/test_config_tooling.py tests/test_v4_rule_pack_contract.py -q
+uv run pytest tests/test_rule_judgment_scorecard.py tests/test_douyin_fifty_plus_scoring.py -q
+uv run pytest tests/test_score_items_platform.py tests/test_v4_validator_contract.py -q
+uv run pytest tests/test_v5_golden_corpus_baseline.py -q
+uv run python scripts/validate_rule_pack_config.py
+uv run python scripts/validate_v4_config_contract.py
+python3 scripts/run_config_gate.py
+git diff -- content_agent/integrations/douyin.py content_agent/integrations/kuaishou.py content_agent/integrations/shipinhao.py content_agent/business_modules/walk_engine.py sql tech_documents/规则包映射
+```
+
+## Unit Test(汇总)
+
+- `tests/test_policy_dispatch.py`
+  - 老调用 `load_policy_bundle("V4")` 默认仍选抖音。
+  - `load_policy_bundle("V4", platform="douyin")` 与老调用行为一致。
+  - synthetic 多平台 dispatch 按 platform 选择。
+  - 同平台多 enabled dispatch 冲突仍报 `CONFIG_RULE_PACK_DISPATCH_CONFLICT`。
+  - 未知平台无 dispatch 清晰失败。
+- `tests/test_config_tooling.py` / `tests/test_v4_rule_pack_contract.py`
+  - scorecard dimension 可带 `platform_scope` / `active` / 权重字段。
+  - 基础两维仍必须存在。
+  - 额外维度合法但 scoring rule 不能引用未知 dimension。
+  - 当前真实 douyin rule pack 仍通过。
+- `tests/test_rule_judgment_scorecard.py` / `tests/test_douyin_fifty_plus_scoring.py`
+  - 50/50、35/35/30、not_attempted 35/35、画像失败技术重试全部保持。
+  - 改规则包权重能影响内存 synthetic bundle,证明 evaluator 不再读硬编码。
+- `tests/test_score_items_platform.py` / `tests/test_v4_validator_contract.py`
+  - 展示权重与 validation 反算都来自同一来源。
+  - legacy scorecard 无新增权重字段时仍可兼容历史 fixture。
+
+## Integrated Test
+
+- M0 golden gate 继续通过:抖音 110 条、快手 51 条全部 exact replay。
+- Config validators 通过,证明规则包新增字段被合同接纳。
+- Config gate 通过,证明 M1/M2 配置合同没有互相打架。
+- Guardrail diff 为空,证明 M2 未误进平台 client、walk engine、DB schema、Excel/映射范围。
+
+## 失败归因(汇总)
+
+- policy loading 失败:先查 platform 是否从 graph/state 透传,再查 `_select_dispatch()` 与 `_build_rule_pack_by_entity()` 是否仍有 `douyin` 字面量。
+- 抖音 golden 分数变化:先查规则包权重字段与 evaluator 读取逻辑,尤其 `not_attempted` 是否误归一化。
+- 快手 golden 分数变化:先查无 50+ 块是否仍走 50/50。
+- validation 失败但 evaluator 通过:查 `run_record/validation.py` 是否仍维护旧硬编码副本。
+- flow ledger 展示权重错误:查 `_v4_display_weights()` 是否从 scorecard/规则包权重证据读取。
+- guardrail diff 非空:说明 M2 误改 M3/M4/DB/Excel 范围,应撤回。
+
+## sub-agent 交叉验证要点
+
+- 代码 sub-agent:确认 platform 参数链路、三处权重副本、测试入口和红线文件。
+- JSON/fixture sub-agent:确认规则包 dispatch/scorecard 真实状态、score thresholds 不属于 M2 主改动、M0 corpus 三维/两维覆盖。
+- DB sub-agent:确认无配置表/权重表/migration 需求,并用两条 golden run 反算公式 `0 mismatch` 支撑行为合同。

+ 123 - 0
tech_documents/工程落地/v5_implementation_briefs/M2/M2A_Policy_Bundle_Platform_Dispatch_Contract.md

@@ -0,0 +1,123 @@
+# M2A Policy Bundle Platform Dispatch Contract Implementation Brief
+
+状态:本 brief 覆盖 V5 M2 的按平台选择规则包机制。M2A 只改 policy bundle loader 的参数与派发选择,不创建新平台手册,不接平台 client,不改 evaluator 公式。
+
+## 目标
+
+- 让 policy bundle loading 支持 `platform` 参数。
+- 保留 `platform="douyin"` 默认值,让老调用和现有脚本不需要批量改动。
+- 让 `graph.load_policy()` 从运行 state 透传 platform。
+- 让 `_select_dispatch()` 与 `_build_rule_pack_by_entity()` 都按调用方 platform 过滤。
+- 未知平台没有 enabled dispatch 时明确失败,不能静默使用抖音规则包。
+
+## 现有证据
+
+- `content_agent/integrations/policy_json.py:16` 当前签名为 `load_policy_bundle(self, strategy_version: str)`。
+- `policy_json.py:17` 规则包路径写死 `product_documents/规则包/douyin_rule_packs.v1.json`。
+- `policy_json.py:28` 调 `_select_dispatch(rule_package, actual_strategy_version)` 时未传 platform。
+- `policy_json.py:88-126` 的 `_select_dispatch()` / `_enabled_dispatches()` 已有 platform 参数与过滤,默认值是 `douyin`。
+- `policy_json.py:149-156` 的 `_build_rule_pack_by_entity()` 写死 `dispatch.get("platform") == "douyin"`。
+- `content_agent/graph.py:144-145` 当前只传 `state["strategy_version"]`。
+- `content_agent/interfaces.py:84-85`、`content_agent/business_modules/policy_version.py:8-9` 协议层也只接 `strategy_version`。
+- `product_documents/规则包/douyin_rule_packs.v1.json` 当前只有一条 enabled dispatch:`dispatch_content / platform=douyin`。
+
+## 修改范围
+
+- `content_agent/interfaces.py`
+  - `PolicyBundleStore.load_policy_bundle(strategy_version: str, platform: str = "douyin") -> dict[str, Any]`。
+- `content_agent/business_modules/policy_version.py`
+  - `run(strategy_version: str, policy_store: PolicyBundleStore, platform: str = "douyin")`。
+  - 内部调用 `policy_store.load_policy_bundle(strategy_version, platform=platform)`。
+- `content_agent/graph.py`
+  - `load_policy()` 改为 `policy_version.run(state["strategy_version"], deps.policy_store, platform=state.get("platform", "douyin"))`。
+- `content_agent/integrations/policy_json.py`
+  - `JsonPolicyBundleStore.load_policy_bundle(..., platform: str = "douyin")`。
+  - `_select_dispatch(..., platform=platform)`。
+  - `_build_rule_pack_by_entity(rule_package, actual_strategy_version, platform=platform)`。
+  - `bundle` 中可加入只读诊断字段 `platform` 或 `dispatch_platform`,但不得影响 evaluator 行为。
+
+## 不修改范围
+
+- 不改 `content_agent/run_service.py` 平台工厂。
+- 不改 `content_agent/integrations/douyin.py` / `kuaishou.py` / `shipinhao.py`。
+- 不改 evaluator、threshold、walk engine。
+- 不新建 `kuaishou_rule_packs`、`shipinhao_rule_packs` 等平台手册。
+- 不修改 DB schema。
+
+## 涉及文件 / 函数 / 类
+
+- `content_agent/interfaces.py::PolicyBundleStore.load_policy_bundle`
+- `content_agent/business_modules/policy_version.py::run`
+- `content_agent/graph.py::build_graph.load_policy`
+- `content_agent/integrations/policy_json.py::JsonPolicyBundleStore.load_policy_bundle`
+- `content_agent/integrations/policy_json.py::_select_dispatch`
+- `content_agent/integrations/policy_json.py::_enabled_dispatches`
+- `content_agent/integrations/policy_json.py::_assert_single_enabled_dispatch`
+- `content_agent/integrations/policy_json.py::_build_rule_pack_by_entity`
+- `tests/test_policy_dispatch.py`
+
+## 数据合同
+
+```python
+load_policy_bundle(strategy_version: str, platform: str = "douyin") -> dict[str, Any]
+```
+
+dispatch 过滤条件:
+
+```python
+dispatch.get("dispatch_enabled")
+and dispatch.get("platform") == platform
+and dispatch.get("runtime_stage") == runtime_stage
+and dispatch.get("strategy_version") == strategy_version
+and dispatch.get("target_entity") == target_entity
+and dispatch.get("content_format") == content_format
+```
+
+`rule_pack_by_entity` 必须只包含同一 platform 的 enabled dispatch,避免同一 bundle 中混入其他平台规则包。
+
+## 实施步骤
+
+1. 改 `PolicyBundleStore` protocol 签名,加默认 `platform="douyin"`。
+2. 改 `policy_version.run()` 签名,继续保留默认值。
+3. 改 `graph.load_policy()` 从 state 取 platform 并透传。
+4. 改 `JsonPolicyBundleStore.load_policy_bundle()` 签名和 `_select_dispatch()` 调用。
+5. 改 `_build_rule_pack_by_entity()` 签名,用调用方 platform 替代 `douyin` 字面量。
+6. 保持 `_enabled_dispatches()` 的过滤语义,必要时让错误消息带 platform,方便定位。
+7. 跑 `tests/test_policy_dispatch.py` 和 M0 golden gate。
+
+## Unit Test
+
+- `tests/test_policy_dispatch.py::test_policy_bundle_uses_content_dispatch_and_exports_runtime_contracts`
+  - 老调用 `load_policy_bundle("V4")` 仍通过,默认选抖音。
+- 新增 `test_policy_bundle_accepts_explicit_douyin_platform`
+  - `load_policy_bundle("V4", platform="douyin")` 与默认调用的 `dispatch_id/rule_pack_id/rule_pack_by_entity` 一致。
+- 新增 `test_select_dispatch_filters_by_platform`
+  - 在内存复制一条 `kuaishou` synthetic dispatch,断言 `_select_dispatch(..., platform="kuaishou")` 选中该 dispatch。
+- 新增 `test_build_rule_pack_by_entity_filters_by_platform`
+  - synthetic 多平台 dispatch 同时存在时,`rule_pack_by_entity` 只包含指定平台实体。
+- 新增 `test_policy_bundle_fails_for_unknown_platform_without_dispatch`
+  - `load_policy_bundle("V4", platform="unknown")` 报 `dispatch not found` 或同等清晰错误。
+- 扩展冲突测试:
+  - 同一 platform 下多条 enabled dispatch 仍触发 `CONFIG_RULE_PACK_DISPATCH_CONFLICT`。
+  - 不同 platform 的 dispatch 不构成冲突。
+
+## Integrated Test
+
+- `graph.load_policy()` 单元或集成桩测试:
+  - state 有 `platform="douyin"` 时 policy store 收到 douyin。
+  - state 缺 platform 时仍使用默认 douyin。
+- `uv run pytest tests/test_policy_dispatch.py -q`。
+- `uv run pytest tests/test_v5_golden_corpus_baseline.py -q`,证明平台参数没有改变历史 evaluator 行为。
+
+## 失败归因
+
+- 老测试大量失败:优先查默认 `platform="douyin"` 是否漏加。
+- 未知平台被抖音规则包吃掉:查 `_select_dispatch()` 是否仍默认 platform 而没有传调用值。
+- `rule_pack_by_entity` 混入错误平台:查 `_build_rule_pack_by_entity()` 是否仍写死 `douyin` 或没有 platform 参数。
+- graph 集成失败:查 `state.get("platform", "douyin")` 是否在 load_policy 节点透传。
+
+## sub-agent 交叉验证要点
+
+- 代码 sub-agent 复查 `interfaces.py`、`policy_version.py`、`graph.py`、`policy_json.py` 参数链路完整。
+- JSON sub-agent 复查真实规则包目前只有 douyin dispatch,M2 测试里的其他平台必须是 synthetic。
+- DB sub-agent 复查 DB 只有运行记录与 JSON scorecard,没有规则包配置表,M2A 不需要 migration。

+ 132 - 0
tech_documents/工程落地/v5_implementation_briefs/M2/M2B_Douyin_Rule_Pack_Standard_Scorecard_Contract.md

@@ -0,0 +1,132 @@
+# M2B Douyin Rule Pack Standard Scorecard Contract Implementation Brief
+
+状态:本 brief 覆盖把 `douyin_rule_packs.v1.json` 迁成第一本标准手册。M2B 只补 scorecard 权重/作用域合同,不改门槛、不改 evaluator 行为、不把 profile 合并进规则包。
+
+## 目标
+
+- 明确当前抖音规则包 scorecard 的真实状态:JSON 只有两维 50/50,抖音三维历史行为来自 evaluator 硬编码和 `content_audience_50plus` 输入。
+- 在规则包中补齐足够表达 V4 权重的最小字段。
+- 保持现有 `query_relevance` / `platform_performance` 基础维度存在。
+- 允许抖音新增 `fifty_plus` 维度配置,但不得改变历史 35/35/30、not_attempted 35/35、无 50+ 50/50 语义。
+- 保持 `score_thresholds.json` 继续独立管理门槛。
+
+## 现有证据
+
+- `product_documents/规则包/douyin_rule_packs.v1.json` 只有一条 enabled dispatch:`dispatch_content / platform=douyin`。
+- 同文件 scorecard dimensions 当前为:
+  - `key=query_relevance`, `weight_percent=50`, `runtime_status=active`。
+  - `key=platform_performance`, `weight_percent=50`, `runtime_status=active`。
+- 同文件 scoring rules 用 `dimension_key=query_relevance` 与 `dimension_key=platform_performance` 引用基础两维。
+- `content_agent/business_modules/rule_judgment/evaluator.py:223-242` 当前根据是否有 `content_audience_50plus` 块决定 50/50、35/35/30 或 35/35。
+- `tests/fixtures/cases/v4_douyin_57663/input/rule_decisions.jsonl` 110 条历史 decisions 覆盖 `fifty_plus_status=ok/not_attempted/unavailable`。
+- `tests/fixtures/cases/v4_kuaishou_57758/input/rule_decisions.jsonl` 51 条历史 decisions 全部没有 `fifty_plus_status`,证明两维路径仍存在。
+
+## 修改范围
+
+- `product_documents/规则包/douyin_rule_packs.v1.json`
+  - 在 scorecard dimensions 中补充 M2 最小字段。
+  - 可增加 `fifty_plus` 维度配置,用于表达抖音三维权重。
+  - 保留 existing rule pack id/version/dispatch,除非后续实现需要版本 bump;M2 brief 不强制 bump。
+- `scripts/validate_rule_pack_config.py`
+  - 校验新增字段类型、基础两维存在、额外维度合法、scoring rule 引用合法。
+- `scripts/validate_v4_config_contract.py`
+  - 同步合同校验,避免 config gate 与单独 validator 不一致。
+- `tests/test_config_tooling.py`
+- `tests/test_v4_rule_pack_contract.py`
+
+## 不修改范围
+
+- 不改 `tech_documents/数据接口与来源/score_thresholds.json`。
+- 不改 Excel。
+- 不改 platform profile。
+- 不新增新平台规则包。
+- 不改 DB schema。
+- 不改 M0 corpus。
+
+## 涉及文件 / 函数 / 类
+
+- `product_documents/规则包/douyin_rule_packs.v1.json`
+- `scripts/validate_rule_pack_config.py::_check_v4_rule_pack`
+- `scripts/validate_v4_config_contract.py::_check_v4_rule_pack_contract`
+- `tests/test_config_tooling.py`
+- `tests/test_v4_rule_pack_contract.py`
+
+## 数据合同
+
+现有 dimension 字段继续保留:
+
+```json
+{
+  "key": "query_relevance",
+  "max_score": 100,
+  "weight_percent": 35,
+  "runtime_status": "active"
+}
+```
+
+M2 可补充最小字段:
+
+```json
+{
+  "key": "fifty_plus",
+  "max_score": 100,
+  "weight_percent": 30,
+  "runtime_status": "active",
+  "active": true,
+  "platform_scope": ["douyin"],
+  "score_source_path": "content_audience_50plus.score"
+}
+```
+
+字段语义:
+
+| 字段 | 语义 | M2 约束 |
+|---|---|---|
+| `key` | 维度唯一键 | 必填,scoring rule 的 `dimension_key` 必须引用它 |
+| `weight_percent` | 权重百分比 | 必须是数字,但 M2 不要求所有 active 维度合计 100,因为 `not_attempted` 历史行为只算 35/35 |
+| `runtime_status` | 运行状态 | active 维度参与权重解析 |
+| `active` | 兼容布尔状态 | 可选;存在时必须与 `runtime_status=active` 一致 |
+| `platform_scope` | 维度适用平台 | 可选;存在时必须是字符串数组 |
+| `score_source_path` | 取分路径说明 | 可选;存在时必须是字符串 |
+
+## 实施步骤
+
+1. 读取当前 `douyin_rule_packs.v1.json` scorecard dimensions,确认基础两维与 scoring rules。
+2. 在基础两维上补充 `active/platform_scope/score_source_path`,权重按 M2 行为合同表达为抖音 35/35。
+3. 添加 `fifty_plus` 维度,`weight_percent=30`,适用 `douyin`。
+4. validator 放行新增字段,但继续拒绝未知 dimension 引用。
+5. 明确 thresholds 继续来自 `score_thresholds.json`,不把门槛复制进规则包。
+6. 跑 config tooling 与 M0 golden gate。
+
+## Unit Test
+
+- `tests/test_config_tooling.py`
+  - 当前真实 douyin rule pack 通过。
+  - 给 dimension 添加 `platform_scope` / `active` / `score_source_path` 后通过。
+  - `platform_scope` 非 list 失败。
+  - `active` 与 `runtime_status` 冲突失败。
+- `tests/test_v4_rule_pack_contract.py`
+  - 基础两维缺任意一个失败。
+  - 添加 `fifty_plus` 额外 active dimension 通过。
+  - scoring rule 引用未知 dimension 失败。
+  - 当前真实 douyin pack 的 rule_pack_dispatch 仍只有一条 enabled douyin dispatch。
+
+## Integrated Test
+
+- `uv run python scripts/validate_rule_pack_config.py`。
+- `uv run python scripts/validate_v4_config_contract.py`。
+- `python3 scripts/run_config_gate.py`。
+- `uv run pytest tests/test_v5_golden_corpus_baseline.py -q`,证明手册新增字段不改变 evaluator 行为。
+
+## 失败归因
+
+- validator 报 active dimensions 错:查 M1D 放开的基础两维包含逻辑是否被回退成 exact two dimensions。
+- golden gate 分数变了:查 M2C evaluator 是否把 35/35/30 或 35/35 读错,不要先改 corpus。
+- thresholds 变化:查是否误把 `score_thresholds.json` 或门槛字段放进本 brief 范围。
+- 新平台相关失败:说明 M2B 误做 M3 平台手册填充。
+
+## sub-agent 交叉验证要点
+
+- 代码 sub-agent 复查 validator 函数名与测试入口。
+- JSON sub-agent 复查真实规则包当前两维 50/50,并确认抖音三维不是现有 JSON 已表达。
+- DB sub-agent 复查历史 DB scorecard 已落库为三维/两维结果,但 DB 没有规则包配置表。

+ 129 - 0
tech_documents/工程落地/v5_implementation_briefs/M2/M2C_Config_Driven_V4_Score_Total_Contract.md

@@ -0,0 +1,129 @@
+# M2C Config Driven V4 Score Total Contract Implementation Brief
+
+状态:本 brief 覆盖 evaluator 的 V4 打分公式迁移。M2C 只把权重来源从代码常量搬到规则包,不改变历史分数、decision action、reason、allow_walk 或 thresholds。
+
+## 目标
+
+- 移除 evaluator 主路径中的 0.5/0.5、0.35/0.35/0.30、not_attempted 0.35/0.35 硬编码。
+- 从 policy bundle 的 rule pack scorecard dimensions 读取权重。
+- 保持历史行为:
+  - 无 `content_audience_50plus` 块:50/50。
+  - 抖音 `fifty_plus_status=ok`:35/35/30。
+  - 抖音 `fifty_plus_status=not_attempted`:35/35,不归一化。
+  - 抖音 `unavailable/incomplete`:技术失败,`total_score=None`。
+- 保持 M0 golden gate 161 条完全通过。
+
+## 现有证据
+
+- `content_agent/business_modules/rule_judgment/evaluator.py:209-259` 的 `_v4_scorecard_total(bundle)` 当前只接 evidence bundle,不接 policy bundle。
+- `evaluator.py:223-229` 无 50+ 块时硬编码 `0.5 + 0.5`。
+- `evaluator.py:235-240` 抖音 `status=ok` 硬编码 `0.35 + 0.35 + 0.30`。
+- `evaluator.py:241-242` 抖音 `not_attempted` 硬编码 `0.35 + 0.35`。
+- `evaluator.py:292-300` 的 `_v4_thresholds(policy_bundle, platform)` 已经示范了配置化读取模式,但门槛不是 M2 主改动。
+- DB sub-agent 反算:
+  - 抖音 golden run 可算 92 条,按 35/35/30 或 35/35 反算 `0 mismatch`。
+  - 快手 golden run 可算 47 条,按 50/50 反算 `0 mismatch`。
+
+## 修改范围
+
+- `content_agent/business_modules/rule_judgment/evaluator.py`
+  - `_decide_v4()` 调 `_v4_scorecard_total(bundle, policy_bundle)`。
+  - `_v4_scorecard_total()` 增加 `policy_bundle` 参数。
+  - 新增最小内部 helper,不新增组件类:
+    - `_v4_score_weights(policy_bundle: dict[str, Any], bundle: dict[str, Any]) -> dict[str, float]`
+    - `_v4_weighted_total(scores: dict[str, Any], weights: dict[str, float]) -> float | None`
+  - 可在 scorecard 中写入只读诊断字段如 `score_weights`,供 M2D validation/flow ledger 复用;M0 fingerprint 不依赖该字段。
+
+## 不修改范围
+
+- 不改 `_v4_thresholds()` 门槛数值。
+- 不改 decision action/reason 规则。
+- 不改 walk gate 判断。
+- 不改 M0 corpus。
+- 不新增类或复杂 scoring engine。
+
+## 涉及文件 / 函数 / 类
+
+- `content_agent/business_modules/rule_judgment/evaluator.py::_decide_v4`
+- `content_agent/business_modules/rule_judgment/evaluator.py::_v4_scorecard_total`
+- `content_agent/business_modules/rule_judgment/evaluator.py::_fifty_plus_from_bundle`
+- `content_agent/business_modules/rule_judgment/evaluator.py::_query_relevance_from_bundle`
+- `content_agent/business_modules/rule_judgment/evaluator.py::_platform_performance_from_bundle`
+- 新增内部 helper:`_v4_score_weights`, `_v4_weighted_total`
+- `tests/test_rule_judgment_scorecard.py`
+- `tests/test_douyin_fifty_plus_scoring.py`
+- `tests/test_v5_golden_corpus_baseline.py`
+
+## 数据合同
+
+输入:
+
+- `policy_bundle["rule_pack"]["scorecard"]["dimensions"]`
+- evidence bundle:
+  - `pattern_match_result.query_relevance_score`
+  - `content_engagement_metrics.platform_performance.platform_performance_score`
+  - optional `content_audience_50plus.status`
+  - optional `content_audience_50plus.score`
+
+权重解析:
+
+| 场景 | 权重来源 | 期望 |
+|---|---|---|
+| 无 50+ 块 | query/platform 基础维度 | 50/50 |
+| `fifty_plus_status=ok` | query/platform/fifty_plus | 35/35/30 |
+| `fifty_plus_status=not_attempted` | query/platform,忽略 fifty_plus 分数 | 35/35,不归一化 |
+| `unavailable/incomplete` | 不算分 | `total_score=None`,技术失败字段保持 |
+
+`_v4_weighted_total()` 只做数值合成:
+
+```python
+round(sum(float(scores[key]) * weights[key] for key in weights), 2)
+```
+
+其中 weights 用小数表达,例如 `0.35`。
+
+## 实施步骤
+
+1. 改 `_decide_v4()` 让 `_v4_scorecard_total()` 能读取 policy bundle。
+2. 写 `_v4_score_weights()`:
+   - 优先读取 rule pack scorecard dimensions。
+   - 按 platform scope / 50+ 块存在状态筛选。
+   - 配置缺失时保留内置 fallback,仅用于兼容老 fixture/测试。
+3. 写 `_v4_weighted_total()` 做纯加权求和。
+4. 保持 scorecard 现有字段不改名:`total_score`、`score_missing`、`fifty_plus_status`、`fifty_plus_score`、`fifty_plus_components`。
+5. 确认 `not_attempted` 不归一化,仍只算 35/35。
+6. 跑 unit tests 与 M0 golden gate。
+
+## Unit Test
+
+- `tests/test_rule_judgment_scorecard.py`
+  - 无 50+ 块仍是 50/50。
+  - synthetic 改 rule pack query/platform 权重后 evaluator 分数跟配置变化,证明不再读硬编码。
+  - 缺权重配置时 fallback 行为不破坏老测试。
+- `tests/test_douyin_fifty_plus_scoring.py`
+  - `test_douyin_ok_uses_35_35_30` 继续通过。
+  - `test_douyin_not_attempted_uses_35_35_two_terms` 继续通过。
+  - `test_douyin_portrait_unavailable_is_technical_retry_and_stops_walk` 继续通过。
+  - `test_non_douyin_keeps_50_50_when_no_block` 继续通过。
+  - 新增 synthetic 权重漂移测试:内存修改 `fifty_plus` 权重后分数变化。
+
+## Integrated Test
+
+- `uv run pytest tests/test_rule_judgment_scorecard.py tests/test_douyin_fifty_plus_scoring.py -q`。
+- `uv run pytest tests/test_v5_golden_corpus_baseline.py -q`。
+- `grep -n "0.35\\|0.30\\|0.5" content_agent/business_modules/rule_judgment/evaluator.py`
+  - 允许 fallback/测试说明里存在,主路径不能再靠裸常量表达业务权重。
+
+## 失败归因
+
+- 抖音 `ok` 分数偏移:查 `fifty_plus` 维度权重和 `score_source_path` 映射。
+- `not_attempted` 分数偏高:通常是误把 35/35 归一化成 50/50。
+- 快手/无 50+ 分数偏移:查无 50+ 块是否仍选择基础两维 50/50。
+- 技术失败变成普通 reject:查 `unavailable/incomplete` 分支是否仍在 total 前短路。
+- golden gate 失败:先修代码,除非明确批准行为变化,不得改 corpus。
+
+## sub-agent 交叉验证要点
+
+- 代码 sub-agent 复查 evaluator 主路径的权重常量是否迁出。
+- JSON sub-agent 复查 rule pack 权重字段能表达 50/50 与 35/35/30。
+- DB sub-agent 复查历史 DB 公式反算 `0 mismatch`,证明 M2C 不能改变数值。

+ 128 - 0
tech_documents/工程落地/v5_implementation_briefs/M2/M2D_Validation_And_Flow_Ledger_Weight_Parity.md

@@ -0,0 +1,128 @@
+# M2D Validation And Flow Ledger Weight Parity Implementation Brief
+
+状态:本 brief 覆盖 V4 打分权重的两个副本联动:run record validation 与 flow ledger 展示。M2D 的目标是让它们跟 evaluator 使用同一权重来源,不再各自硬编码。
+
+## 目标
+
+- `run_record/validation.py` 不再用本地 0.5/0.35/0.30 副本反算。
+- `flow_ledger_service.py` 不再用本地 50/50 与 35/35/30 副本展示。
+- validation 与 flow ledger 优先读取 evaluator 输出的权重证据或同一规则包权重来源。
+- 历史 fixture/legacy scorecard 没有新增权重字段时仍兼容。
+
+## 现有证据
+
+- `content_agent/business_modules/run_record/validation.py:963-974` 根据 `fifty_plus_status` 维护一份独立硬编码公式。
+- `content_agent/flow_ledger_service.py:1482-1490` 的 `_decision_score_items()` 调 `_v4_display_weights(scorecard)`。
+- `flow_ledger_service.py:1494-1506` 的 `_v4_display_weights()` 根据 `fifty_plus_status` 再维护一份展示权重。
+- M2C 会让 evaluator 从规则包读取权重,如果 M2D 不改,三处权重会再次漂移。
+- M0 golden gate 当前只比对行为字段,可允许 scorecard 增加 `score_weights` 这类诊断字段,但分数/action/reason 必须不变。
+
+## 修改范围
+
+- `content_agent/business_modules/run_record/validation.py`
+  - `_check_v4_score_contract()` 使用 scorecard 中的权重证据或共享 helper 反算。
+  - legacy scorecard 无权重字段时保留旧推断 fallback。
+- `content_agent/flow_ledger_service.py`
+  - `_decision_score_items()` 向 `_v4_display_weights()` 传 scorecard。
+  - `_v4_display_weights()` 优先读取 scorecard 权重证据。
+  - legacy scorecard 无权重字段时保留旧推断 fallback。
+- `tests/test_v4_validator_contract.py`
+- `tests/test_score_items_platform.py`
+- 必要时扩展 `tests/test_flow_ledger_api.py`
+
+## 不修改范围
+
+- 不改 evaluator 判定规则;M2D 只消费 M2C 的权重证据。
+- 不改 flow ledger API 输出字段名,除非已有 UI 允许展示新增权重。
+- 不改 DB schema。
+- 不改历史 runtime record fixture。
+
+## 涉及文件 / 函数 / 类
+
+- `content_agent/business_modules/run_record/validation.py::_check_v4_score_contract`
+- `content_agent/business_modules/run_record/validation.py::_v4_score_values`
+- `content_agent/flow_ledger_service.py::_decision_score_items`
+- `content_agent/flow_ledger_service.py::_v4_display_weights`
+- `content_agent/flow_ledger_service.py::_score_items_from_values`
+- `tests/test_v4_validator_contract.py`
+- `tests/test_score_items_platform.py`
+- `tests/test_flow_ledger_api.py`
+
+## 数据合同
+
+M2C 可让 evaluator 在 scorecard 写入:
+
+```json
+{
+  "score_weights": {
+    "query_relevance": 0.35,
+    "platform_performance": 0.35,
+    "fifty_plus": 0.30
+  }
+}
+```
+
+`not_attempted` 的 scorecard 可写:
+
+```json
+{
+  "score_weights": {
+    "query_relevance": 0.35,
+    "platform_performance": 0.35
+  },
+  "fifty_plus_status": "not_attempted"
+}
+```
+
+兼容规则:
+
+- 有 `score_weights`:validation 和 flow ledger 以它为准。
+- 无 `score_weights`:按 legacy 逻辑从 `fifty_plus_status` 推断,保证旧 fixture 可验。
+- `score_weights` 不参与 M0 行为 fingerprint,但可作为展示/验收诊断字段。
+
+## 实施步骤
+
+1. 在 validation 中新增最小内部 helper,例如 `_v4_score_weights_from_scorecard(scorecard)`。
+2. `_check_v4_score_contract()` 用 helper 返回的 weights 反算 expected。
+3. helper 对 legacy scorecard 做 fallback:
+   - 无 `fifty_plus_status`:50/50。
+   - `ok`:35/35/30。
+   - `not_attempted`:35/35。
+4. 在 flow ledger 中改 `_v4_display_weights()`:
+   - 优先把 `score_weights` 映射成展示层 `{"query": ..., "platform": ..., "fifty": ...}`。
+   - fallback 保持现有展示。
+5. 保持 `_decision_score_items()` 和 API 输出结构稳定。
+6. 跑 validator/flow ledger/unit/golden gate。
+
+## Unit Test
+
+- `tests/test_v4_validator_contract.py`
+  - 带 `score_weights` 的三维 scorecard 按权重反算通过。
+  - 改错 `score_weights` 后 validation 报 `v4_score_total_mismatch`。
+  - legacy 无 `score_weights` 的历史 fixture 仍通过。
+  - `not_attempted` 使用 35/35,不归一化。
+- `tests/test_score_items_platform.py`
+  - flow ledger 有 `score_weights` 时展示对应权重。
+  - legacy 无 `score_weights` 时仍展示 50/50 或 35/35/30。
+  - `not_attempted` 的 fifty 信息仍显示 status,但不展示不存在的 fifty score。
+- `tests/test_flow_ledger_api.py`(必要时)
+  - API 返回结构不破坏前端消费字段。
+
+## Integrated Test
+
+- `uv run pytest tests/test_v4_validator_contract.py tests/test_score_items_platform.py -q`。
+- 如修改 API 序列化,补跑 `uv run pytest tests/test_flow_ledger_api.py -q`。
+- `uv run pytest tests/test_v5_golden_corpus_baseline.py -q`。
+
+## 失败归因
+
+- validation 误报:先看 scorecard 是否有 `score_weights`,再查 fallback 是否覆盖 legacy 历史数据。
+- flow ledger 展示权重错:查 `_v4_display_weights()` 的 key 映射,尤其 `query_relevance` 到 `query`。
+- golden gate 失败:说明 M2D 间接影响 evaluator 输出核心字段,应回到 M2C 修。
+- 前端 API 测试失败:不要改字段名,只在现有结构里修权重值来源。
+
+## sub-agent 交叉验证要点
+
+- 代码 sub-agent 复查 validation 与 flow ledger 不再保留独立业务权重常量。
+- JSON/fixture sub-agent 复查历史 scorecard 没有 `score_weights`,必须有 fallback。
+- DB sub-agent 复查已落库历史 scorecard 同样没有新字段,只读验收不能要求 DB 回填。

+ 187 - 0
tech_documents/工程落地/v5_implementation_briefs/M2/M2E_Acceptance_Commands_And_Subagent_Audit.md

@@ -0,0 +1,187 @@
+# M2E Acceptance Commands And Subagent Audit Implementation Brief
+
+状态:本 brief 汇总 V5 M2 的验收命令、测试矩阵、DB 只读证据、guardrail 和失败归因。M2E 不新增业务功能。
+
+## 目标
+
+- 证明 policy bundle 已支持按 platform dispatch。
+- 证明抖音规则包已能表达 V4 权重合同。
+- 证明 evaluator、validation、flow ledger 三处权重来源一致。
+- 证明抖音 110 条、快手 51 条历史 decisions 行为零变化。
+- 证明 M2 没误改平台 client、walk engine、Excel/映射、DB schema 或 M0 corpus。
+
+## 现有证据
+
+- 代码 sub-agent 确认:
+  - `policy_json.py` 仍有规则包路径与 `rule_pack_by_entity` 抖音字面量。
+  - `graph.py` / `interfaces.py` / `policy_version.py` 尚未传 platform。
+  - evaluator、validation、flow ledger 三处都维护权重副本。
+- JSON/fixture sub-agent 确认:
+  - `douyin_rule_packs.v1.json` 只有一条 enabled douyin dispatch。
+  - 当前 scorecard 两维 50/50,三维抖音行为不是 JSON 已表达。
+  - `score_thresholds.json` 已经独立配置 default + douyin,不是 M2 主改动。
+  - M0 corpus 覆盖抖音三维与快手两维。
+- DB sub-agent 只读确认:
+  - `content-deconstruction-supply`,MySQL `8.0.46`,事务只读。
+  - 无 profile/config/rule_pack/threshold/weight/walk config 表。
+  - 抖音 run:110 decisions、265 walk actions、可算 92 条按 35/35/30 或 35/35 反算 `0 mismatch`。
+  - 快手 run:51 decisions、142 walk actions、可算 47 条按 50/50 反算 `0 mismatch`。
+
+## 修改范围
+
+- 后续实现可扩展 M2 相关 tests。
+- 后续实现可更新 config validators 支持 M2 scorecard 字段。
+- 文档阶段只新增本 M2 brief 目录。
+
+## 不修改范围
+
+- 不改 DB schema 或 migration。
+- 不改 Excel/规则包映射。
+- 不改平台 client。
+- 不改 walk engine。
+- 不改 M0 golden corpus。
+- 不接新平台真实接口。
+
+## 涉及文件 / 函数 / 类
+
+- `tests/test_policy_dispatch.py`
+- `tests/test_config_tooling.py`
+- `tests/test_v4_rule_pack_contract.py`
+- `tests/test_rule_judgment_scorecard.py`
+- `tests/test_douyin_fifty_plus_scoring.py`
+- `tests/test_score_items_platform.py`
+- `tests/test_v4_validator_contract.py`
+- `tests/test_v5_golden_corpus_baseline.py`
+- `scripts/validate_rule_pack_config.py`
+- `scripts/validate_v4_config_contract.py`
+- `scripts/run_config_gate.py`
+
+## 测试矩阵
+
+| 层级 | 测试 | 验证点 |
+|---|---|---|
+| Unit | policy dispatch | platform 参数、dispatch 过滤、冲突、未知平台失败、默认 douyin 兼容 |
+| Unit | rule pack config | scorecard 权重字段、基础两维、额外维度、未知 dimension 引用失败 |
+| Unit | evaluator scorecard | 50/50、35/35/30、not_attempted 35/35、技术失败 |
+| Unit | validation | 读取同源权重反算,legacy fallback |
+| Unit | flow ledger | 展示权重读取同源字段,legacy fallback |
+| Integrated | config gate | validators 与真实 JSON 全通过 |
+| Regression | M0 golden gate | 161 条历史 decision exact replay |
+| Guardrail | git diff | client/walk/sql/Excel/corpus 范围无误改 |
+
+## 验证命令
+
+```bash
+# M2A platform dispatch
+uv run pytest tests/test_policy_dispatch.py -q
+
+# M2B config contract
+uv run pytest tests/test_config_tooling.py tests/test_v4_rule_pack_contract.py -q
+
+# M2C evaluator score behavior
+uv run pytest tests/test_rule_judgment_scorecard.py tests/test_douyin_fifty_plus_scoring.py -q
+
+# M2D validation / flow ledger parity
+uv run pytest tests/test_score_items_platform.py tests/test_v4_validator_contract.py -q
+
+# M0 golden evaluator gate
+uv run pytest tests/test_v5_golden_corpus_baseline.py -q
+
+# Config validators
+uv run python scripts/validate_rule_pack_config.py
+uv run python scripts/validate_v4_config_contract.py
+python3 scripts/run_config_gate.py
+
+# Guardrail:these should stay empty
+git diff -- content_agent/integrations/douyin.py content_agent/integrations/kuaishou.py content_agent/integrations/shipinhao.py content_agent/business_modules/walk_engine.py sql tech_documents/规则包映射
+```
+
+## Unit Test
+
+- `tests/test_policy_dispatch.py`
+  - 默认 douyin 兼容。
+  - explicit douyin 兼容。
+  - synthetic platform dispatch 选择。
+  - 同平台冲突失败。
+  - 未知平台失败。
+- `tests/test_config_tooling.py` / `tests/test_v4_rule_pack_contract.py`
+  - 新增权重字段合法。
+  - 基础两维必需。
+  - 额外维度合法。
+  - scoring rule 未知 dimension 失败。
+- `tests/test_rule_judgment_scorecard.py` / `tests/test_douyin_fifty_plus_scoring.py`
+  - 所有历史权重场景零变化。
+  - synthetic 改权重能改变分数,证明已配置化。
+- `tests/test_score_items_platform.py` / `tests/test_v4_validator_contract.py`
+  - validation/flow ledger 读同一权重证据。
+  - legacy scorecard fallback 通过。
+
+## Integrated Test
+
+- M0 golden gate 通过。
+- Config gate 通过。
+- Guardrail diff 为空。
+- 如 full pytest 仍有既有非 M2 失败,先按失败名归因,不得为了 M2 放宽 golden gate 或改 corpus。
+
+## DB / 只读验收
+
+DB 不进入常规 pytest/CI,只作为审计证据:
+
+- CI 不依赖内网 MySQL、`.env` 密钥或真实外部状态。
+- M2 的 source of truth 是本地规则包 JSON 与 golden corpus。
+- DB 只证明历史运行已经按三维/两维公式落库,且没有需要 migration 的配置表。
+
+可选只读审计 SQL 摘要:
+
+```sql
+SET SESSION TRANSACTION READ ONLY;
+START TRANSACTION;
+SELECT DATABASE(), VERSION(), @@transaction_read_only;
+SELECT table_name
+FROM information_schema.tables
+WHERE table_schema = DATABASE()
+  AND (LOWER(table_name) LIKE '%config%'
+       OR LOWER(table_name) LIKE '%rule%pack%'
+       OR LOWER(table_name) LIKE '%threshold%'
+       OR LOWER(table_name) LIKE '%weight%'
+       OR LOWER(table_name) LIKE '%walk%config%');
+ROLLBACK;
+```
+
+期望:只读事务成立;config/rule_pack/threshold/weight/walk config 表为空。
+
+## Guardrail
+
+这些路径不应出现在 M2 diff 中:
+
+- `content_agent/integrations/douyin.py`
+- `content_agent/integrations/kuaishou.py`
+- `content_agent/integrations/shipinhao.py`
+- `content_agent/business_modules/walk_engine.py`
+- `content_agent/run_service.py` 的平台 client 工厂逻辑
+- `sql/`
+- `tech_documents/规则包映射`
+- `tests/fixtures/cases/v4_douyin_57663/`
+- `tests/fixtures/cases/v4_kuaishou_57758/`
+
+## 失败归因
+
+- `tests/test_policy_dispatch.py` 失败:归 M2A,先查 platform 参数和 dispatch 过滤。
+- config validators 失败:归 M2B,先查新增字段合同和基础两维。
+- evaluator 分数失败:归 M2C,先查权重解析和 `not_attempted` 不归一化。
+- validation/flow ledger 失败:归 M2D,先查是否仍有本地硬编码或 legacy fallback。
+- M0 golden gate 失败:行为变了,先修代码;只有经确认的有意行为变化才可另建新基线。
+- guardrail diff 非空:误入 M3/M4/DB/Excel 范围,撤回相关改动。
+
+## sub-agent 交叉验证要点
+
+- 代码 sub-agent:
+  - 复查 M2A-D 涉及的函数名和测试入口。
+  - 复查平台 client、walk engine、DB schema 没有被纳入 M2。
+- JSON/fixture sub-agent:
+  - 复查 `douyin_rule_packs.v1.json` dispatch 与 scorecard 当前状态。
+  - 复查 `score_thresholds.json` 不改门槛。
+  - 复查 M0 corpus 抖音 110 条、快手 51 条。
+- DB sub-agent:
+  - 复查 MySQL 版本、只读事务、无配置表。
+  - 复查两条 golden run 的公式反算 `0 mismatch`。

+ 173 - 0
tech_documents/工程落地/v5_implementation_briefs/M3/00_M3_Brief_Index.md

@@ -0,0 +1,173 @@
+# V5 M3 三短视频平台规则与游走补齐 Brief Index
+
+状态: implementation brief 草案。
+阶段:M3。
+上游依赖:M0 golden evaluator gate、M1 四态/边目录地基、M2 platform dispatch 与配置化权重。
+
+## 目标定位
+
+M3 只做三件事:
+
+1. 让抖音、快手、视频号都能通过 `load_policy_bundle("V4", platform=...)` 选择规则包 dispatch。
+2. 正式打通快手 `author_to_works`,让已有 walk engine 可以顺着快手作者挖作品。
+3. 保持视频号作者边 blocked,不接图文/图片/文字链路,不改 DB。
+
+M3 不是"全平台接入"阶段。公众号、小红书、头条、知乎、微博、B 站、YouTube 只保留在 profile/config 台账,不进入 V5 runtime。
+
+## 子 brief 执行口径(A->E 线性)
+
+| 子 brief | 文件 | 核心交付 | 依赖 |
+|---|---|---|---|
+| M3A | `M3A_Three_Platform_Dispatch_And_Rule_Pack_Config.md` | 真实规则包 JSON 补快手/视频号 Content/video dispatch | M2 platform dispatch 已落地 |
+| M3B | `M3B_Kuaishou_Author_Works_Client_Contract.md` | `CrawapiKuaishouClient.fetch_author_works()` 数据合同与测试计划 | M3A |
+| M3C | `M3C_Profile_Edge_And_Walk_Runtime_Alignment.md` | 快手 profile 作者边改 supported,视频号保持 blocked,walk engine 零改 | M3B |
+| M3D | `M3D_Source_Evidence_Registry_And_Capture_Reconciliation.md` | 修正快手 blogger 新旧证据冲突,补 smoke/capture 证据口径 | M3B-M3C |
+| M3E | `M3E_Acceptance_Commands_And_Subagent_Audit.md` | 验收命令、测试矩阵、DB/Excel guardrail、失败归因 | M3A-M3D |
+
+## 不处理范围(红线)
+
+- 不改 `content_agent/business_modules/rule_judgment/evaluator.py` 的判定行为。
+- 不新增平台 client 工厂分支;`run_service._platform_client()` 仍只注册 douyin/kuaishou/shipinhao。
+- 不接公众号/小红书,不做图文 Gemini 判定,不新增图片/文字下载与 OSS 存储。
+- 不改 `sql/`,不新增 migration,不新增 DB 表或列。
+- 不重组 Excel,不改 `scripts/build_config_from_excel.py` 的新增行能力;Excel 全平台大矩阵留 M4。
+- 不改 M0 golden corpus 与 fixture 里的历史 decision baseline。
+
+## 现有证据(共享,子 brief 各自细引)
+
+### Code
+
+- `content_agent/integrations/policy_json.py:16/28/122-131/154-173`:M2 后 loader 已接 `platform` 并按 platform 过滤 dispatch。
+- `content_agent/graph.py:144-148` 与 `content_agent/business_modules/policy_version.py:8`:runtime 会把 `state["platform"]` 传入 policy loader。
+- `content_agent/run_service.py:568-577`:real 模式只注册 `douyin/kuaishou/shipinhao` 三平台。
+- `content_agent/business_modules/walk_engine.py:437-456`:profile 不支持作者边时写 `edge_blocked_by_platform_profile`;支持后才动态找 `fetch_author_works`。
+- `content_agent/business_modules/walk_engine.py:506-567`:作者作品 fetch 成功后会回灌 search_query、prefetched batch、frontier。
+- `content_agent/integrations/kuaishou.py:1-5`:当前注释仍写作者作品接口未暴露。
+- `tests/test_kuaishou_client.py:291`:当前测试明确断言快手没有 `fetch_author_works`。
+
+### JSON / Capture
+
+- `product_documents/规则包/douyin_rule_packs.v1.json:21-35`:真实规则包只有 `platform=douyin` dispatch。
+- `product_documents/规则包/douyin_rule_packs.v1.json:301-362`:scorecard 已有 `query_relevance`、`platform_performance`、`fifty_plus` 与三套 `score_weight_profiles`。
+- `tech_documents/数据接口与来源/score_thresholds.json:1-19`:门槛真源是 default + douyin;快手/视频号无 override,先走 default。
+- `tech_documents/数据接口与来源/platform_profiles/kuaishou.json:117-123`:快手 `author_to_works`/`author_work_to_content` 仍 blocked。
+- `tech_documents/数据接口与来源/platform_profiles/shipinhao.json:47-48`:视频号作者边保持 blocked。
+- `product_documents/全平台游走策略.md:7-10/28-33/97-101`:2026-06-26 实测快手 `/crawler/kuai_shou/blogger` 返回 20 条,推翻旧口径。
+- `tech_documents/数据接口与来源/captures/`:当前有快手 search/detail/account_info/video_dl capture,没有 `kuaishou_blogger.json`。
+
+### DB / Excel
+
+- 国内库只读审计:MySQL 8.0.46,事务只读。
+- 抖音:14 run、1896 video items、1896 media、1896 decisions、4748 walk_actions。
+- 快手:13 run、1033 video items、1033 media、1033 decisions、2867 walk_actions。
+- 视频号:0 run。
+- 快手 `author_to_works`:954/954 全部 `skipped / edge_blocked_by_platform_profile`;其中 golden run `v1_run_57758_kuaishou_1782622792` 为 44/44。
+- 非 video items 为 0;M3 不需要图文/图片/文字 schema。
+- `scripts/run_config_gate.py` 当前通过;M3 不碰 Excel 结构。
+
+## 数据合同(汇总)
+
+### 三平台 Dispatch
+
+```python
+JsonPolicyBundleStore(Path(".")).load_policy_bundle("V4", platform="douyin")
+JsonPolicyBundleStore(Path(".")).load_policy_bundle("V4", platform="kuaishou")
+JsonPolicyBundleStore(Path(".")).load_policy_bundle("V4", platform="shipinhao")
+```
+
+期望:
+
+- 三平台都返回 `target_entity == "Content"`、`content_format == "video"`。
+- `bundle["platform"]` 等于调用平台。
+- `rule_pack_by_entity["Content"]["dispatch"]["platform"]` 等于调用平台。
+- 未知平台继续 `dispatch not found`,不得 fallback 到 douyin。
+
+### 快手 Author Works
+
+`CrawapiKuaishouClient.fetch_author_works(query)` 输入:
+
+```json
+{
+  "platform_author_id": "3xfkwajatdh7p7i",
+  "search_query_id": "author_xxx",
+  "discovery_start_source": "pattern_itemset"
+}
+```
+
+输出:canonical video item list,每条至少包含:
+
+- `platform="kuaishou"`
+- `platform_content_id`
+- `platform_author_id`
+- `platform_content_format="video"`
+- `play_url`
+- `statistics`
+- `previous_discovery_step="author_works"`
+- `content_metadata_source="kuaishou_blogger"`
+
+### Walk Profile
+
+| 平台 | `author_to_works` | `author_work_to_content` | M3 口径 |
+|---|---|---|---|
+| douyin | supported | supported | 不改 |
+| kuaishou | supported | supported | M3 打开 |
+| shipinhao | blocked | blocked | 保持 blocked |
+
+## M3 已拍板口径
+
+- 快手 `author_to_works` 是 M3 core,不再拆 M3X。
+- 快手 walk 行为允许有意变化,但 evaluator decision golden 必须零变化。
+- 视频号没有历史 DB run,只做 dispatch/profile/client smoke 与 blocked guardrail。
+- M3 不接公众号/小红书,不做多模态。
+- M3 不重组 Excel;M4 才做全平台大矩阵。
+
+## 验证命令(汇总)
+
+```bash
+uv run pytest tests/test_policy_dispatch.py -q
+uv run pytest tests/test_kuaishou_client.py tests/test_douyin_client.py tests/test_shipinhao_client.py -q
+uv run pytest tests/test_walk_graph_config.py tests/test_v4_walk_contract.py tests/test_walk_engine_deep_frontier.py -q
+uv run pytest tests/test_v5_golden_corpus_baseline.py -q
+uv run --with openpyxl python scripts/run_config_gate.py
+uv run --with pymysql python scripts/validate_content_agent_db.py --env-file .env --allow-missing-tables
+git diff -- sql tech_documents/规则包映射 scripts/build_config_from_excel.py
+```
+
+## Unit Test(汇总)
+
+- `tests/test_policy_dispatch.py`
+  - 真实 JSON 支持 douyin/kuaishou/shipinhao 三平台 dispatch。
+  - unknown platform 失败,不 fallback。
+  - `rule_pack_by_entity` 只索引当前 platform。
+- `tests/test_kuaishou_client.py`
+  - `fetch_author_works()` 请求 `/crawler/kuai_shou/blogger`。
+  - 输出 canonical video items。
+  - 改写当前 `test_kuaishou_has_no_author_works_client`。
+- `tests/test_walk_graph_config.py` / `tests/test_v4_walk_contract.py`
+  - 快手作者边 supported。
+  - 视频号作者边 blocked。
+- `tests/test_walk_engine_deep_frontier.py`
+  - 快手 supported + client method 时回灌作者作品。
+  - 视频号 blocked 时不调用 client,继续写 `edge_blocked_by_platform_profile`。
+
+## Integrated Test
+
+- `tests/test_v5_golden_corpus_baseline.py`:抖音 110 条、快手 51 条 evaluator decision exact replay。
+- `scripts/run_config_gate.py`:证明 M3 没破坏现有 Excel byte-equal gate。
+- `validate_content_agent_db.py`:证明 M3 无 DB migration 需求。
+- 快手真实 smoke:实现阶段单发 `/crawler/kuai_shou/blogger`,间隔遵守 profile rate limit,输出 walk action diff。
+
+## 失败归因(汇总)
+
+- `load_policy_bundle("V4", platform="kuaishou")` 失败:查 M3A dispatch 是否写入真实 JSON、是否 enabled、是否指向存在的 enabled rule pack。
+- 快手 author works 无结果:查 M3B request body、blogger path、normalizer、video URL selection。
+- 快手 still skipped by profile:查 M3C profile 是否仍 blocked 或 validator 是否仍钉死快手 blocked。
+- 视频号出现 author fetch:查 M3C 是否误把 shipinhao profile 打开。
+- evaluator golden fail:查是否误改 evaluator、threshold、score_weight_profiles 或 corpus。
+- config gate fail:查是否误进 M4 Excel/转换器范围。
+
+## sub-agent 交叉验证要点
+
+- Code subagent:确认 policy dispatch、run_service 三平台、walk_engine author 链路、快手无 client 方法、相关测试残留断言。
+- JSON/config subagent:确认真实规则包只有 douyin dispatch、快手 profile blocked、视频号 blocked、快手 blogger 无 capture 但有 2026-06-26 新证据。
+- DB/Excel subagent:确认国内库三平台历史数据、快手 954 条 profile gate skip、非 video 为 0、Excel gate 当前通过且 M3 不动。

+ 126 - 0
tech_documents/工程落地/v5_implementation_briefs/M3/M3A_Three_Platform_Dispatch_And_Rule_Pack_Config.md

@@ -0,0 +1,126 @@
+# M3A Three Platform Dispatch And Rule Pack Config Implementation Brief
+
+状态: implementation brief 草案。
+阶段:M3A。
+
+## 目标
+
+补齐真实规则包 JSON 的三短视频平台 dispatch,让:
+
+- `load_policy_bundle("V4", platform="douyin")` 继续通过且行为不变。
+- `load_policy_bundle("V4", platform="kuaishou")` 可以加载 Content/video 规则包。
+- `load_policy_bundle("V4", platform="shipinhao")` 可以加载 Content/video 规则包。
+- unknown platform 继续失败,不得静默 fallback 到 douyin。
+
+M3A 只补 dispatch 配置与测试;不搬文件路径、不拆规则包文件、不改 evaluator。
+
+## 现有证据
+
+- `content_agent/integrations/policy_json.py:16`:签名已是 `load_policy_bundle(self, strategy_version: str, platform: str = "douyin")`。
+- `policy_json.py:28`:loader 调 `_select_dispatch(..., platform=platform)`。
+- `policy_json.py:122-131`:`_enabled_dispatches()` 已按 `dispatch.platform == platform` 过滤。
+- `policy_json.py:154-173`:`_build_rule_pack_by_entity()` 已按 platform 构建实体索引。
+- `content_agent/graph.py:144-148`:runtime load policy 时从 `state.get("platform", "douyin")` 透传。
+- `product_documents/规则包/douyin_rule_packs.v1.json:21-35`:当前真实 JSON 只有 `dispatch_content / platform=douyin` 一条 enabled dispatch。
+- `product_documents/规则包/douyin_rule_packs.v1.json:301-362`:scorecard 已有跨平台基础两维和 douyin-only `fifty_plus`,M3 不需要新增维度。
+- `tech_documents/数据接口与来源/score_thresholds.json:1-19`:门槛真源是独立 `default + douyin`;快手/视频号没有 override,先走 default。
+- `tests/test_policy_dispatch.py:93-117`:当前已有 synthetic platform dispatch 测试,但没有真实 JSON 三平台 dispatch 测试。
+
+## 修改范围
+
+- `product_documents/规则包/douyin_rule_packs.v1.json`
+  - 在 `rule_pack_dispatch` 中新增 `kuaishou` 与 `shipinhao` 两条 Content/video dispatch。
+  - 两条 dispatch 先指向当前标准 content rule pack,减少未来代码变动。
+- `tests/test_policy_dispatch.py`
+  - 从 synthetic platform dispatch 增加到真实 JSON dispatch 验证。
+
+## 不修改范围
+
+- 不新增 `product_documents/规则包/kuaishou_rule_packs*.json` 或 `shipinhao_rule_packs*.json`。
+- 不改 `content_agent/integrations/policy_json.py` 的 dispatch 选择逻辑,除非真实测试暴露 bug。
+- 不改 evaluator、validation、flow ledger。
+- 不改 `score_thresholds.json` 数值;快手/视频号先走 default。
+- 不改 Excel 或 `build_config_from_excel.py`。
+
+## 涉及文件 / 函数 / 类
+
+- `product_documents/规则包/douyin_rule_packs.v1.json::rule_pack_dispatch`
+- `content_agent/integrations/policy_json.py::JsonPolicyBundleStore.load_policy_bundle`
+- `content_agent/integrations/policy_json.py::_select_dispatch`
+- `content_agent/integrations/policy_json.py::_enabled_dispatches`
+- `content_agent/integrations/policy_json.py::_build_rule_pack_by_entity`
+- `tests/test_policy_dispatch.py`
+
+## 数据合同
+
+新增 dispatch 建议:
+
+```json
+{
+  "dispatch_id": "dispatch_content_kuaishou",
+  "platform": "kuaishou",
+  "runtime_stage": "V1.0",
+  "strategy_version": "V4",
+  "target_entity": "Content",
+  "content_format": "video",
+  "rule_pack_id": "douyin_content_discovery_rule_pack_v1",
+  "rule_pack_version": "4.0.0",
+  "dispatch_enabled": true,
+  "priority": 1,
+  "fallback_policy": "fail_if_not_matched_or_multi_matched",
+  "notes": "M3 runtime dispatch for kuaishou Content/video; shares standard V4 content rule pack."
+}
+```
+
+`shipinhao` 同结构,`dispatch_id="dispatch_content_shipinhao"`、`platform="shipinhao"`。
+
+M3A 不新增 `score_thresholds` platform override:
+
+- douyin:继续合并 douyin override。
+- kuaishou:走 default。
+- shipinhao:走 default。
+
+## 实施步骤
+
+1. 在 `rule_pack_dispatch` 追加快手、视频号 dispatch,保持 `target_entity=Content`、`content_format=video`、`strategy_version=V4`。
+2. 两条 dispatch 指向当前 enabled content rule pack,不复制规则包正文。
+3. 扩 `tests/test_policy_dispatch.py`:
+   - 新增 `test_policy_bundle_loads_real_dispatch_for_three_short_video_platforms`。
+   - 断言三平台 `bundle["platform"]`、`dispatch.platform`、`rule_pack_by_entity["Content"].dispatch.platform` 均匹配。
+   - 断言三平台 `rule_pack_id` 都指向同一标准 content rule pack。
+4. 保留 unknown platform 失败测试,确保没有 fallback。
+5. 跑 M0 golden gate,确认历史 decision 不变。
+
+## Unit Test
+
+- `tests/test_policy_dispatch.py::test_policy_bundle_accepts_explicit_douyin_platform`
+  - 保留默认 douyin 兼容。
+- 新增 `test_policy_bundle_loads_real_dispatch_for_three_short_video_platforms`
+  - 参数化 `douyin/kuaishou/shipinhao`。
+  - `load_policy_bundle("V4", platform=platform)` 成功。
+  - `bundle["dispatch"]["platform"] == platform`。
+  - `set(bundle["rule_pack_by_entity"]) == {"Content"}`。
+- 更新 `test_policy_bundle_fails_for_unknown_platform_without_dispatch`
+  - unknown 仍失败。
+- 保留 conflict 测试
+  - 确认其它平台 dispatch 不会污染当前平台。
+
+## Integrated Test
+
+- `uv run pytest tests/test_policy_dispatch.py -q`。
+- `uv run pytest tests/test_v5_golden_corpus_baseline.py -q`。
+- `uv run python scripts/validate_rule_pack_config.py`。
+- `uv run python scripts/validate_v4_config_contract.py`。
+
+## 失败归因
+
+- 快手/视频号 dispatch 找不到:检查 `dispatch_enabled`、`platform`、`runtime_stage`、`strategy_version`、`target_entity`、`content_format` 是否匹配。
+- dispatch 指向 missing rule pack:检查 `rule_pack_id/rule_pack_version` 是否与 enabled rule pack 对齐。
+- unknown platform 误通过:说明 selector 出现 fallback 或测试污染真实 JSON。
+- golden gate fail:通常不是 M3A 正常结果,优先检查是否误改 scorecard/threshold/evaluator。
+
+## sub-agent 交叉验证要点
+
+- Code subagent 确认 M2 后 selector 已按 platform 过滤,无需新增 loader 逻辑。
+- JSON/config subagent 确认真实 JSON 只有 douyin dispatch,快手/视频号缺口在配置层。
+- DB/Excel subagent 确认快手已有历史 run 但不是 dispatch 配置来源;M3A 不依赖 DB。

+ 138 - 0
tech_documents/工程落地/v5_implementation_briefs/M3/M3B_Kuaishou_Author_Works_Client_Contract.md

@@ -0,0 +1,138 @@
+# M3B Kuaishou Author Works Client Contract Implementation Brief
+
+状态: implementation brief 草案。
+阶段:M3B。
+
+## 目标
+
+为快手 client 增加 `fetch_author_works()`,让现有 walk engine 可以调用 `/crawler/kuai_shou/blogger` 拉作者作品并归一化为 canonical video item。
+
+M3B 只补快手 client 能力与测试,不改 walk engine、不改 run_service 平台工厂、不接新平台。
+
+## 现有证据
+
+- `content_agent/integrations/kuaishou.py:1-5`:当前注释写 M2 只接 search/detail/account_info,作者作品未暴露。
+- `kuaishou.py:121-147`:`CrawapiKuaishouClient.__init__()` 只有 `keyword_path/detail_path/account_info_path`。
+- `kuaishou.py:148-178`:`from_env()` 只读快手 search/detail/account_info env。
+- `kuaishou.py:69-118`:`_normalize_kuaishou_item()` 已能把快手 item 归一化成 canonical content item。
+- `kuaishou.py:217-251`:search 通过 `_normalize_kuaishou_item()` 返回 canonical items。
+- `kuaishou.py:253-279`:detail 返回 canonical detail item。
+- `tests/test_kuaishou_client.py:53-70`:`_item()` 已提供快手 canonical item fixture。
+- `tests/test_kuaishou_client.py:291-294`:当前测试钉死 `not hasattr(client, "fetch_author_works")`。
+- `product_documents/全平台游走策略.md:28-31`:2026-06-26 实测 `/crawler/kuai_shou/blogger` 返回 20 条。
+- `tech_documents/数据接口与来源/captures`:当前没有 `kuaishou_blogger.json`,所以单测先用 synthetic response,真实 smoke 在 M3D/M3E 验收。
+
+## 修改范围
+
+- `content_agent/integrations/kuaishou.py`
+  - 新增 blogger path 常量/初始化参数。
+  - `from_env()` 支持 `CONTENTFIND_KUAISHOU_BLOGGER_PATH`,默认 `/crawler/kuai_shou/blogger`。
+  - 新增 `fetch_author_works(self, query: dict[str, Any]) -> list[dict[str, Any]]`。
+- `tests/test_kuaishou_client.py`
+  - 改写当前无 author works 测试。
+  - 新增 blogger 请求和 canonical normalization 测试。
+
+## 不修改范围
+
+- 不改 `content_agent/business_modules/walk_engine.py`。
+- 不改 `content_agent/run_service.py`。
+- 不改 `content_agent/integrations/douyin.py` 或 `shipinhao.py`。
+- 不新增真实网络依赖到单测。
+- 不要求本阶段新增 `kuaishou_blogger.json` capture;真实 smoke 证据由 M3D/M3E 约束。
+
+## 涉及文件 / 函数 / 类
+
+- `content_agent/integrations/kuaishou.py::CrawapiKuaishouClient.__init__`
+- `content_agent/integrations/kuaishou.py::CrawapiKuaishouClient.from_env`
+- `content_agent/integrations/kuaishou.py::CrawapiKuaishouClient.fetch_author_works`
+- `content_agent/integrations/kuaishou.py::_normalize_kuaishou_item`
+- `content_agent/integrations/kuaishou.py::_select_video_url_with_detail_fallback`
+- `tests/test_kuaishou_client.py`
+
+## 数据合同
+
+### Request
+
+`fetch_author_works(query)` 接受 walk engine 传入的 query:
+
+```json
+{
+  "platform_author_id": "3xfkwajatdh7p7i",
+  "search_query_id": "author_abcd1234",
+  "discovery_start_source": "pattern_itemset"
+}
+```
+
+请求 crawapi:
+
+```json
+{
+  "account_id": "3xfkwajatdh7p7i"
+}
+```
+
+如果真实接口必须带 `is_cache` 或 `cursor`,实现阶段只允许按真实 smoke 结果添加最小字段;不得增加分页循环。
+
+### Response
+
+兼容快手 search 的 list block:
+
+- `data.data`:list[dict]
+- `data.has_more`:bool
+- `data.next_cursor`:str|null
+
+每条 item 复用 `_normalize_kuaishou_item()` 输出,并覆盖:
+
+- `previous_discovery_step="author_works"`
+- `content_metadata_source="kuaishou_blogger"`
+- `search_query_id=query["search_query_id"]`
+- `discovery_start_source=query["discovery_start_source"]`
+
+## 实施步骤
+
+1. 在 `kuaishou.py` 增加 `BLOGGER_RATE_LIMIT_BUCKET = "kuaishou_blogger"`。
+2. `__init__()` 增加 `blogger_path="/crawler/kuai_shou/blogger"`,保存为 `self.blogger_path`。
+3. `from_env()` 增加 `CONTENTFIND_KUAISHOU_BLOGGER_PATH` 可选配置,默认 `/crawler/kuai_shou/blogger`。
+4. 实现 `fetch_author_works(query)`:
+   - 从 `query["platform_author_id"]` 取 account id。
+   - 调 `_post_json(self.blogger_path, {"account_id": account_id}, operation="blogger", rate_limit_bucket=BLOGGER_RATE_LIMIT_BUCKET)`。
+   - 读取 `data.data` list、`has_more`、`next_cursor`。
+   - 逐条调用 `_normalize_kuaishou_item(query, item, index, has_more, next_cursor, selection)`。
+   - 将 `previous_discovery_step` 改为 `author_works`。
+   - 将 `content_metadata_source` 改为 `kuaishou_blogger`。
+5. 单测不访问真实网络,用 `FakeHttpClient` synthetic response。
+
+## Unit Test
+
+- 改写 `tests/test_kuaishou_client.py::test_kuaishou_has_no_author_works_client`
+  - 新口径为 `test_kuaishou_fetch_author_works_maps_canonical_fields`。
+- 新测应断言:
+  - 请求 URL 以 `/crawler/kuai_shou/blogger` 结尾。
+  - 请求 body 为 `{"account_id": "3xfkwajatdh7p7i"}`。
+  - 输出 `platform == "kuaishou"`。
+  - 输出 `platform_content_id`、`platform_author_id`、`play_url`、`statistics`。
+  - `previous_discovery_step == "author_works"`。
+  - `content_metadata_source == "kuaishou_blogger"`。
+- 新增 `test_kuaishou_from_env_sets_blogger_path`
+  - env 未配置时默认 path 正确。
+  - env 配置时可覆盖。
+- 保留现有 search/detail/account_info 测试不变。
+
+## Integrated Test
+
+- `uv run pytest tests/test_kuaishou_client.py -q`。
+- `uv run pytest tests/test_douyin_client.py tests/test_shipinhao_client.py -q`,确认其它平台 client 未误伤。
+- M3E 实现验收中加真实 smoke:单发 `/crawler/kuai_shou/blogger`,间隔遵守 profile 15s。
+
+## 失败归因
+
+- 请求 404/路径错:查 `blogger_path` 默认值与 env 覆盖。
+- 输出没有 `play_url`:查 `_select_video_url_with_detail_fallback()` 是否拿到 blogger item 的 `video_url_list`。
+- walk engine 回灌后断链:查输出是否有 `search_query_id`、`platform_content_id`、`discovery_start_source`。
+- 单测仍断言无 method:说明旧测试未改写。
+
+## sub-agent 交叉验证要点
+
+- Code subagent 确认快手当前无 `fetch_author_works`,但 normalizer 可复用。
+- JSON/config subagent 确认快手 blogger 当前无 capture,因此单测使用 synthetic response。
+- DB/Excel subagent 确认快手历史 run 里 954 条作者边被 profile gate 阻断,M3B 是打开能力前提。

+ 141 - 0
tech_documents/工程落地/v5_implementation_briefs/M3/M3C_Profile_Edge_And_Walk_Runtime_Alignment.md

@@ -0,0 +1,141 @@
+# M3C Profile Edge And Walk Runtime Alignment Implementation Brief
+
+状态: implementation brief 草案。
+阶段:M3C。
+
+## 目标
+
+把快手 profile 的作者作品边从 blocked 改为 supported,并确保 walk runtime 只靠现有 profile gate + client method 生效。
+
+M3C 不改 walk engine 主流程。视频号作者边保持 blocked。
+
+## 现有证据
+
+- `tech_documents/数据接口与来源/platform_profiles/kuaishou.json:117-123`:快手 `author_to_works`/`author_work_to_content` 当前 blocked。
+- `tech_documents/数据接口与来源/platform_profiles/shipinhao.json:47-48`:视频号 `author_to_works`/`author_work_to_content` 当前 blocked。
+- `tech_documents/数据接口与来源/platform_profiles/douyin.json:56-57`:抖音作者边 supported。
+- `content_agent/integrations/walk_graph_json.py`:M1 后 `edge_supported()` 语义为只有 `supported` 返回 true。
+- `content_agent/business_modules/walk_engine.py:437-450`:profile 不支持作者边时,每个唯一作者写 `skipped / edge_blocked_by_platform_profile`,不调用平台。
+- `walk_engine.py:452-456`:profile 支持后动态查 `fetch_author_works` 或 `author_works`。
+- `walk_engine.py:506-567`:client 返回 works 后,回灌 author query、prefetched batch、frontier。
+- `tests/test_v4_walk_contract.py:61`:当前测试钉快手/视频号作者边 blocked,需同步更新快手口径。
+- `scripts/validate_v4_config_contract.py:424`:validator 当前可能仍带快手作者边 blocked 的旧合同,需同步更新。
+- 国内 DB:快手 `author_to_works` 954/954 均为 `skipped / edge_blocked_by_platform_profile`,证明 profile gate 已是运行事实。
+
+## 修改范围
+
+- `tech_documents/数据接口与来源/platform_profiles/kuaishou.json`
+  - `endpoints.blogger` 补 `/crawler/kuai_shou/blogger`。
+  - `author_to_works.status` 改 `supported`。
+  - `author_work_to_content.status` 改 `supported`。
+  - note 改为引用 2026-06-26 实测。
+- `scripts/validate_v4_config_contract.py`
+  - 更新三短视频平台作者边合同:快手 supported,视频号 blocked。
+- `tests/test_walk_graph_config.py`
+- `tests/test_v4_walk_contract.py`
+- `tests/test_walk_engine_deep_frontier.py`
+
+## 不修改范围
+
+- 不改 `content_agent/business_modules/walk_engine.py`。
+- 不改 walk budgets。
+- 不改视频号 profile 作者边。
+- 不改抖音 profile。
+- 不改 DB schema 或历史 fixture。
+
+## 涉及文件 / 函数 / 类
+
+- `tech_documents/数据接口与来源/platform_profiles/kuaishou.json`
+- `tech_documents/数据接口与来源/platform_profiles/shipinhao.json`
+- `content_agent/integrations/walk_graph_json.py::edge_supported`
+- `content_agent/integrations/walk_graph_json.py::WalkGraphStore.load_profile`
+- `content_agent/business_modules/walk_engine.py::_expand_authors`
+- `content_agent/business_modules/walk_engine.py::_author_walk_action`
+- `scripts/validate_v4_config_contract.py`
+- `tests/test_walk_graph_config.py`
+- `tests/test_v4_walk_contract.py`
+- `tests/test_walk_engine_deep_frontier.py`
+
+## 数据合同
+
+快手 profile 目标:
+
+```json
+{
+  "endpoints": {
+    "blogger": {
+      "path": "/crawler/kuai_shou/blogger",
+      "params": {"account_id": "str"},
+      "response_shape": "normalized(channel_*)",
+      "evidence": "2026-06-26 product_documents/全平台游走策略.md 实测 20 条"
+    }
+  },
+  "edges": {
+    "author_to_works": {
+      "status": "supported",
+      "note": "2026-06-26 实测 blogger 返回 20 条"
+    },
+    "author_work_to_content": {
+      "status": "supported"
+    }
+  }
+}
+```
+
+视频号保持:
+
+```json
+"author_to_works": {"status": "blocked", "note": "blogger code=25011"}
+```
+
+## 实施步骤
+
+1. 快手 profile 补 `endpoints.blogger`,并更新作者边状态与 note。
+2. 不改视频号 profile。
+3. 同步 `validate_v4_config_contract.py` 中三短视频平台边合同:
+   - douyin author edges supported。
+   - kuaishou author edges supported。
+   - shipinhao author edges blocked。
+4. 更新 `tests/test_walk_graph_config.py`:
+   - 参数化三平台作者边状态。
+   - 断言 `edge_supported(kuaishou_profile, "author_to_works") is True`。
+   - 断言 `edge_supported(shipinhao_profile, "author_to_works") is False`。
+5. 更新 `tests/test_v4_walk_contract.py`:
+   - 移除快手 blocked 断言。
+   - 保留视频号 blocked 断言。
+6. 扩 `tests/test_walk_engine_deep_frontier.py`:
+   - 快手 supported + mock client `fetch_author_works` 时产生 success walk action 和 author_works query。
+   - 视频号 blocked 时不调用 mock client,写 skipped reason。
+
+## Unit Test
+
+- `tests/test_walk_graph_config.py`
+  - `test_three_short_video_author_edge_profile_contract`
+  - `test_kuaishou_profile_loads_blogger_endpoint`
+- `tests/test_v4_walk_contract.py`
+  - 快手从 blocked 期望改为 supported。
+  - 视频号仍 blocked。
+- `tests/test_walk_engine_deep_frontier.py`
+  - `test_kuaishou_author_edge_supported_refills_frontier`
+  - `test_shipinhao_author_edge_blocked_skips_without_client_call`
+- `scripts/validate_v4_config_contract.py` 对真实 profile 全通过。
+
+## Integrated Test
+
+- `uv run pytest tests/test_walk_graph_config.py tests/test_v4_walk_contract.py -q`。
+- `uv run pytest tests/test_walk_engine_deep_frontier.py -q`。
+- `uv run python scripts/validate_v4_config_contract.py`。
+- `uv run pytest tests/test_v5_golden_corpus_baseline.py -q`。
+
+## 失败归因
+
+- 快手仍 skipped by profile:profile 仍 blocked 或 test 读到旧文件。
+- 快手调用 client 但无回灌:查 M3B output 是否包含 `platform_content_id` 和 `search_query_id`。
+- 视频号误调用 client:profile 被误改 supported 或 test profile 注入错误。
+- validator 失败:旧合同仍把快手作者边钉死 blocked。
+
+## sub-agent 交叉验证要点
+
+- Code subagent 确认 walk engine 不需要改,只要 profile supported + client method 即可。
+- JSON/config subagent 确认快手 profile 当前 blocked,视频号 blocked,抖音 supported。
+- DB subagent 确认快手历史 954 条 skipped 都来自 profile gate,打开后属于有意行为变化。

+ 140 - 0
tech_documents/工程落地/v5_implementation_briefs/M3/M3D_Source_Evidence_Registry_And_Capture_Reconciliation.md

@@ -0,0 +1,140 @@
+# M3D Source Evidence Registry And Capture Reconciliation Implementation Brief
+
+状态: implementation brief 草案。
+阶段:M3D。
+
+## 目标
+
+修正快手作者作品能力的新旧证据冲突,让 M3 实现有真实来源依据:
+
+- 旧 registry/台账写"快手无独立作者作品接口"。
+- 2026-06-26 新实测证明 `/crawler/kuai_shou/blogger` 返回 20 条。
+- 当前 captures 目录没有 `kuaishou_blogger.json`,实现测试先用 synthetic response,真实 smoke 后补证据。
+
+M3D 只处理证据文档与验收口径,不改变 runtime 行为。
+
+## 现有证据
+
+- `product_documents/全平台游走策略.md:7-10`:明示本次实测纠正旧说法,快手能顺着作者挖作品。
+- `product_documents/全平台游走策略.md:28-33`:快手 `/crawler/kuai_shou/blogger` 实测 20 条。
+- `product_documents/全平台游走策略.md:97-101`:快手可顺着作者拿更多作品。
+- `product_documents/全平台游走策略.md:181-186`:快手列入能顺着作者挖更多作品的平台。
+- `tech_documents/数据接口与来源/crawler_endpoints.registry.json:308-344`:旧 registry 快手 account_info 段写"未抓到独立作者作品列表接口"。
+- `tech_documents/数据接口与来源/接口台账/快手.md:270-271`:旧台账写无 blogger capture,作者到作品暂依赖搜索聚合。
+- `tech_documents/数据接口与来源/游走图谱.md:68`:旧口径仍可能写快手作者作品不可走。
+- `tech_documents/数据接口与来源/00_数据接口总览.md:773`:旧总览仍可能写快手无独立作者作品接口。
+- `tech_documents/数据接口与来源/captures/_run_log.txt:10-14`:当前快手 capture 只有 search/detail/account_info/video_dl。
+- `tech_documents/数据接口与来源/captures/kuaishou_search.json`:快手 search 有 `channel_account_id/name`、统计、视频 URL。
+- `tech_documents/数据接口与来源/captures/kuaishou_detail.json`:快手 detail 有 `video_url_list`,且 detail 的 `share_count` 可能比 search 更可信。
+- `tech_documents/数据接口与来源/captures/kuaishou_account_info.json`:快手 account_info 可补作者档案。
+
+## 修改范围
+
+- `tech_documents/数据接口与来源/crawler_endpoints.registry.json`
+  - 新增或更正快手 blogger endpoint 条目。
+  - 旧 account_info boundary 中"未抓到独立作者作品列表接口"改为历史旧口径,并指向新 blogger 条目。
+- `tech_documents/数据接口与来源/接口台账/快手.md`
+  - 增加 2026-06-26 blogger 实测结果。
+- `tech_documents/数据接口与来源/游走图谱.md`
+  - 快手作者边改为可走。
+- `tech_documents/数据接口与来源/00_数据接口总览.md`
+  - 同步快手 blogger 能力。
+- 可选新增 `tech_documents/数据接口与来源/captures/kuaishou_blogger.json`
+  - 只在实现阶段真实 smoke 成功并确认可脱敏后新增。
+  - 若未新增 capture,必须在 brief/验收记录中保留 synthetic 单测 + smoke 结果说明。
+
+## 不修改范围
+
+- 不修改 `product_documents/全平台游走策略.md` 的新口径。
+- 不为公众号/小红书新增 M3 runtime 文档。
+- 不把快手 blogger 证据写进 DB。
+- 不让单测依赖真实网络 capture。
+
+## 涉及文件 / 函数 / 类
+
+- `tech_documents/数据接口与来源/crawler_endpoints.registry.json`
+- `tech_documents/数据接口与来源/接口台账/快手.md`
+- `tech_documents/数据接口与来源/游走图谱.md`
+- `tech_documents/数据接口与来源/00_数据接口总览.md`
+- `tech_documents/数据接口与来源/captures/kuaishou_blogger.json`(可选)
+- `tests/test_kuaishou_client.py`(synthetic response 合同)
+
+## 数据合同
+
+Registry 新条目建议:
+
+```json
+{
+  "platform": "kuaishou",
+  "source_id": "PLT_KUAISHOU_BLOGGER",
+  "display_name": "快手博主作品列表",
+  "source_type": "http_api",
+  "status": ["verified"],
+  "system": "crawler.aiddit.com",
+  "table_or_endpoint": "/crawler/kuai_shou/blogger",
+  "access_mode": "read",
+  "owner_module": "平台接入模块",
+  "required_env_vars": ["CONTENTFIND_API_CRAWAPI_BASE_URL"],
+  "input_fields": ["account_id"],
+  "output_fields": [
+    "channel_content_id",
+    "channel_account_id",
+    "content_type",
+    "video_url_list",
+    "view_count",
+    "like_count",
+    "collect_count",
+    "comment_count",
+    "share_count",
+    "has_more",
+    "next_cursor"
+  ],
+  "v1_boundary": "2026-06-26 实测 code=0,单次返回 20 条;用于 author_to_works。"
+}
+```
+
+如果真实 smoke 发现 request body 需要 `is_cache` 或 `cursor`,以 smoke 结果更新 `input_fields`。
+
+## 实施步骤
+
+1. 全局搜索旧口径:
+   - `rg -n "快手.*作者作品|无独立作者作品|author_to_works.*快手|blogger" tech_documents product_documents`
+2. 更新 registry:
+   - 新增 `PLT_KUAISHOU_BLOGGER` 或等价条目。
+   - 旧 `PLT_KUAISHOU_ACCOUNT_INFO` boundary 改为不再声明无 blogger。
+3. 更新快手接口台账、游走图谱、接口总览中的旧描述。
+4. 实现阶段执行真实 smoke:
+   - 单发 `/crawler/kuai_shou/blogger`。
+   - 间隔遵守 15s rate limit。
+   - 记录返回数量、关键字段、是否可下载视频。
+5. 若新增 capture:
+   - 脱敏/截断敏感 URL 参数。
+   - 保持现有 capture wrapper 结构。
+6. 若不新增 capture:
+   - 在 M3E 验收记录中说明 synthetic 单测覆盖结构,真实 smoke 覆盖接口可用性。
+
+## Unit Test
+
+- `tests/test_kuaishou_client.py` 使用 synthetic response,不依赖 `kuaishou_blogger.json`。
+- 若新增 capture reader 测试,必须只读本地文件并避免网络。
+
+## Integrated Test
+
+- `uv run pytest tests/test_kuaishou_client.py -q`。
+- 文档一致性检查:
+  - `rg -n "快手.*无独立作者作品|author_to_works 在快手侧暂依赖搜索聚合" tech_documents product_documents`
+  - 预期只剩历史说明或为 0。
+- 真实 smoke 结果作为人工验收附件,不进入常规 pytest/CI。
+
+## 失败归因
+
+- registry 与 profile 冲突:快手 profile supported 但 registry 仍 blocked。
+- 单测通过但 smoke 失败:上游接口时效/参数变化,先更新 registry/capture,再决定是否回滚 profile supported。
+- 新增 capture 导致敏感 URL 泄露:只保留结构化字段或脱敏 URL。
+- 旧文档残留导致后续实现误判:补 `rg` 验收。
+
+## sub-agent 交叉验证要点
+
+- JSON/config subagent 确认当前没有 `kuaishou_blogger.json`,但 `全平台游走策略.md` 是新证据。
+- Code subagent 确认单测可以用 synthetic response,不需要 capture。
+- DB subagent 确认 DB 不存接口 registry,文档修正不需要 migration。

+ 184 - 0
tech_documents/工程落地/v5_implementation_briefs/M3/M3E_Acceptance_Commands_And_Subagent_Audit.md

@@ -0,0 +1,184 @@
+# M3E Acceptance Commands And Subagent Audit Implementation Brief
+
+状态: implementation brief 草案。
+阶段:M3E。
+
+## 目标
+
+定义 M3 的最终验收命令、测试矩阵、DB/Excel guardrail、失败归因与 sub-agent 审计口径。
+
+M3E 不新增业务能力,只确保 M3A-M3D 的实现被正确验证。
+
+## 现有证据
+
+- Code subagent:
+  - `policy_json.py` 已有 platform dispatch 机制。
+  - `run_service.py` real clients 只注册三短视频平台。
+  - `walk_engine.py` 已有 profile gate + dynamic `fetch_author_works`。
+  - `kuaishou.py` 当前无 `fetch_author_works`,测试钉死无 method。
+- JSON/config subagent:
+  - 真实规则包只有 douyin dispatch。
+  - 快手 profile 作者边 blocked,视频号 blocked,抖音 supported。
+  - 快手 blogger 无 capture,但 2026-06-26 策略文档证明上游可用。
+- DB/Excel subagent:
+  - MySQL 8.0.46,只读事务。
+  - 抖音 14 run / 1896 video items;快手 13 run / 1033 video items;视频号 0 run。
+  - 快手 `author_to_works` 954/954 都是 `edge_blocked_by_platform_profile`。
+  - 非 video items 为 0。
+  - config gate 当前通过;M3 不动 Excel。
+
+## 修改范围
+
+- `tests/test_policy_dispatch.py`
+- `tests/test_kuaishou_client.py`
+- `tests/test_walk_graph_config.py`
+- `tests/test_v4_walk_contract.py`
+- `tests/test_walk_engine_deep_frontier.py`
+- `tests/test_v5_golden_corpus_baseline.py`(只作为 regression gate,一般不改)
+- `scripts/validate_v4_config_contract.py`
+- `tech_documents/数据接口与来源/*` 中快手 blogger 证据文档
+
+## 不修改范围
+
+- `sql/`
+- `tech_documents/规则包映射/`
+- `scripts/build_config_from_excel.py`
+- `content_agent/business_modules/rule_judgment/evaluator.py`
+- M0 corpus/fixture
+- 新平台 client 工厂分支
+
+## 涉及文件 / 函数 / 类
+
+- `tests/test_policy_dispatch.py`
+- `tests/test_kuaishou_client.py`
+- `tests/test_douyin_client.py`
+- `tests/test_shipinhao_client.py`
+- `tests/test_walk_graph_config.py`
+- `tests/test_v4_walk_contract.py`
+- `tests/test_walk_engine_deep_frontier.py`
+- `tests/test_v5_golden_corpus_baseline.py`
+- `scripts/run_config_gate.py`
+- `scripts/validate_content_agent_db.py`
+
+## 测试矩阵
+
+| 层级 | 测试 | 验证点 |
+|---|---|---|
+| Unit | policy dispatch | 真实 JSON 三平台 dispatch、unknown platform fail、platform 不串包 |
+| Unit | kuaishou client | blogger path、request body、canonical video item、author_works metadata |
+| Unit | profile contract | 快手作者边 supported、视频号 blocked、抖音不变 |
+| Unit | walk engine | 快手 supported 后回灌;视频号 blocked 后 skip 且不调用 client |
+| Integrated | config gate | Excel byte-equal 与 config validators 继续通过 |
+| Regression | M0 golden gate | 161 条历史 evaluator decision exact replay |
+| DB readonly | schema/data audit | 无 migration;现有数据均 video;快手历史 skip 来源明确 |
+| Guardrail | git diff | 不改 SQL、Excel、build_config_from_excel |
+
+## 验证命令
+
+```bash
+# M3A dispatch
+uv run pytest tests/test_policy_dispatch.py -q
+
+# M3B client
+uv run pytest tests/test_kuaishou_client.py tests/test_douyin_client.py tests/test_shipinhao_client.py -q
+
+# M3C walk/profile
+uv run pytest tests/test_walk_graph_config.py tests/test_v4_walk_contract.py tests/test_walk_engine_deep_frontier.py -q
+
+# M0 evaluator regression
+uv run pytest tests/test_v5_golden_corpus_baseline.py -q
+
+# Config gate: M3 must not break Excel byte-equal/current validators
+uv run --with openpyxl python scripts/run_config_gate.py
+
+# DB schema/data readonly validation
+uv run --with pymysql python scripts/validate_content_agent_db.py --env-file .env --allow-missing-tables
+
+# Guardrail: these should stay empty
+git diff -- sql tech_documents/规则包映射 scripts/build_config_from_excel.py
+```
+
+## DB / 只读验收
+
+DB 证据只进入文档和人工验收,不进入常规 pytest/CI。
+
+推荐 SQL 摘要:
+
+```sql
+SELECT platform, COUNT(*) runs
+FROM content_agent_runs
+GROUP BY platform;
+
+SELECT platform, platform_content_format, COUNT(*) items
+FROM content_agent_discovered_content_items
+GROUP BY platform, platform_content_format;
+
+SELECT r.platform, w.edge_id, w.walk_status, w.reason_code, COUNT(*) n
+FROM content_agent_walk_actions w
+JOIN content_agent_runs r ON r.run_id = w.run_id
+WHERE w.edge_id = 'author_to_works'
+GROUP BY r.platform, w.edge_id, w.walk_status, w.reason_code;
+```
+
+当前只读结果:
+
+- douyin:14 run,1896 video items,4748 walk_actions。
+- kuaishou:13 run,1033 video items,2867 walk_actions。
+- shipinhao:0 run。
+- kuaishou author_to_works:954/954 `skipped / edge_blocked_by_platform_profile`。
+- non-video items:0。
+
+## Guardrail
+
+M3 允许改:
+
+- 规则包 dispatch JSON。
+- 快手 client。
+- 快手 profile 作者边。
+- 相关接口证据文档。
+- 相关 tests/validator。
+
+M3 不允许改:
+
+- `sql/`
+- Excel workbooks。
+- `scripts/build_config_from_excel.py`
+- evaluator 判定逻辑。
+- M0 corpus。
+- `run_service.py` 的平台工厂新增平台。
+
+## 真实 smoke 口径
+
+快手 blogger smoke 不放入常规 pytest:
+
+1. 使用真实 crawapi host。
+2. 输入已知快手 `channel_account_id`。
+3. 单发 `/crawler/kuai_shou/blogger`,间隔遵守 15s。
+4. 记录返回 code、条数、关键字段。
+5. 若结果可脱敏,补 `kuaishou_blogger.json` capture;否则写入 M3D 验收记录。
+
+## 失败归因
+
+- `tests/test_policy_dispatch.py` 失败:归 M3A,查 dispatch 配置。
+- `tests/test_kuaishou_client.py` 失败:归 M3B,查 request body/path/normalizer。
+- `tests/test_walk_graph_config.py` 或 `test_v4_walk_contract.py` 失败:归 M3C,查 profile 与 validator 合同。
+- `tests/test_walk_engine_deep_frontier.py` 失败:归 M3C,查 profile gate 与 client method 是否对齐。
+- `tests/test_v5_golden_corpus_baseline.py` 失败:误碰 evaluator/threshold/scorecard/corpus,不是 M3 合理结果。
+- `run_config_gate.py` 失败:M3 误碰 Excel/JSON byte-equal 或 config validator。
+- `git diff -- sql ...` 非空:误进 M4/DB 范围。
+
+## sub-agent 交叉验证要点
+
+- Code subagent 输出需要覆盖:
+  - policy dispatch current shape。
+  - run_service 三平台边界。
+  - walk_engine author_to_works 链路。
+  - 快手无 method 的代码/测试残留。
+- JSON/config subagent 输出需要覆盖:
+  - 规则包 dispatch 缺口。
+  - profile 三平台边状态。
+  - capture/registry 新旧证据冲突。
+- DB/Excel subagent 输出需要覆盖:
+  - 国内库平台数量和 video-only 事实。
+  - 快手 author_to_works profile gate skip 数。
+  - Excel gate pass 与 M3 不重组 Excel。

+ 212 - 0
tech_documents/工程落地/v5_implementation_briefs/M4/00_M4_Brief_Index.md

@@ -0,0 +1,212 @@
+# V5 M4 Excel 全平台大矩阵与回流入口 Brief Index
+
+状态: implementation brief 草案。
+阶段:M4。
+上游依赖:M0 golden evaluator gate、M1 四态/边目录地基、M2 platform dispatch 与配置化权重、M3 三短视频 dispatch 与快手作者边。
+
+## 目标定位
+
+M4 只做五件事:
+
+1. 把 Excel 从"只 overlay 旧 JSON"升级成可治理全平台大矩阵,新增行/新增 sheet 必须能生成运行 JSON。
+2. 把权重与门槛纳入 Excel 审批治理,只有 `approval_status=approved` 且 `runtime_enabled=true` 才进入运行 JSON。
+3. 把游走预算整理成 `platform + edge_id` 矩阵,先保持现有 `walk_policy.json` 数值等价,只给快手 `author_to_works` 显式平台行。
+4. 用 fake feedback 验证后验表现能 join 回历史 rule decision,但不自动学习、不自动改规则。
+5. 死配置只先归档进 git,不自动删除。
+
+M4 不是"学习算法上线"阶段,也不是"全平台 runtime 接入"阶段。Excel 里出现的平台只是台账和未来能力位,不代表公众号/小红书/图文链路启用。
+
+## 子 brief 执行口径(A->E 线性)
+
+| 子 brief | 文件 | 核心交付 | 依赖 |
+|---|---|---|---|
+| M4A | `M4A_Excel_Runtime_Governance_And_Converter_Structure.md` | Excel 新增行生成 JSON、审批双闸、no-op sheet 治理 | M3 当前 config gate 通过 |
+| M4B | `M4B_Score_Weight_And_Threshold_Profile_Matrix.md` | 通用权重 profile 矩阵 + 门槛 profile 矩阵 | M4A converter 新增结构能力 |
+| M4C | `M4C_Walk_Budget_And_Platform_Edge_Matrix.md` | 平台边能力矩阵 + `platform + edge_id` 预算矩阵 | M4A |
+| M4D | `M4D_Fake_Feedback_Join_And_Learning_Guardrail.md` | fake feedback join 验证 + 不自动反写 guardrail | M4A-M4B |
+| M4E | `M4E_Dead_Config_Archive_Acceptance_And_Subagent_Audit.md` | 死配置归档、验收命令、DB/Excel guardrail、失败归因 | M4A-M4D |
+
+## 不处理范围(红线)
+
+- 不改 `sql/`,不新增 migration,不新增 DB 表或列。
+- 不接真实回流源,不做跨 run 学习器,不自动改规则、权重、门槛、walk strategy 或预算。
+- 不新增 runtime 平台;V5 runtime 仍只启用 douyin/kuaishou/shipinhao。
+- 不绕过 M0 golden evaluator gate。
+- 不把 `score_thresholds.json` 当死配置删除;它当前仍是 evaluator 门槛真源。
+- 不把 `content_discovery_id` 写成 fake feedback join key。
+- 不用 Excel 是否有一行来判断 runtime 是否启用。
+
+## 现有证据(共享,子 brief 各自细引)
+
+### Code / JSON
+
+- `content_agent/business_modules/rule_judgment/evaluator.py:267-296`:V4 evaluator 已读 `score_weight_profiles`,但 selector 仍按三套旧 profile 名选择。
+- `evaluator.py:168-170/334-349`:生效门槛来自 `policy_bundle["score_thresholds"]`,并随 decision 写入 `scorecard.score_thresholds`。
+- `content_agent/integrations/policy_json.py:36-40`:loader 读取独立 `tech_documents/数据接口与来源/score_thresholds.json`。
+- `policy_json.py:122-131/176-183`:当前真实运行字段是 `dispatch_enabled` 与 `enabled`,不是通用 `runtime_enabled`。
+- `content_agent/business_modules/learning_review.py:21-39/641-656/730-753/766-775`:feedback 只进入 summary/recommendation,建议固定 `requires_human_approval=True`,不自动反写。
+- `content_agent/business_modules/walk_engine.py:298-308/478-486`:预算耗尽写 `reason_code="budget_exhausted"` 与 `budget_tier="blocked"`。
+- `product_documents/规则包/douyin_rule_packs.v1.json:376-390`:当前只有 `default_two_dimension`、`douyin_fifty_plus_ok`、`douyin_fifty_plus_not_attempted` 三套权重。
+- `tech_documents/数据接口与来源/score_thresholds.json:1-20`:当前门槛是 `default + douyin`。
+- `tech_documents/数据接口与来源/walk_policy.json:6-15`:当前总闸 10、max_depth 5、author/tag 各 5、每作者作品 5。
+
+### Excel / Converter
+
+- 规则包 workbook 当前 13 张 sheet;converter 实际读取 8 张:`rule_pack_dispatch`、`decision_reason_codes`、`effect_status_mapping`、`query_effect_aggregation`、`hard_gate_rules`、`scorecard_dimensions`、`scorecard_scoring_rules`、`threshold_actions`。
+- 规则包 workbook 当前 `rule_package_meta`、`rule_pack_catalog`、`input_contract_fields`、`decision_action_catalog`、`字段注释` 是 ledger/no-op。
+- 游走 workbook 当前 4 张 sheet:`walk_edge_catalog`、`walk_rule_pack_binding`、`v4_walk_gate`、`walk_fact_contract`。
+- `scripts/build_config_from_excel.py:1-15`:当前 converter 明确是 overlay-onto-base,不做结构新增。
+- `build_config_from_excel.py:116-133`:只有 JSON 路径存在才写。
+- `build_config_from_excel.py:147-162`:Excel 行 ID 命中 JSON 对象才 overlay;命不中直接跳过。
+- `scripts/run_config_gate.py:19-23`:当前 gate 包含 Excel→JSON byte-equal,但 byte-equal 通过不代表新增 sheet 会生效。
+
+### DB / Schema
+
+- 国内库 `content_agent_*` 共 25 张表。
+- 关键计数:`content_agent_rule_decisions=2929`,`content_agent_discovered_content_items=2929`,`content_agent_walk_actions=7615`,`content_agent_performance_feedback=0`。
+- `rule_decisions` action 分布:`KEEP_CONTENT_FOR_REVIEW=995`,`ADD_TO_CONTENT_POOL=839`,`REJECT_CONTENT=703`,`TECHNICAL_RETRY_REQUIRED=392`。
+- fake join 当前 `2929/2929` 命中,正确键是 `run_id + policy_run_id + decision_target_id = platform_content_id`。
+- `decision_target_id = content_discovery_id` 当前 `0/2929` 命中,不得作为 join key。
+- 无 config/rule_pack/profile/threshold/weight/archive/deprecated/legacy 配置表;M4 不做 migration。
+- `walk_actions` 已有 `edge_id / budget_tier / reason_code`;`budget_exhausted=250`,`edge_blocked_by_platform_profile=954`。
+
+## 数据合同(汇总)
+
+### 审批与运行启用
+
+合法 `approval_status`:
+
+```text
+draft
+waiting_for_human_reivew
+approved
+rejected
+deprecated
+```
+
+运行 JSON 生成条件:
+
+```text
+approval_status == "approved" AND runtime_enabled == true
+```
+
+审批审计字段只保留:
+
+```text
+approval_status
+last_reviewed_at
+owner
+```
+
+### Weight / Threshold Profiles
+
+`score_weight_profiles` 长期矩阵字段:
+
+```text
+profile_id, profile_label, historical_alias_ids, rule_pack_id, rule_pack_version,
+platform_scope, content_format, applies_when, dimension_key, weight_percent,
+normalization_policy, normalization_reason, missing_dimension_policy,
+critical_dimension, runtime_enabled, approval_status, priority,
+fallback_profile_id, learning_tunable, 注释
+```
+
+`score_threshold_profiles` 一行一个门槛:
+
+```text
+profile_id, platform_scope, content_format, threshold_key, threshold_value,
+applies_when, runtime_enabled, approval_status, last_reviewed_at, owner,
+priority, learning_tunable, 注释
+```
+
+### Walk Matrix
+
+`platform_edge_matrix.status` 固定四态:
+
+```text
+supported / blocked / no_interface / planned
+```
+
+`walk_policy_budget_matrix` 以 `platform + edge_id` 为主键。初始迁移:
+
+- `platform="default"`:原样承接现有 global/edge 预算。
+- `platform="kuaishou", edge_id="author_to_works"`:显式一行,数值不调大。
+
+### Fake Feedback Join
+
+正确 join key:
+
+```text
+rule_decisions.run_id = discovered_content_items.run_id
+rule_decisions.policy_run_id = discovered_content_items.policy_run_id
+rule_decisions.decision_target_id = discovered_content_items.platform_content_id
+```
+
+不得使用 `content_discovery_id`。
+
+## M4 已拍板口径
+
+- `waiting_for_human_reivew` 作为长期枚举值保留。
+- 权重和门槛都进 Excel,但必须人工审批后生成 JSON。
+- `approval_status + runtime_enabled` 是双闸。
+- 门槛一行一个 `threshold_key + threshold_value`。
+- fake feedback 从 M0 golden corpus 和当前 DB 各选样本。
+- 死配置归档目录进入 git;M4 不自动删除。
+- no-op sheet 不删,按 `ledger_only` 或后续 converter 候选处理。
+
+## 验证命令(汇总)
+
+```bash
+uv run --with openpyxl python scripts/build_config_from_excel.py --check
+uv run --with openpyxl python scripts/validate_config_excel_sync.py
+uv run python scripts/validate_rule_pack_config.py
+uv run python scripts/validate_v4_config_contract.py
+python3 scripts/run_config_gate.py
+uv run pytest tests/test_v5_golden_corpus_baseline.py -q
+uv run --with pymysql python scripts/validate_content_agent_db.py --env-file .env --allow-missing-tables
+git diff -- sql
+```
+
+## Unit Test(汇总)
+
+- `tests/test_config_tooling.py`
+  - Excel 新增 runtime 行不能静默跳过。
+  - 未 `approved + runtime_enabled` 的行不进入运行 JSON。
+  - `waiting_for_human_reivew` 是合法枚举。
+- `tests/test_v4_rule_pack_contract.py` / `tests/test_rule_judgment_scorecard.py`
+  - 通用权重 profile selector 支持 alias、platform_scope、applies_when、priority。
+  - 默认权重加总必须 100;`raw_sum` 允许不等于 100 且必须有 reason。
+  - `missing_dimension_policy=technical_retry` 记录缺失维度和原因。
+- `tests/test_v4_thresholds_per_platform.py`
+  - `score_threshold_profiles` 生成运行态 `score_thresholds.json`。
+  - 门槛一行一个 `threshold_key + threshold_value`。
+- `tests/test_walk_graph_config.py` / `tests/test_walk_engine_budget.py`
+  - `walk_policy_budget_matrix` 迁移后预算数值与现有 `walk_policy.json` 等价。
+  - 快手 `author_to_works` 有显式预算行。
+  - 超限仍写 `budget_exhausted`,展示层映射 `budget_blocked`。
+- `tests/test_p8_performance_feedback.py` / `tests/test_p8_no_auto_mutation.py`
+  - fake feedback join 命中。
+  - learning_review 只产建议,不自动反写规则/门槛/预算。
+
+## Integrated Test
+
+- `scripts/run_config_gate.py`:Excel→JSON、config validators、V4 config contract 继续通过。
+- `tests/test_v5_golden_corpus_baseline.py`:161 条历史 evaluator decision exact replay。
+- `validate_content_agent_db.py`:证明 M4 无 DB migration 需求。
+- fake join SQL:证明 M0 corpus 与当前 DB 样本都可回连 decision/content。
+- archive manifest check:每个归档项有 no-runtime-read 证据和 restore instruction。
+
+## 失败归因(汇总)
+
+- Excel 改了但 JSON 没变:查 M4A converter spec 是否仍 overlay-only 或新行未进入 index。
+- 草稿行进入运行 JSON:查 `approval_status/runtime_enabled` 双闸过滤。
+- evaluator golden fail:查 M4B 是否误改权重/门槛语义或删除 `score_thresholds.json`。
+- walk 预算漂移:查 M4C 是否没有原样迁移 default 预算。
+- fake join 命中率不足:查是否误用 `content_discovery_id` 或缺 run/policy 维度。
+- learning_review 产生 patch 字段:查 M4D guardrail。
+- archive 后 config gate fail:查 M4E 是否归档了仍被 runtime 读取的真源。
+
+## sub-agent 交叉验证要点
+
+- Code/JSON subagent:确认 evaluator 权重/门槛现状、learning_review 不反写、walk_engine 预算留痕。
+- Excel/converter subagent:确认 workbook sheet、overlay-only 限制、gate 当前 pass 但新增结构不会自动生效。
+- DB/schema subagent:确认 25 张表、关键 count、fake join key、performance_feedback 0 行、无配置表/migration 需求。

+ 152 - 0
tech_documents/工程落地/v5_implementation_briefs/M4/M4A_Excel_Runtime_Governance_And_Converter_Structure.md

@@ -0,0 +1,152 @@
+# M4A Excel Runtime Governance And Converter Structure Implementation Brief
+
+状态: implementation brief 草案。
+阶段:M4A。
+
+## 目标
+
+把 Excel 从"只覆盖已有 JSON 值"升级成 M4 全平台大矩阵的真实治理入口:
+
+- Excel 新增 runtime 行必须能生成运行 JSON,不能静默跳过。
+- `approval_status=approved` 且 `runtime_enabled=true` 才能进入运行 JSON。
+- no-op sheet 明确是 `ledger_only` 还是后续 converter 候选。
+- 当前 byte-equal gate 继续保留,但升级成能发现新增行丢失。
+
+M4A 只处理 converter/gate/治理字段底座,不改 evaluator、walk engine、DB schema。
+
+## 现有证据
+
+- `scripts/build_config_from_excel.py:1-15`:当前脚本说明是 overlay-onto-base,只覆盖已有 JSON path。
+- `build_config_from_excel.py:35-38`:固定两本 Excel 与两份 runtime JSON 路径。
+- `build_config_from_excel.py:60-73`:规则包只读 8 张 sheet。
+- `build_config_from_excel.py:78-84`:游走只读 4 张 sheet。
+- `build_config_from_excel.py:116-133`:`_set_if_exists()` 只在路径存在时写值。
+- `build_config_from_excel.py:147-162`:`overlay_workbook()` 只有 Excel 行 ID 命中 JSON 对象才 overlay。
+- `scripts/validate_config_excel_sync.py:23-29`:当前只做 build 后 canonical byte-equal。
+- `scripts/run_config_gate.py:19-23`:config gate 当前包含 `excel_json_byte_equal` 与 `excel_json_sync`。
+- 规则包 workbook 当前 13 张 sheet,其中 `rule_package_meta/rule_pack_catalog/input_contract_fields/decision_action_catalog/字段注释` 不进 converter。
+- 游走 workbook 当前没有 `walk_policy_budget_matrix`、`platform_edge_matrix`、`dead_config_archive_manifest`。
+
+## 修改范围
+
+- `scripts/build_config_from_excel.py`
+  - 增强 SheetSpec 或等价结构,支持 runtime sheet 新增行生成 JSON。
+  - 对 `approval_status/runtime_enabled` 做统一过滤。
+  - 对 `ledger_only` sheet 明确不生成 runtime JSON。
+- `scripts/validate_config_excel_sync.py`
+  - 报告新增行是否进入 JSON,不只判断 byte-equal。
+- `scripts/run_config_gate.py`
+  - 保持现有 gate 顺序,补 M4 converter/approval 检查。
+- 两本 Excel
+  - 增加 M4 必要字段,但不把 no-op sheet 误接 runtime。
+- tests
+  - 覆盖新增行生成、双闸过滤、合法审批枚举。
+
+## 不修改范围
+
+- 不改 `content_agent/business_modules/rule_judgment/evaluator.py`。
+- 不改 `content_agent/business_modules/walk_engine.py`。
+- 不改 `sql/`。
+- 不新增 DB 配置表。
+- 不把公众号/小红书 runtime 启用。
+- 不删除现有 sheet。
+
+## 涉及文件 / 函数 / 类
+
+- `scripts/build_config_from_excel.py::SheetSpec`
+- `scripts/build_config_from_excel.py::_set_if_exists`
+- `scripts/build_config_from_excel.py::overlay_workbook`
+- `scripts/build_config_from_excel.py::build`
+- `scripts/validate_config_excel_sync.py::main`
+- `scripts/run_config_gate.py`
+- `tests/test_config_tooling.py`
+
+## 数据合同
+
+合法审批枚举:
+
+```text
+draft
+waiting_for_human_reivew
+approved
+rejected
+deprecated
+```
+
+运行 JSON 过滤:
+
+```python
+row["approval_status"] == "approved" and row["runtime_enabled"] is True
+```
+
+审计字段:
+
+```text
+approval_status
+last_reviewed_at
+owner
+```
+
+sheet 状态:
+
+| 状态 | 语义 |
+|---|---|
+| `runtime_required` | 必须生成或校验运行 JSON |
+| `converter_read` | 当前 converter 已读取 |
+| `runtime_compat_copy` | 兼容副本,迁移后可归档 |
+| `ledger_only` | 台账/说明,不直接进入运行 JSON |
+
+## 实施步骤
+
+1. 在 M4 brief 中要求先为 SheetSpec 增加 sheet mode 或等价配置,区分 `runtime_required` 与 `ledger_only`。
+2. 明确 converter 新增结构能力:
+   - 现有 overlay 保留给旧 sheet。
+   - 新 runtime sheet 必须可从 Excel rows 构建 JSON object/list。
+   - 未命中的 runtime 行必须报错,不得静默跳过。
+3. 统一解析 `runtime_enabled` bool 与 `approval_status` 枚举。
+4. 只有双闸通过的 row 才进入运行 JSON。
+5. no-op sheet 处理:
+   - `rule_package_meta/rule_pack_catalog/decision_action_catalog/字段注释` 保持 `ledger_only`。
+   - `input_contract_fields` 暂保留业务合同价值,后续优先评估接 converter;未接入时必须只读标注。
+6. 扩 `validate_config_excel_sync.py` 输出:
+   - skipped runtime rows。
+   - invalid approval rows。
+   - generated runtime object count。
+7. 扩 `run_config_gate.py` 把 M4 converter 结构检查纳入 gate。
+
+## Unit Test
+
+- `tests/test_config_tooling.py::test_excel_runtime_row_generates_json`
+  - 构造临时 workbook/JSON。
+  - 新增 approved+runtime_enabled 行。
+  - 断言 build 输出包含该行。
+- `test_excel_runtime_row_without_approval_is_ledger_only`
+  - `draft/rejected/deprecated/waiting_for_human_reivew` 都不进运行 JSON。
+- `test_waiting_for_human_reivew_is_valid_approval_status`
+  - 钉死已拍板拼写。
+- `test_converter_fails_when_runtime_row_is_silently_skipped`
+  - runtime sheet 新增行找不到生成规则时 fail。
+- `test_ledger_only_sheet_does_not_generate_runtime_json`
+  - 修改 `rule_pack_catalog` 不应直接改变运行 JSON。
+
+## Integrated Test
+
+- `uv run --with openpyxl python scripts/build_config_from_excel.py --check`
+- `uv run --with openpyxl python scripts/validate_config_excel_sync.py`
+- `python3 scripts/run_config_gate.py`
+
+M4A 完成后,现有 byte-equal 仍应通过;新增临时测试必须证明新增行不再被静默跳过。
+
+## 失败归因
+
+- 新增 Excel 行没进 JSON:SheetSpec 仍按旧 overlay-only 处理。
+- 未审批行进入 JSON:双闸过滤位置错误。
+- `waiting_for_human_reivew` 被判非法:枚举没有按已拍板拼写落地。
+- no-op sheet 改动影响 runtime:ledger-only 与 runtime sheet 没隔离。
+- gate 仍全绿但新增行丢失:validate_config_excel_sync 只做 byte-equal,没有新增行审计。
+
+## sub-agent 交叉验证要点
+
+- Excel/converter subagent 确认当前哪些 sheet 被读取、哪些 sheet 是 ledger-only。
+- Code subagent 确认 runtime 当前不认识通用 `runtime_enabled` 字段。
+- DB subagent 确认 M4A 不需要 DB 配置表。

+ 194 - 0
tech_documents/工程落地/v5_implementation_briefs/M4/M4B_Score_Weight_And_Threshold_Profile_Matrix.md

@@ -0,0 +1,194 @@
+# M4B Score Weight And Threshold Profile Matrix Implementation Brief
+
+状态: implementation brief 草案。
+阶段:M4B。
+
+## 目标
+
+把 M2 的三套过渡权重 profile 升级成长期通用矩阵,并把运行门槛从独立 JSON 手工文件纳入 Excel 审批治理:
+
+- `score_weight_profiles` 从 JSON-only 迁入 Excel。
+- `score_threshold_profiles` 新增为门槛治理矩阵。
+- 旧三名只作为历史 alias 保留。
+- evaluator 行为必须通过 M0 golden gate 保持零变化。
+
+M4B 不改门槛数值、不改历史 corpus、不做学习算法自动调参。
+
+## 现有证据
+
+- `content_agent/business_modules/rule_judgment/evaluator.py:267-296`:`_v4_score_weights()` 已从 `score_weight_profiles` 读权重,但仍按三套旧名选择。
+- `evaluator.py:168-170/334-349`:`_v4_thresholds()` 从 `policy_bundle["score_thresholds"]` 合并 default+platform override。
+- `content_agent/integrations/policy_json.py:36-40`:独立加载 `score_thresholds.json`。
+- `product_documents/规则包/douyin_rule_packs.v1.json:376-390`:当前三套权重是 `default_two_dimension`、`douyin_fifty_plus_ok`、`douyin_fifty_plus_not_attempted`。
+- `tech_documents/数据接口与来源/score_thresholds.json:1-20`:当前 default/douyin 门槛数值。
+- `product_documents/规则包/douyin_rule_packs.v1.json:430-457`:规则包内仍有 `thresholds`,但 V4 evaluator 实际不读这里作为门槛真源。
+- `scripts/validate_rule_pack_config.py:187-212`:当前 validator 只校验三套旧 profile 必备和权重为正。
+- `scripts/validate_v4_config_contract.py:602-613`:V4 contract 仍强制三套旧 profile。
+
+## 修改范围
+
+- `tech_documents/规则包映射/规则包映射配置表.xlsx`
+  - 新增 `score_weight_profiles` sheet。
+  - 新增 `score_threshold_profiles` sheet。
+- `product_documents/规则包/douyin_rule_packs.v1.json`
+  - 迁移运行态权重 profile 结构,保留历史 alias。
+- `tech_documents/数据接口与来源/score_thresholds.json`
+  - 改为由 Excel 生成的运行态门槛 JSON。
+- `content_agent/business_modules/rule_judgment/evaluator.py`
+  - selector 从固定三名改为通用 profile 匹配。
+- `content_agent/integrations/policy_json.py`
+  - 继续加载运行态 `score_thresholds.json`,不直接读 Excel。
+- validators/tests
+  - 校验 profile selector、权重加总、raw_sum reason、门槛矩阵。
+
+## 不修改范围
+
+- 不改实际门槛数值。
+- 不改 M0 corpus。
+- 不改 DB。
+- 不把 `thresholds[]` 当唯一运行真源。
+- 不让未审批 profile 进入运行 JSON。
+- 不删除三套旧名 alias,直到 golden gate 与兼容测试稳定。
+
+## 涉及文件 / 函数 / 类
+
+- `content_agent/business_modules/rule_judgment/evaluator.py::_v4_score_weights`
+- `content_agent/business_modules/rule_judgment/evaluator.py::_v4_thresholds`
+- `content_agent/business_modules/rule_judgment/evaluator.py::_v4_scorecard_total`
+- `content_agent/integrations/policy_json.py::JsonPolicyBundleStore.load_policy_bundle`
+- `scripts/validate_rule_pack_config.py::_check_score_weight_profiles`
+- `scripts/validate_v4_config_contract.py::_check_v4_score_weight_profiles`
+- `tests/test_rule_judgment_scorecard.py`
+- `tests/test_douyin_fifty_plus_scoring.py`
+- `tests/test_v4_thresholds_per_platform.py`
+- `tests/test_v4_rule_pack_contract.py`
+
+## 数据合同
+
+### `score_weight_profiles`
+
+字段:
+
+```text
+profile_id, profile_label, historical_alias_ids, rule_pack_id, rule_pack_version,
+platform_scope, content_format, applies_when, dimension_key, weight_percent,
+normalization_policy, normalization_reason, missing_dimension_policy,
+critical_dimension, runtime_enabled, approval_status, priority,
+fallback_profile_id, learning_tunable, 注释
+```
+
+匹配规则:
+
+1. 只看 `approval_status=approved && runtime_enabled=true` 的 profile rows。
+2. `platform_scope` 匹配当前平台或 `*`。
+3. `content_format` 匹配 video 或 `*`。
+4. `applies_when` 只允许:
+   - `always`
+   - `no_extra_dimension_available`
+   - `field_exists:<path>`
+   - `field_missing:<path>`
+   - `field_equals:<path>:<value>`
+5. 多个 profile 匹配时按 `priority` 选最小值或文档指定排序,不得靠 dict 顺序。
+
+权重规则:
+
+- 默认同一 profile 权重加总必须等于 100。
+- 只有 `normalization_policy=raw_sum` 允许不等于 100。
+- `raw_sum` 必须填写 `normalization_reason`。
+- `missing_dimension_policy=technical_retry` 时,缺关键维度要写 failure reason,不能默默按 0 分。
+
+### `score_threshold_profiles`
+
+字段:
+
+```text
+profile_id, platform_scope, content_format, threshold_key, threshold_value,
+applies_when, runtime_enabled, approval_status, last_reviewed_at, owner,
+priority, learning_tunable, 注释
+```
+
+门槛一行一个 `threshold_key + threshold_value`。运行态 JSON 仍生成 evaluator 当前能读的结构:
+
+```json
+{
+  "default": {
+    "pool_total": 70,
+    "pool_query": 70,
+    "review_total": 55,
+    "review_query": 55,
+    "walk_query": 70,
+    "walk_platform": 65,
+    "walk_total": 70
+  },
+  "douyin": {
+    "pool_total": 65,
+    "pool_query": 65,
+    "walk_query": 65,
+    "walk_total": 65
+  }
+}
+```
+
+## 实施步骤
+
+1. 新增 `score_weight_profiles` Excel sheet,把三套旧 profile 迁成通用矩阵 rows。
+2. 为三套旧名写 `historical_alias_ids`,长期 `profile_id` 使用平台无关命名:
+   - `base_video_two_dimension_v1`
+   - `audience_50plus_available_v1`
+   - `audience_50plus_skipped_by_query_gate_v1`
+3. 新增 `score_threshold_profiles` Excel sheet,按一行一个 threshold key 迁移 `score_thresholds.json`。
+4. 扩 converter:
+   - 只从 approved+runtime_enabled rows 生成运行态权重/门槛 JSON。
+   - 未审批 rows 保留在 Excel,不进入 JSON。
+5. 改 evaluator selector:
+   - 优先使用通用 profile 矩阵。
+   - 兼容旧三名 alias。
+   - 缺通用配置时保持当前 fallback 行为。
+6. 扩 validator:
+   - profile 引用的 dimension 必须存在。
+   - priority 唯一且可排序。
+   - weight sum / raw_sum reason / technical_retry missing reason 必须校验。
+   - threshold_key 必须属于 evaluator 认识的门槛集合。
+7. 跑 M0 golden gate,确保 161 条历史 decision 不变。
+
+## Unit Test
+
+- `tests/test_rule_judgment_scorecard.py::test_generic_weight_profile_selector_matches_default_two_dimension`
+  - 无 extra dimension 时走 `base_video_two_dimension_v1`。
+- `test_generic_weight_profile_selector_matches_douyin_fifty_plus_ok`
+  - 抖音 50+ ok 时走 audience profile,总分保持 35/35/30。
+- `test_generic_weight_profile_selector_supports_historical_alias`
+  - 旧三名仍可兼容。
+- `test_weight_profile_requires_sum_100_unless_raw_sum`
+  - 默认非 100 fail;`raw_sum` 且有 reason pass。
+- `test_missing_critical_dimension_marks_technical_retry`
+  - 关键维度缺失时 failure reason 记录缺失 dimension/path。
+- `tests/test_v4_thresholds_per_platform.py::test_threshold_profiles_generate_runtime_score_thresholds`
+  - Excel rows 生成现有 `score_thresholds.json` 等价结构。
+- `test_unapproved_threshold_profile_does_not_enter_runtime_json`
+  - draft/rejected/waiting 不进入运行门槛。
+
+## Integrated Test
+
+- `uv run pytest tests/test_rule_judgment_scorecard.py tests/test_douyin_fifty_plus_scoring.py -q`
+- `uv run pytest tests/test_v4_thresholds_per_platform.py -q`
+- `uv run pytest tests/test_v4_rule_pack_contract.py tests/test_config_tooling.py -q`
+- `uv run pytest tests/test_v5_golden_corpus_baseline.py -q`
+- `uv run python scripts/validate_rule_pack_config.py`
+- `uv run python scripts/validate_v4_config_contract.py`
+- `python3 scripts/run_config_gate.py`
+
+## 失败归因
+
+- 分数漂移:profile 匹配 priority 或 alias 映射错。
+- 门槛漂移:`score_threshold_profiles` 生成的运行态 JSON 与原 `score_thresholds.json` 不等价。
+- 未审批门槛生效:converter 双闸漏掉。
+- raw_sum 未解释也通过:validator 没校验 `normalization_reason`。
+- `technical_retry` 没失败:missing critical dimension 没落到 evaluator failure path。
+- golden gate fail:优先查权重/门槛迁移,不要改 corpus。
+
+## sub-agent 交叉验证要点
+
+- Code subagent 确认 evaluator 当前三旧名选择与 threshold 真源。
+- Excel subagent 确认当前没有 `score_weight_profiles`/`score_threshold_profiles` sheet。
+- DB subagent 确认权重/门槛不在 DB 配置表,无需 migration。

+ 167 - 0
tech_documents/工程落地/v5_implementation_briefs/M4/M4C_Walk_Budget_And_Platform_Edge_Matrix.md

@@ -0,0 +1,167 @@
+# M4C Walk Budget And Platform Edge Matrix Implementation Brief
+
+状态: implementation brief 草案。
+阶段:M4C。
+
+## 目标
+
+把平台边能力与游走预算纳入 Excel 全平台矩阵:
+
+- 新增 `platform_edge_matrix`,治理每个平台每条边的四态。
+- 新增 `walk_policy_budget_matrix`,按 `platform + edge_id` 配预算。
+- 现有 `walk_policy.json` 数值先等价迁移为 `default`。
+- 快手 `author_to_works` 单独显式一行,但不调大预算。
+- 运营展示统一把预算超限叫 `budget_blocked`。
+
+M4C 不重写 walk engine,不改变现有预算数值,不改 DB。
+
+## 现有证据
+
+- `tech_documents/数据接口与来源/walk_policy.json:6-15`:当前 global/edge 预算为总闸 10、max_depth 5、author/tag 各 5、每作者作品 5。
+- `walk_policy.json:29-43`:v4_walk_gate 以 `allow_walk` 控制 `hashtag_to_query/author_to_works`。
+- `content_agent/integrations/walk_graph_json.py:94-95`:load_policy 生成 `budget_tiers` 与 `edge_budgets_by_id`。
+- `walk_graph_json.py:140-142`:当前校验 `edge_budgets` edge_id 是否存在。
+- `content_agent/business_modules/walk_engine.py:124-130/185-226`:walk engine 消费 global/edge budgets。
+- `walk_engine.py:298-308/478-486`:预算耗尽写 skipped action,`reason_code="budget_exhausted"`。
+- `content_agent/flow_ledger_service.py:79`:展示层已有 `budget_exhausted` 文案。
+- `flow_ledger_service.py:863-883`:展示预算只读 `walk_policy.json` 的 `edge_budgets`。
+- DB `walk_actions` 已有 `edge_id/budget_tier/reason_code`;线上 `budget_exhausted=250`,`budget_tier=blocked=4453`。
+
+## 修改范围
+
+- `tech_documents/游走策略/游走策略配置表.xlsx`
+  - 新增 `platform_edge_matrix`。
+  - 新增 `walk_policy_budget_matrix`。
+- `tech_documents/数据接口与来源/walk_policy.json`
+  - 保持运行态 JSON,由 Excel 生成。
+- `content_agent/integrations/walk_graph_json.py`
+  - 支持从新运行态结构加载等价 `global/edge_budgets_by_id`。
+- `content_agent/flow_ledger_service.py`
+  - 映射运营展示状态 `budget_blocked`。
+- validators/tests
+  - 校验四态、预算主键、default 等价迁移。
+
+## 不修改范围
+
+- 不调大预算。
+- 不把 `low_budget=halve_min_1` 解释为真实缩减预算,除非后续另拍。
+- 不改 walk action DB schema。
+- 不改 `walk_engine` 核心 frontier 逻辑,除非适配新 config shape 必需。
+- 不启用视频号作者边或公众号/小红书边。
+
+## 涉及文件 / 函数 / 类
+
+- `content_agent/integrations/walk_graph_json.py::WalkGraphStore.load_policy`
+- `content_agent/integrations/walk_graph_json.py::_validate_policy`
+- `content_agent/business_modules/walk_engine.py`
+- `content_agent/flow_ledger_service.py`
+- `scripts/validate_v4_config_contract.py::_check_v4_walk_gate_config`
+- `tests/test_walk_graph_config.py`
+- `tests/test_walk_engine_budget.py`
+- `tests/test_score_items_platform.py`
+
+## 数据合同
+
+### `platform_edge_matrix`
+
+字段:
+
+```text
+platform, edge_id, status, status_reason, runtime_enabled, approval_status,
+profile_path, endpoint_ref, can_walk, blocked_reason, evidence_ref,
+last_verified_at, 注释
+```
+
+`status` 取值:
+
+```text
+supported / blocked / no_interface / planned
+```
+
+派生规则:
+
+```text
+can_walk = (status == supported AND runtime_enabled == true AND approval_status == approved)
+```
+
+### `walk_policy_budget_matrix`
+
+字段:
+
+```text
+platform, edge_id, budget_tier, max_total_actions, max_per_content,
+max_works_per_author, max_pages, budget_exhausted_reason_code,
+operator_display_status, runtime_enabled, approval_status,
+last_reviewed_at, owner, 注释
+```
+
+初始 rows:
+
+| platform | edge_id | 口径 |
+|---|---|---|
+| `default` | `__global__` | 迁移现有 `global.max_total_actions_per_run/max_depth` |
+| `default` | `author_to_works` | 迁移现有 max_total_actions=5,max_works_per_author=5 |
+| `default` | `hashtag_to_query` | 迁移现有 max_total_actions=5 |
+| `kuaishou` | `author_to_works` | 显式平台行,数值等于 default,不调大 |
+
+超限展示:
+
+```text
+runtime reason_code = budget_exhausted
+runtime budget_tier = blocked
+operator_display_status = budget_blocked
+```
+
+## 实施步骤
+
+1. 新增 `platform_edge_matrix` sheet,按全平台/全 edge 补行,但 runtime 启用仍受双闸控制。
+2. 新增 `walk_policy_budget_matrix` sheet,把当前 `walk_policy.json` 原样迁成 default rows。
+3. 为快手 `author_to_works` 添加显式平台预算行,数值不高于 default。
+4. 扩 converter 生成现有 `walk_policy.json` 等价结构:
+   - 保持 `global`。
+   - 保持 `edge_budgets`。
+   - 保持 `edge_permissions` 与 `v4_walk_gate`。
+5. 扩 validator:
+   - `platform + edge_id + budget_tier` 不重复。
+   - edge_id 必须存在于 `walk_edge_catalog.json`。
+   - status 必须四态。
+   - 只有 approved+runtime_enabled rows 生效。
+6. 展示层把 `budget_exhausted / blocked` 映射为运营可读的 `budget_blocked`。
+7. 跑 walk budget 单测和 golden gate。
+
+## Unit Test
+
+- `tests/test_walk_graph_config.py::test_walk_policy_budget_matrix_generates_current_default_policy`
+  - 生成后的 global/edge budgets 与当前 `walk_policy.json` 等价。
+- `test_kuaishou_author_to_works_has_explicit_budget_row`
+  - 快手 author row 存在且数值不高于 default。
+- `test_platform_edge_matrix_accepts_four_statuses`
+  - 四态合法,未知状态 fail。
+- `test_platform_edge_matrix_can_walk_is_derived`
+  - 只有 supported+approved+runtime_enabled 才 can_walk。
+- `tests/test_walk_engine_budget.py::test_budget_exhausted_still_records_runtime_reason`
+  - 超限仍写 `budget_exhausted`。
+- `tests/test_score_items_platform.py::test_budget_exhausted_displays_as_budget_blocked`
+  - 运营展示状态为 `budget_blocked`。
+
+## Integrated Test
+
+- `uv run pytest tests/test_walk_graph_config.py tests/test_walk_engine_budget.py -q`
+- `uv run pytest tests/test_score_items_platform.py -q`
+- `uv run python scripts/validate_v4_config_contract.py`
+- `python3 scripts/run_config_gate.py`
+- `uv run pytest tests/test_v5_golden_corpus_baseline.py -q`
+
+## 失败归因
+
+- 预算数值变了:default 迁移没有做到与当前 `walk_policy.json` 等价。
+- 快手预算被放大:违反 M4 不调大预算口径。
+- 视频号作者边被打开:platform edge matrix 把 blocked 写成 supported。
+- 超限没留痕:walk_engine 预算消费适配破坏了 `budget_exhausted`。
+- 展示还是旧文案:flow ledger 没映射 `budget_blocked`。
+
+## sub-agent 交叉验证要点
+
+- Code subagent 确认 walk_engine 当前预算消费与 `budget_exhausted` 留痕。
+- JSON/config subagent 确认当前 `walk_policy.json` 数值。
+- DB subagent 确认 `walk_actions` 已有 edge_id/budget_tier/reason_code,不需要 schema change。

+ 182 - 0
tech_documents/工程落地/v5_implementation_briefs/M4/M4D_Fake_Feedback_Join_And_Learning_Guardrail.md

@@ -0,0 +1,182 @@
+# M4D Fake Feedback Join And Learning Guardrail Implementation Brief
+
+状态: implementation brief 草案。
+阶段:M4D。
+
+## 目标
+
+用 fake feedback 验证后验表现能回连 rule decision 与 discovered content,为未来学习算法铺入口:
+
+- fake feedback 写入现有 `content_agent_performance_feedback` 或本地 fake runtime。
+- join key 固定为 `run_id + policy_run_id + decision_target_id = platform_content_id`。
+- 样本从 M0 golden corpus 和当前 DB 各选。
+- `learning_review` 只能生成建议,不得自动改规则、门槛、权重、预算。
+
+M4D 不接真实回流源,不新增 DB 表,不做自动学习。
+
+## 现有证据
+
+- `sql/content_agent_schema.sql:496`:已有 `content_agent_performance_feedback` 表。
+- DB 只读结果:`content_agent_performance_feedback=0`。
+- DB join 结果:`rule_decisions -> discovered_content_items` 用 `run_id + policy_run_id + decision_target_id = platform_content_id` 命中 `2929/2929`。
+- DB 反例:`decision_target_id = content_discovery_id` 命中 `0/2929`。
+- `content_agent/integrations/database_runtime.py:301-307`:runtime 已有 `read_performance_feedback()`。
+- `content_agent/business_modules/learning_review.py:33`:读取 performance feedback。
+- `learning_review.py:641-656`:构建 performance feedback summary。
+- `learning_review.py:730-753`:有反馈时只生成 review 建议。
+- `learning_review.py:766-775`:recommendation 固定 `requires_human_approval=True`。
+- `tests/test_p8_performance_feedback.py:19-49`:现有测试证明 fake feedback 只让 summary available 并生成建议。
+- `tests/test_p8_no_auto_mutation.py:6-30`:防自动反写。
+
+## 修改范围
+
+- fake feedback fixture/helper
+  - 可放 tests helper 或脚本,只写 fake 数据/本地 runtime,不碰真实回流源。
+- `tests/test_p8_performance_feedback.py`
+  - 覆盖 M0 corpus 样本和 DB-like 样本 join。
+- `tests/test_p8_no_auto_mutation.py`
+  - 扩 guardrail,确保无 rule_pack/walk_strategy/budget patch 字段。
+- M4 验收 SQL/文档
+  - 固定 join key 与命中率报告。
+
+## 不修改范围
+
+- 不改 `sql/`。
+- 不新增 `content_agent_performance_feedback` 字段。
+- 不接真实平台表现回流。
+- 不自动写回 `douyin_rule_packs.v1.json`、`score_thresholds.json`、`walk_policy.json` 或 Excel。
+- 不把 fake feedback 作为训练/学习结果上线。
+
+## 涉及文件 / 函数 / 类
+
+- `content_agent/integrations/database_runtime.py::read_performance_feedback`
+- `content_agent/business_modules/learning_review.py::run`
+- `content_agent/business_modules/learning_review.py::_build_performance_feedback_summary`
+- `tests/test_p8_performance_feedback.py`
+- `tests/test_p8_no_auto_mutation.py`
+- `tests/test_p8_drift_guards.py`
+- `tests/test_v5_golden_corpus_baseline.py`(只作为样本来源/回归门)
+
+## 数据合同
+
+fake feedback 字段:
+
+```text
+feedback_id = fake_m4_{policy_run_id}_{platform_content_id}
+feedback_source = m4_fake_feedback
+feedback_status = available
+platform
+platform_content_id
+content_asset_id
+completion_rate
+share_rate
+average_watch_seconds
+play_count
+like_count
+comment_count
+share_count
+collect_count
+impression_count
+raw_payload
+```
+
+`raw_payload` 必放:
+
+```text
+run_id
+policy_run_id
+decision_id
+rule_pack_id
+rule_pack_version
+score
+scorecard_snapshot
+fake_case_version
+```
+
+fake 样本类型:
+
+| 类型 | 来源 |
+|---|---|
+| 高分入池 | M0 corpus + DB |
+| 人工复核 | M0 corpus + DB |
+| 低分拒绝 | M0 corpus + DB |
+| 技术失败 | M0 corpus + DB |
+| 快手 `author_to_works` 游走增强 | 当前 DB walk action |
+
+join key:
+
+```sql
+r.run_id = d.run_id
+AND r.policy_run_id = d.policy_run_id
+AND r.decision_target_id = d.platform_content_id
+```
+
+## 实施步骤
+
+1. 从 M0 corpus 选 4 类 decision 样本:
+   - `ADD_TO_CONTENT_POOL`
+   - `KEEP_CONTENT_FOR_REVIEW`
+   - `REJECT_CONTENT`
+   - `TECHNICAL_RETRY_REQUIRED`
+2. 从当前 DB 选同类真实样本,并额外选快手 `author_to_works` 游走增强样本。
+3. 写 fake feedback helper:
+   - 生成 deterministic `feedback_id`。
+   - 写入 runtime fake store 或测试 DB stub。
+   - raw_payload 带 scorecard snapshot。
+4. 写 join verifier:
+   - M0 corpus 用本地 reconstructed rows 验证。
+   - DB 样本用只读 SQL 验证。
+   - 命中率必须 100%。
+5. 调用 `learning_review.run()`:
+   - 断言 `performance_feedback_status=available`。
+   - 断言有人工复盘建议。
+   - 断言没有自动 patch 字段。
+6. 保持 `content_agent_performance_feedback` 真实表 schema 不变。
+
+## Unit Test
+
+- `tests/test_p8_performance_feedback.py::test_fake_feedback_join_matches_m0_golden_samples`
+  - 4 类 decision 样本 join 命中。
+- `test_fake_feedback_join_uses_platform_content_id_not_content_discovery_id`
+  - 用错误 key 应失败或命中 0。
+- `test_learning_review_reads_fake_feedback_but_only_recommends`
+  - feedback available,只生成建议。
+- `tests/test_p8_no_auto_mutation.py::test_learning_review_does_not_emit_rule_or_budget_patch`
+  - recommendations 不含 `auto_apply/rule_pack_patch/walk_strategy_patch/budget_patch`。
+- `test_fake_feedback_id_is_deterministic`
+  - 同样 policy/content 生成同一 fake id。
+
+## Integrated Test
+
+- `uv run pytest tests/test_p8_performance_feedback.py tests/test_p8_no_auto_mutation.py -q`
+- `uv run pytest tests/test_p8_drift_guards.py -q`
+- `uv run pytest tests/test_v5_golden_corpus_baseline.py -q`
+- `uv run --with pymysql python scripts/validate_content_agent_db.py --env-file .env --allow-missing-tables`
+
+DB 只读验收 SQL:
+
+```sql
+SELECT COUNT(*) total,
+       SUM(d.id IS NOT NULL) matched
+FROM content_agent_rule_decisions r
+LEFT JOIN content_agent_discovered_content_items d
+  ON d.run_id = r.run_id
+ AND d.policy_run_id = r.policy_run_id
+ AND d.platform_content_id = r.decision_target_id;
+```
+
+期望当前结果:`matched/total = 2929/2929`。
+
+## 失败归因
+
+- join 失败:检查是否误用 `content_discovery_id`。
+- fake feedback 没被 learning_review 读到:检查 runtime stub 或 `read_performance_feedback()`。
+- recommendations 出现 patch 字段:违反 M4 guardrail。
+- DB 样本不可复现:当前 DB 数据变化,需要重新贴只读 SQL 证据。
+- M0 corpus 样本不稳定:不能改 corpus,应修 helper。
+
+## sub-agent 交叉验证要点
+
+- DB subagent 确认 join key、2929/2929 命中、performance_feedback 0 行。
+- Code subagent 确认 learning_review 只读 feedback 并生成建议。
+- JSON/config subagent 确认 fake feedback 不进入规则包/门槛/预算配置。

+ 212 - 0
tech_documents/工程落地/v5_implementation_briefs/M4/M4E_Dead_Config_Archive_Acceptance_And_Subagent_Audit.md

@@ -0,0 +1,212 @@
+# M4E Dead Config Archive Acceptance And Subagent Audit Implementation Brief
+
+状态: implementation brief 草案。
+阶段:M4E。
+
+## 目标
+
+定义 M4 的最终归档、验收命令、测试矩阵、DB/Excel guardrail、失败归因与 sub-agent 审计口径。
+
+M4E 不新增业务能力,只确保 M4A-M4D 的实现被正确验证,并把死配置安全归档到 git。
+
+## 现有证据
+
+- `score_thresholds.json` 仍是运行门槛真源,不能归档误删。
+- `build_config_from_excel.py` 当前 overlay-only,新增结构必须先改 converter。
+- `run_config_gate.py` 当前 gate 通过,但不能证明新增 sheet 生效。
+- DB 无 archive/deprecated/legacy 配置表,死配置归档应走 repo 文件归档。
+- 现有脚本有归档先行 precedent:`scripts/archive_v2_db_rows.py` 与 `scripts/drop_content_audience_profile_column.py` 说明先导出/归档再删除。
+- M4 已拍板:归档目录进入 git,M4 不自动物理删除,后续人工决定是否删。
+
+## 修改范围
+
+- `archive/v5_m4_dead_config_archive/<timestamp>/...`
+  - 新增归档目录。
+  - 新增 manifest。
+  - 放入经证明不再被 runtime 读取的死配置。
+- `tech_documents/工程落地/v5_implementation_briefs/M4/*`
+  - 写明归档证明和验收结果。
+- tests/scripts
+  - 只增加归档 manifest 校验或 guardrail,不动 DB。
+
+## 不修改范围
+
+- 不自动删除归档后的源文件,除非后续人工另拍。
+- 不归档 `score_thresholds.json`。
+- 不归档 M0 corpus。
+- 不改 `sql/`。
+- 不把 DB 历史运行事实当死配置。
+- 不把 `archive/` 目录加入 gitignore。
+
+## 涉及文件 / 函数 / 类
+
+- `archive/v5_m4_dead_config_archive/<timestamp>/manifest.json`
+- `scripts/run_config_gate.py`
+- `scripts/validate_rule_pack_config.py`
+- `scripts/validate_v4_config_contract.py`
+- `scripts/validate_content_agent_db.py`
+- `tests/test_v5_golden_corpus_baseline.py`
+- `tests/test_config_tooling.py`
+
+## 数据合同
+
+manifest 字段:
+
+```text
+archive_id
+source_path
+archive_path
+archived_at
+archived_by
+proof_no_runtime_read
+restore_instruction
+stable_until
+delete_after
+approval_status
+注释
+```
+
+归档目录:
+
+```text
+archive/v5_m4_dead_config_archive/<timestamp>/...
+```
+
+删除策略:
+
+```text
+M4 只归档不自动删除;后续是否物理删除由人工手动决定。
+```
+
+## 测试矩阵
+
+| 层级 | 测试 | 验证点 |
+|---|---|---|
+| Unit | converter/gate | 新增行不静默跳过、审批双闸生效 |
+| Unit | weight/threshold | 通用 profile、门槛一行一 key、golden 行为零变化 |
+| Unit | walk budget | default 预算等价、快手 author 显式预算、budget_blocked 展示 |
+| Unit | fake feedback | join 命中、learning_review 不自动反写 |
+| Integrated | config gate | Excel→JSON、validators、V4 config contract |
+| Regression | M0 golden gate | 161 条历史 evaluator decision exact replay |
+| DB readonly | schema/data audit | 无 migration、fake join key 正确 |
+| Guardrail | git diff | `sql/` 为空;归档只进 `archive/v5_m4_dead_config_archive` |
+
+## 验证命令
+
+```bash
+# Converter / Excel
+uv run --with openpyxl python scripts/build_config_from_excel.py --check
+uv run --with openpyxl python scripts/validate_config_excel_sync.py
+
+# Config validators
+uv run python scripts/validate_rule_pack_config.py
+uv run python scripts/validate_v4_config_contract.py
+python3 scripts/run_config_gate.py
+
+# Unit/integrated tests
+uv run pytest tests/test_config_tooling.py -q
+uv run pytest tests/test_v4_rule_pack_contract.py tests/test_rule_judgment_scorecard.py -q
+uv run pytest tests/test_v4_thresholds_per_platform.py -q
+uv run pytest tests/test_walk_graph_config.py tests/test_walk_engine_budget.py -q
+uv run pytest tests/test_p8_performance_feedback.py tests/test_p8_no_auto_mutation.py -q
+uv run pytest tests/test_v5_golden_corpus_baseline.py -q
+
+# DB readonly
+uv run --with pymysql python scripts/validate_content_agent_db.py --env-file .env --allow-missing-tables
+
+# Guardrail
+git diff -- sql
+git diff --name-only -- archive/v5_m4_dead_config_archive
+```
+
+## DB / 只读验收
+
+DB 证据只进入文档和人工验收,不进入常规 pytest/CI。
+
+推荐 SQL 摘要:
+
+```sql
+SELECT COUNT(*) FROM content_agent_rule_decisions;
+SELECT COUNT(*) FROM content_agent_discovered_content_items;
+SELECT COUNT(*) FROM content_agent_walk_actions;
+SELECT COUNT(*) FROM content_agent_performance_feedback;
+```
+
+fake join:
+
+```sql
+SELECT COUNT(*) total,
+       SUM(d.id IS NOT NULL) matched
+FROM content_agent_rule_decisions r
+LEFT JOIN content_agent_discovered_content_items d
+  ON d.run_id = r.run_id
+ AND d.policy_run_id = r.policy_run_id
+ AND d.platform_content_id = r.decision_target_id;
+```
+
+当前只读结果:
+
+- `rule_decisions=2929`
+- `discovered_content_items=2929`
+- `walk_actions=7615`
+- `performance_feedback=0`
+- join `2929/2929`
+
+## Guardrail
+
+M4 允许改:
+
+- 两本 Excel workbook。
+- converter / config validators / config gate。
+- 规则包 JSON、运行态 score thresholds JSON、walk policy JSON。
+- tests 与 M4 docs。
+- `archive/v5_m4_dead_config_archive/<timestamp>/...`。
+
+M4 不允许改:
+
+- `sql/`
+- M0 corpus/fixture。
+- 平台 client。
+- `run_service.py` 平台工厂新增平台。
+- 真实回流源接入。
+- 自动学习/自动 patch 逻辑。
+
+## 死配置归档口径
+
+每个归档项必须先满足:
+
+1. `rg` 证明 runtime 不读。
+2. validator/golden gate 通过。
+3. manifest 有 restore instruction。
+4. 归档路径进 git。
+
+不能归档:
+
+- `score_thresholds.json`,除非 M4B 已完成运行态生成并证明 evaluator 仍读取新生成文件。
+- `walk_policy.json`,除非 M4C 已完成运行态生成并证明 walk engine 等价读取。
+- M0 golden corpus。
+- DB schema 或历史运行事实。
+
+## 失败归因
+
+- archive 后 config gate 失败:归档了仍被 converter/validator 读取的文件。
+- golden gate 失败:误删/误改权重或门槛真源。
+- fake join fail:归 M4D,查 join key。
+- `git diff -- sql` 非空:误进 DB 范围。
+- `archive/` 未出现在 git diff:归档目录可能被 ignore 或未创建。
+- manifest 缺 restore instruction:归档不可回滚,不得验收。
+
+## sub-agent 交叉验证要点
+
+- Code subagent 输出需要覆盖:
+  - evaluator/threshold/walk_policy 仍读取哪些真源。
+  - learning_review 不自动反写。
+  - budget_exhausted 留痕。
+- Excel/converter subagent 输出需要覆盖:
+  - 当前 sheet 与 converter spec。
+  - 新增行静默跳过风险。
+  - run_config_gate 当前形态。
+- DB/schema subagent 输出需要覆盖:
+  - 25 张表与关键 count。
+  - fake join key 与命中率。
+  - 无配置表/归档表。