diff --git a/research-wiki/reviews/2026-09-11-138-branch-review.md b/research-wiki/reviews/2026-09-11-138-branch-review.md new file mode 100644 index 0000000..9e452ca --- /dev/null +++ b/research-wiki/reviews/2026-09-11-138-branch-review.md @@ -0,0 +1,43 @@ +九个审查点已全部核对完毕。以下为完整审查报告。 + +--- + +# 1.3.8 流式中段沉默分支:整分支合并前审查 + +- **结论先行:可合并(OK with notes)**。无阻断项;2 条建议级(P2)发现,均不阻塞合并。 +- 范围:`main a28d944...HEAD ca8743b`(代码提交 901a193/53bcf43/e0b0be0/2ac05a2 + 文档提交)。审查方式:只读,未运行 git/pytest(审查纪律),一切裁决以当前工作区源码 + 测试 + 文档实读为证据;绿态证据引自 `findings/2026-09-11-streaming-stall-validation.md` 登记的命令输出。 + +## 逐点裁决表 + +| # | 审查点 | 裁决 | 关键证据 | +| --- | --- | --- | --- | +| 1 | 取消穿透 | **通过** | `retry.py:596-645` `_past_hedge_window` 两阶段均逐字套用"waiter.cancel() → await waiter → CancelledError 时 `cancelling()` 判外部取消穿透"既有范式;阶段 1(`:608-622`)、阶段 2 循环内(`:630-642`)、对冲在途(`_attempt_hedged` Phase 5 `:540-556` + `except BaseException` 收口 `:557-567` 逐字保留)三时点全部穿透。测试:`test_stall_hedge.py::test_external_cancel_during_stall_wait_penetrates`(阶段 2 内取消,无残留任务、permit 释放、`asyncio.all_tasks()` 前后相等)+ `test_hedge.py:501` 既有取消用例未动 | +| 2 | Event 并发纪律 | **通过** | 与计划 §3.6 伪代码逐点比对:①复检 `is_set()` 在 `primary.done()` 之后、触发之前(`retry.py:638`);②`is_set()`→`clear()` 紧邻无 await(`:638-639`);③对冲路恒 `content_event=None`(`:513` Phase 4);④每轮自建 waiter(`:630`);⑤等待者唯一(编排单点等待,waiter 进 finally 收口后才进下一轮)。唯一偏差:阶段 1 的 waiter 创建被提升到 `if` 分支外(`:608`),功能等价且更简洁,非问题 | +| 3 | 净口径换算唯一处 | **通过** | 全库 `int(.*stall` 仅 `retry.py:377-378` 两处相邻行(同一次换算);`net = max(0, wall_ms − (stall_ms or 0))` clamp 收敛在 `_record_winner_sample`(`:575-590`),单路(`_record_generation`)与对冲赢家(`_register_hedge`)共用。None 透传五路径逐一核实:非流式(`_complete_once` 永不置位、TransportResult 默认 None)、chunk<2(`_stall_from_chunk_times` 返回 `(None,None)`,`openai_compat.py:98-99`)、两败(`_register_hedge(winner_sink=None)` 不记 generation、stall 保持 None)、embedding/ocr(`record_generation` 不带 stall kw,embedding.py/ocr.py 零改动已 grep 证实) | +| 4 | 看门狗汇合 | **通过** | Phase 5"先了结的是失败则等另一路"(`retry.py:540-543`)逐字复用;看门狗掐死 → `StreamLivenessTimeout`→`TransientError`→`_attempt` 既有失败分支(`breaker.record_failure`/`attempt_fails`/遥测 error 行照常,`:415-437`)。测试 `test_stall_hedge.py::test_watchdog_kills_primary_hedge_leg_survives` 断言熔断失败样本 +1、主路 error 行携 TransientError 对象、副路正常行——N9 不对称(掐死喂熔断 vs 输家取消不喂)未改任一 | +| 5 | 配置守卫 | **通过** | `check_hedge_assembly` 返回 `tuple[float|None, float|None]`(`config.py:819/912`),两调用点(`config.py:391`、`client.py:255`)均解构;两阈值皆 None 才早退(`:855`,先于空源检查,B2 钉死落实);三守卫齐备:有效最小值 ValueError(`:885-894`,`inter_token_timeout_s or timeout_s` 回落与 `openai_compat.py` 看门狗装配同款)、deadline 冲突 ValueError(`:895-899`)、单源 warning(`:900-906`);B4 落 `_warn_hedge_keys_on_non_stream_scope` + Embedding/Ocr 两 `__post_init__`(`:950/:996`,含 1.3.7 AFTER_S 同病补齐);`STREAM` 保留段(`:57`);三层分发正确:`from_settings` 恒分发(`client.py:543`)、直构造 `None` 不 stomp、非 None 经 `dataclasses.replace` 触发 `__post_init__` 重跑校验(`client.py:267-270`)。测试 `test_config.py:1304-1436` 全覆盖 | +| 6 | 遥测链路 | **通过** | 协议 +2 参无默认值(`ports.py:343-344`,共 38 参,`test_ports.py:324` 钉住);`_AttemptUsage.of()` 不从 response 取(B2 裁决 b,`telemetry.py:219-223`);`emit_attempt` 显式转发、`emit_cache_hit`/`emit_terminal_failure` 显式 None(`:465-466/:515-516/:570-571`);`_record` +2 参透传(`:616-617/:686-687`);schema 五编辑点齐备且保序对齐(`schema.py:66-67/110-111/146-147/175-176/227-228`)物理列 39;DDL 幂等有测试(`test_telemetry.py:460-480` 二次初始化零 ALTER、`:485-503` manual 档点名两条新 ALTER);`test_hedge.py:145-148` RecordingEmitter 显式全签名 +2 kw(B1)已同步。附带:`sqlite.py:146` 计数已同步 38;`postgres.py:411` docstring 本就无字段计数文本,计划"计数同步"对它无对象,无残留"36"字样(全 src grep 证实) | +| 7 | 默认关闭回归 | **通过** | 入口门第一分支(两阈值皆 None)逐字走 1.3.7 单路且 `first_token_event=None, content_event=None`(`retry.py:285-295`);第二分支(仅 stall + 非流式)同形(`:297-307`);T1 阶段单路调用点恒传 None 已并入最终形态;`SourceConfig.stall_threshold_ms` 默认 1000 只影响计量(这是已批准的 P1 行为变更本体,CHANGELOG 行为变更段已披露,非回归);findings 登记批次 E 门:`1688 passed, 17 skipped` 且既有语义断言一行未改 | +| 8 | 反 gold-plating | **通过** | 全 src grep `1.3.8` 标记仅落在计划 §2.1 清单内 8 个文件(ports/types/config/client/retry/telemetry/schema/openai_compat);`errors.py`/`deadline.py`/`streaming.py`/`middleware/{ratelimit,breaker,structured,cache,admission}.py`/`embedding.py`/`ocr.py`/`tests/contracts/**` 零新增引用(仅既存 issue #8 stall-window 语境);无 `except Exception: pass`、无 `print()`、日志全 loguru、公共函数类型注解齐全;缓存指纹 `build_model_fingerprint` 只取 model/extra_body/thinking 三件套(`client.py:125-164`),`stall_threshold_ms` 不进指纹——改阈值不触发缓存冷启动,无意外副作用 | +| 9 | 测试质量 | **通过(带 1 条 P2)** | 批次 D 复检用例确实走到"waiter 正常完成 → is_set→clear→continue"路径(不是平静场景:chunk 压在 0.049s、阈值 0.05s,复检-继续被真实执行);对冲赢家净口径、看门狗记账、两败一次预算、取消穿透、对冲路恒 None 的断言强度与 `test_hedge.py` 同档(同一设施范式 + 逐次身份记录 + 任务泄漏断言)。`test_stall_hedge.py` 还主动补了计划外的 `test_single_leg_attempt_row_carries_stall`(T3 缺口钉:chat 成功行经 `_emit` 端到端落 stall 两值),属范围纪律内的测试加固,非 gold-plating | + +## 发现列表 + +**阻断(B):无。** + +**建议(N/P2):** + +- **N1(P2)`retry.py:369-371` 注释阶段性残留**:`_attempt` 内 `content_event=content_event` 处注释仍写"本阶段(T1)所有调用点恒传 None = 不观测;T4 才在启用 stall 对冲的流式原路上接真事件"。T4 已落地,该缝现在转发的是调用方传入的真事件,"本阶段(T1)"措辞会让后来者误以为代码停在 T1 中间态。最小修:把"本阶段(T1)…T4 才…"改为终态表述("未启用 stall 对冲时调用方传 None;启用时流式原路携真事件,对冲路恒 None")。不影响行为,可合并后随手改。 +- **N2(P2)复检用例时序余量仅 1ms**:`test_stall_hedge.py::test_stall_recheck_prevents_false_trigger` 用 `chunk_after=0.049s` 对阈值 `0.05s`,与本文件自定"容差 4–10×"纪律相逆(此处是反向边界,宽余量反而测不到复检路径,故取值本身可辩护);且真实 loop 钟下"chunk 与超时同一 pass 到达"不可确定性复现,该用例实际测到的是"超时前一刻到达 → 复检-继续",严格同 pass 竞态仍只剩代码走查兜底(已走查,正确)。风险是 CI 抖动下 sleep 晚醒 >1ms 会假触发使 `transport.calls` 多出 s2 而红。计划 §7 已预留缓解(调宽至 0.45× 阈值并记 findings),合并不阻塞,建议观察首轮 CI 表现。 + +## 残余风险与说明 + +- 本审查为只读静态审查 + 已登记绿态证据复核,未重跑测试;合并前按 CLAUDE.md §4.4.1 第 4 步仍需跑 slow 交集子集(Redis 时间语义变体 + `test_smoke_gateway.py`),`test_thinking_live.py` 矩阵按 2026-09-10 决议不跑(复用 1.3.7 证据)。 +- `_past_hedge_window` 复检窗口只能收窄不能归零(计划 §8 已诚实标注),属已批准残留。 +- 对冲取消与外部取消同时到达时可能误贴 `hedge_cancelled` 标签,`retry.py:418-423` 注释已声明记账方向一致、属已批准可接受残留。 + +**Merge verdict: OK with notes**(N1/N2 不阻塞;建议合并后小修 N1 注释)。 + +--- + +**结论:可合并(OK with notes)。发现计数:阻断 0 / 建议 2(N1 `retry.py:369-371` 注释阶段性残留;N2 `test_stall_hedge.py` 复检用例 1ms 时序余量偏紧)。** \ No newline at end of file diff --git a/src/polygateway/middleware/retry.py b/src/polygateway/middleware/retry.py index 8fbbbaa..b048918 100644 --- a/src/polygateway/middleware/retry.py +++ b/src/polygateway/middleware/retry.py @@ -367,8 +367,8 @@ class RetryMW: # 对冲编排(计划 §3.4): 原路携带首 token 事件,对冲路恒 None(v1 单路, # 不再梯次);未启用对冲时 __call__ 传 None = 调用方不观测首 token first_token_event=first_token_event, - # 中段沉默处置位(1.3.8): 本阶段(T1)所有调用点恒传 None = 不观测; - # T4 才在启用 stall 对冲的流式原路上接真事件(设计 §5.2 N3③) + # 中段沉默处置位(1.3.8): 未启用 stall 对冲时调用方传 None = 不观测; + # 启用时流式原路携真事件,对冲路恒 None(设计 §5.2 N3③) content_event=content_event, ) # 取整全库唯一处(设计 §4.2 N8): transport 的 float 沉默值在此