Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 126 additions & 0 deletions docs/sessions/260823-title-finalize/reviews/r1-verdict.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
# r1-verdict:title-finalize 写边界元数据定稿

- **审查范围**:`5ce323aad3fbaa3525df959f74a2d873d84d85b5..f1ee994b8db722bc5862e775b7a0ac9fc8ca80ce`(PR #62)
- **审查者**:Cursor 执行器(独立 review 卡 r1)
- **日期**:2026-08-23
- **risk-tier**:personal(infra/状态机例外,降层三问必答)

## Verdict

**pass** — 无未接受的 P1。不变式 I1–I5 在审查范围内成立;`save_cache` 写边界前的定稿逻辑与测试一致。

## 本轮新证据

(以下均非复读 diff 文本,为审查过程中独立拉取的调用链与命令输出。)

1. **save_cache 站点全覆盖**:在 head `f1ee994` 上对 `transcription.py` 执行 `grep save_cache|_finalize_presentation`,6 处 `save_cache` 前均有 `_finalize_presentation_fields()`;缓存未命中主路径无遗漏写边界。
2. **LLM 队列消费字段**:阅读 `llm_ops.py` 中 `process_llm_task` — `video_title` 取自 `llm_task["video_title"]` 用于通知与 `_generate_title_if_needed`;对照 head 上缓存部分命中分支,发现 `handoff_payload` 在 `finalize_presentation_metadata` 之前组装,与 `calibrating_status_kwargs` 可能不一致(见 F2)。
3. **补拉 downloader 选择**:阅读 `_finalize_presentation_fields` 中 `retry_downloader = download_downloader or metadata_downloader`;当 `has_separate_download_url` 时 `download_downloader` 恒为 `GenericDownloader()`,会优先于 `metadata_downloader`(平台 downloader)用于 `get_metadata` 补拉(见 F3)。
4. **GenericDownloader 补拉能力**:阅读 `generic.py` `_fetch_metadata` — 依赖 `get_video_info(url)` 解析页面,对小宇宙 episode URL 无专用逻辑,补拉大概率仍得空标题后落 basename 占位。
5. **红验锚点(卡面预取,本轮用于锁死测试约束力)**:base `5ce323aa` 上拷入 `test_flow_metadata_retry_before_save_cache` 失败,断言标题为 `6a89b9b7008ed7314d3acdbe` 而非 `Real Episode Title`;helper 单测在 base 上 ImportError — 测试确实绑定本次行为而非恒真。

## 降层三问(infra 例外)

### ① 终态写入成功之前已发生哪些不可逆动作?

| 阶段 | 不可逆动作 | 是否受 finalize 保护 |
|------|-----------|-------------------|
| 第一次 `get_metadata` 失败 | 仅日志;title/author 保持空(新代码) | 是(I1) |
| 下载 / 转录 | 音频落盘、转录文本生成 | 否(设计外) |
| 中途 `notify_task_status` | 通知可能带 interim 标题 | 否(行为层) |
| `save_cache` | video_cache 行 title/author 持久化 | **是** — `_finalize_presentation_fields()` 紧邻其前 |
| 缓存部分命中 → LLM handoff | `CALIBRATING` 状态写入 | `calibrating_status_kwargs` 用 finalize 后变量;`llm_payload` 可能仍用旧值(F2) |

核心不变式针对的是 **video_cache 持久化标题**;下载/转录不可逆但不在本 PR 范围。

### ② 守卫用的值在实际部署形态下自身唯一吗?

- **写边界守卫**:`finalize_presentation_metadata` 产出的 `(title, author)` _tuple,由当前 downloader 实例内存缓存(`BaseDownloader._metadata_cache` 按 `video_id`)+ 同进程第二次 `get_metadata` 复用。
- **部署形态**:personal 单实例(n305);同 URL 同任务内单 downloader 实例 — 补拉与下载阶段 warming 共享缓存,唯一性成立。
- **media_id / platform**:缓存键与任务状态键,单实例下无副本竞争问题。

### ③ 保护覆盖的是「写入」还是「行为」?

- **写入**:`save_cache(..., title=..., author=...)` 前定稿 — **覆盖完整**(6 站点)。
- **行为**:中途通知、`handoff_payload["video_title"]`(部分缓存命中)、成功响应 `data.video_title`(缓存全命中路径未调用 finalize)— **未全覆盖**;属展示/通知层,不导致本次设计的「错误标题永久写入 video_cache」静默错结果(F2/F4 为 P2)。

## 不变式核对

| ID | 判定 | 证据 |
|----|------|------|
| I1 | ✅ | 失败分支改为 `""` 而非 basename/Unknown(diff `1628–1636` 段);`test_flow_metadata_retry_before_save_cache` |
| I2 | ✅ | `_finalize_presentation_fields` + `finalize_presentation_metadata` 在全部 `save_cache` 前;单测 `test_finalize_retries_get_metadata_for_blank_title_and_author` |
| I3 | ✅ | override 优先且不触发 downloader;已有非空 title 不被 retry 覆盖 — 单测 `test_finalize_applies_metadata_override_over_existing`、`test_finalize_does_not_overwrite_existing_non_empty_title` |
| I4 | ✅ | `except Exception` 吞掉后仍落 fallback,任务继续 — 单测 `test_finalize_swallows_get_metadata_exception`、流程测试 success |
| I5 | ✅ | 无新 DownloadInfo 字段/质量枚举;`finalize_presentation_metadata` 为同文件函数,非独立模块 |

## Findings

### F1 — finalize 内宽 `except Exception`(OCR medium)

| 维度 | 内容 |
|------|------|
| 工具标注 | OCR minimax `partial`,medium — 宽 except 吞掉所有异常 |
| 本仓判定 | **接受(设计内)** — 对应 I4;infra 卡锁定「补拉异常吞掉不得让任务失败」 |
| P1 两问 | ① 会触发:补拉失败时;② 后果:落 basename/Unknown 占位,任务仍 success — **可接受**(显式 fallback,非静默无结果) |
| 溯源 | I4 |
| 处置 | 不修(本 PR) |

### F2 — 缓存部分命中:handoff_payload 在 finalize 之前组装(本仓新发现)

| 维度 | 内容 |
|------|------|
| 工具标注 | — |
| 本仓判定 | **P2** |
| P1 两问 | ① 仅当 finalize 会改变 title(如 `metadata_override` 覆盖缓存标题、或缓存 title 为空走 fallback)且走部分命中 LLM 队列时触发;② LLM 通知/协调器读 `llm_task["video_title"]` 可能短时不一致,但 `video_cache` 已存正确标题 — **后果可接受,非静默持久化错误** |
| 溯源 | I2 行为层缺口 |
| 处置 | backlog:将 finalize 提前到 `handoff_payload` 组装前,或 finalize 后更新 `handoff_payload["video_title"]` |

### F3 — separate `download_url` 时 GenericDownloader 优先补拉(卡面降层观察 + 本仓核实)

| 维度 | 内容 |
|------|------|
| 工具标注 | — |
| 本仓判定 | **P2** |
| P1 两问 | ① `download_url` 与平台 URL 分离且首次 `get_metadata` 失败时触发;② GenericDownloader 对小宇宙 URL 难拿真标题,可能仍 basename — **与旧代码「立即冻 basename」同级或略好**(旧路径无任何补拉) |
| 溯源 | I2 补拉 downloader 选择 |
| 处置 | backlog:retry 优先 `metadata_downloader or download_downloader` |

### F4 — 缓存全命中路径不调用 finalize(本仓新发现)

| 维度 | 内容 |
|------|------|
| 工具标注 | — |
| 本仓判定 | **P2(接受不修)** |
| P1 两问 | ① 仅历史脏缓存(旧代码写入 basename)全命中时;② 展示仍错但 PR 范围是「防新发」— PR 描述已声明 |
| 溯源 | 非 I2 范围(无新 write) |
| 处置 | 接受;生产脏数据已单独回填 |

### F5 — 成功路径 author 为空时 finalize 仍会 `get_metadata`(OCR medium)

| 维度 | 内容 |
|------|------|
| 工具标注 | OCR medium — 成功路径可能再调 get_metadata |
| 本仓判定 | **P3** |
| P1 两问 | ① 仅 title 已填、author 仍空时;② 多一次网络调用,结果正确 — 可接受 |
| 溯源 | I2(填空白字段) |
| 处置 | backlog 可选优化:分字段判断 retry |

### F6 — f-string 日志与 PEP8 空行(OCR low)

| 维度 | 内容 |
|------|------|
| 工具标注 | OCR low |
| 本仓判定 | **P3** |
| P1 两问 | 不适用 |
| 处置 | 不修 |

## 测试与红验

- 新增单测 `tests/unit/test_finalize_presentation_metadata.py`(5 条)覆盖 override、retry、不覆盖、fallback、吞异常。
- 流程回归 `test_flow_metadata_retry_before_save_cache` 覆盖「首次 metadata 超时 → 下载 warming → save_cache 真标题」。
- 红验:base 上流程测试失败、helper ImportError — 测试非恒真。

## 结论摘要

本次 diff 守住「展示元数据在写边界定稿」核心不变式:失败路径不再早填占位符,全部 `save_cache` 前统一 `finalize_presentation_metadata`。未发现会导致 **video_cache 错误标题静默持久化** 的 P1。遗留为通知/handoff 行为层与 separate `download_url` downloader 选择,记 P2 backlog,不阻塞合并。
59 changes: 59 additions & 0 deletions docs/sessions/260823-title-finalize/reviews/r2-verdict.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# r2-verdict:title-finalize 反向审查(persist 路径穷举)

- **审查范围**:`5ce323aad3fbaa3525df959f74a2d873d84d85b5..f1ee994b8db722bc5862e775b7a0ac9fc8ca80ce`(PR #62,冻结范围,不含此后 verdict 提交)
- **审查者**:Kimi 执行器(独立 review 卡 r2,反向视角)
- **日期**:2026-08-23
- **risk-tier**:personal;P1 仅当「真实使用方式下会把错误标题持久化进 video_cache 且不报错」
- **本轮唯一问题**:还有没有路径能把 URL basename / `Unknown` **静默写进 video_cache**?

## Verdict

**pass** — 反向穷举未发现本 diff 引入或遗留未守的 video_cache 静默写占位符路径。唯一剩余路径(F1,merge_metadata 默认值预填使补拉失效)为存量行为、base/head 完全一致,记 P2 backlog。

## 本轮新证据(与 r1 不同的检查)

r1 正向确认「6 处 save_cache 前有 finalize」;本轮反向穷举「谁能写 video_cache」:

1. **video_cache 写入点全仓枚举(head f1ee994)**:`git grep INSERT/REPLACE INTO video_cache` 全仓仅 `cache/cache_manager.py:735`(`save_cache` 内)一处 SQL 写入;`save_cache` 全仓调用点仅 `transcription.py` 6 处。`llm/core/cache_manager.py` 的 `video_cache` 只是产物目录名,不写表。
2. **save_cache 有/无 finalize 全量表**(head 行号,逐一核对上下文):

| # | save_cache 行 | 场景 | finalize 行 | 紧邻 |
|---|---|---|---|---|
| 1 | 1838 | YouTube API 快路径·平台字幕直用 | 1836 | ✅ |
| 2 | 1933 | YouTube API 快路径·FunASR | 1932 | ✅ |
| 3 | 1966 | YouTube API 快路径·CapsWriter | 1965 | ✅ |
| 4 | 2141 | 平台字幕路径(get_subtitle_result) | 2139 | ✅ |
| 5 | 2321 | 常规下载·FunASR | 2319 | ✅ |
| 6 | 2369 | 常规下载·CapsWriter | 2368 | ✅ |

YouTube API 快路径(卡面点名方向)3 处 save_cache **不漏点**;独立 `download_url` 路径汇入常规下载流(站点 5/6),同样过 finalize,且 finalize 的 `url=parse_url`(平台页 URL,非 CDN 下载直链),basename 兜底取值正确。
3. **`_fail_task_and_notify` 是否写 cache**(head 行 857–944):只调 `cache_manager.update_task_status(task_id, FAILED, download_url=..., error_message=...)`,**不传 title/author**;且 `update_task_status` 写的是 `task_status` 表(cache_manager.py:2431 `UPDATE task_status`),不是 video_cache。失败收口无 video_cache 写入。✅
4. **`merge_metadata` 反向核实(新发现 F1 的证据)**:head 行 303–306,该函数(本 diff 未改)在 `parsed_metadata` 为真但 title/author 为空时,就地填入 `extract_filename_from_url(url) or "Untitled"` 与 `"Unknown"`。再核对 downloader 实现:`xiaoyuzhou.py:224`、`generic.py:767` 均为 `title=info.get("video_title", "")`——**get_metadata 成功但返回空标题是现实可达的**(r1 F3 亦承认 GenericDownloader 对小宇宙 URL「大概率仍得空标题」)。
5. **task_status 表的 title 写入**(旁证,非 video_cache):`llm_ops.py:468/818`、`transcription.py:1371` 等 `update_task_status(..., title=...)` 写 task_status 表,且 `cache_manager.py:2369` `if title:` 空串跳过;不在本卡 P1 定义(video_cache)范围内。

## 降层三问(短答)

1. **终态写入前有哪些不可逆动作?** 音频落盘、转录文本生成、中途通知;video_cache 的唯一写边界是 `save_cache`(唯一 SQL 入口),6/6 站点 finalize 紧邻其前。
2. **守卫用的值在部署形态下唯一吗?** finalize 产出 `(title, author)` 依赖同进程 downloader 实例内存缓存;personal 单实例部署,无副本竞争,成立。
3. **保护覆盖写入还是行为?** 写入侧全覆盖(唯一 SQL 入口 + 6/6 调用点);行为层(中途通知、handoff 字段顺序)r1 已记 P2 并接受,本轮不重开。

## Findings

### F1 — `merge_metadata` 默认值预填使 finalize 补拉失效(本轮新发现)

| 维度 | 内容 |
|------|------|
| 工具标注 | —(本仓反向穷举发现,非外部工具) |
| 本仓判定 | **P2(backlog)** |
| 触发路径 | `get_metadata` **成功但返回空 title/author**(如 generic 页无 og:title、xiaoyuzhou 解析出空串不抛异常)→ `merge_metadata`(head:303–306,存量未改)就地填入 basename/`"Unknown"` → `finalize_presentation_metadata` 的 `needs_retry` 为 False(title/author 均非空)→ 补拉不发生 → basename/`Unknown` 静默写进 video_cache |
| P1 两问 | ① 真实使用下会触发吗?**可达**(上述 downloader 实现允许成功返空标题);② 后果能否接受?错误标题静默持久化——孤立看命中卡面 P1 定义,**但**该路径 base 与 head 行为逐字节一致,非本 diff 引入或加剧;按 review-discipline「只审本次 diff;存量代码问题直接记 backlog」降为 P2 |
| 溯源 | I1 的覆盖边界:I1 只管「get_metadata 异常」分支,不管「成功返空」分支 |
| 处置 | backlog:后续把 `merge_metadata` 步骤 2 的默认值填充删除或推迟到写边界 finalize(届时 finalize 的 needs_retry 才能对成功返空路径生效)。本 PR 不阻塞 |

### r1 已登记项(不重开)

r1 的 F1(宽 except)、F2(handoff_payload 顺序)、F3(GenericDownloader 优先补拉)、F4(全命中不 finalize)均已判 P2/P3 接受不修,本轮核实结论不变,不换措辞重提。

## 结论摘要

反向问题的答案:**除存量 F1 外,没有路径能把 URL basename / `Unknown` 静默写进 video_cache**。video_cache 有唯一 SQL 写入口,6 个调用点全部 finalize 紧邻;`_fail_task_and_notify`、YouTube API 快路径、平台字幕路径、独立 download_url 路径均无漏点。verdict:**pass**。
Loading
Loading