优化: CR-53/54审查回填PASS+CR-55/56/57登记+CR-53-1注释修正(并行化非移锁外)

This commit is contained in:
2026-06-17 02:29:12 +08:00
parent 80c0955a1a
commit 2c8004df65
2 changed files with 58 additions and 18 deletions

View File

@@ -377,23 +377,62 @@
- ⑦**FR-S1**:run_workflow 无 api_key,tool_result 仅 execution_id+task_id+target_status。
- **待修项回流 todo**: **无**(a2 实施正确,grep+test+构建全 PASS;no_retry 不增为设计取舍,去重缓存等效)。
### CR-260616-53 波11 CR-51 yellow修(audit trust-hit持锁重构) + UX-01工具失败视觉 + UX-03自动收起增强(332a6bb+31ea151) — 🟡 待审
### CR-260616-53 波11 CR-51 yellow修(audit trust-hit持锁重构) + UX-01工具失败视觉 + UX-03自动收起增强(332a6bb+31ea151) — ✅ 已审(PASS·⚪2)
- **范围**: 2 agent workflow w79v52ofy,文件域零重叠。①**audit.rs**(332a6bb):CR-51 yellow 修复——trust-hit 分支原 inline `.await tools_arc.execute``&mut session`(注释 L593 声称锁外但代码持锁,run_command 慢命令阻塞同会话所有触 state.ai_session 的 IPC)。重构:trust_hits Vec 收集(L558,元组携带 risk_level 供回填审计)→ 命中分支缩减为 toast emit+push trust_hits+continue(L588-601,删 inline execute)→ join_all 闭包锁外 execute+emit AiToolCallCompleted+emit_data_changed(L657-695)→ 串行回填持锁 push tool_result+audit_tool_call Some("auto_trust")(L696-705)。对齐 Low risk L708+ spawn+clone 模式,join_all 保序。②**UX-01 ToolCard**(31ea151):isToolFailure(tc)(run_command 看 exit_code≠0 结构化 / 通用工具 parseResult=null+文本匹配 /执行失败|failed|error/i 对齐 AR-6 err_msg)+ --failed 红框/banner/红底结果框 + aiStreamRetry 文案改"AI 响应失败,正在重试"(明确 LLM/网络重试非工具失败)+ transition border/shadow/opacity。③**UX-03**(31ea151):AiChat wasNearBottom 边沿检测(远离→近底转换瞬间收起一次)+ streaming watch s=true 分支(generatingConvId===activeConversationId 守卫防后台/恢复误触)+ ToolCardList userExpandedCards 记忆态(collapseInactive 不清,isCardExpanded 优先读)。
- **维度**: ①CR-51 yellow execute 真移锁外(非表面重构——核验 spawn 闭包确不持 sessionArc.lock,join_all 在 process_tool_calls 锁释放后跑)+ 语义不变(trust 命中仍真执行+emit AiToolCallCompleted+emit_data_changed+audit auto_trust 留痕 9 条逐条核验) ②UX-01 失败检测正确性(run_command exit_code 判定可靠 / 通用工具文本匹配启发式是否会误判成功结果如 "no errors found")+ Fatal canRetry 隐藏(auth/provider_config)与流式重试 retryable 区分清晰 ③UX-03 收起触发不误伤(streaming watch 守卫防后台生成误触 / wasNearBottom 边沿防重复触发 / userExpandedCards 记忆态防"刚在看被收起")+ transition 不影响流式渲染性能(body 高度不动,仅 card 层 3 属性)。
- **commit**: 332a6bb(audit yellow)+ 31ea151(前端 UX)
- **主代核查(2026-06-17)**: cargo check --workspace EXIT 0(7 warning 全 pre-existing dead_code)/ vue-tsc --noEmit EXIT 0 / cargo test -p devflow --lib **88 passed**(无回归)/ grep 核验形态(audit.rs trust_hits Vec L558 + join_all 闭包 L657 execute 锁外 + 串行回填 L696 持锁 / ToolCard isToolFailure + --failed CSS / ToolCardList userExpandedCards / AiChat streaming watch + wasNearBottom)。
- **审查 agent 待复审重点**: ①CR-51 yellow——核验 join_all 闭包确在 session 锁外执行(grep sessionArc.lock 作用域,确认 execute 不在锁内)+ 串行回填持锁(push tool_result/audit)+ join_all 保序(tool_result 按 tool_call_id 索引,LLM 按 id 匹配不依赖到达顺序) ②语义不变——trust 命中 9 条逐条核验(真执行/emit toast/emit completed/emit_data_changed/audit auto_trust/仅 execute 移锁外/Med-High 非 trust 路径零改动/Low risk 零改动/pending_count 不变/错误信息格式不变) ③UX-01 isToolFailure——run_command exit_code 路径(parseResult 命中合法 JSON 看 exit_code)+ 通用工具文本匹配(正则是否误判含 failed/error 字样的成功结果) ④UX-03 守卫——streaming watch generatingConvId===activeConversationId 防后台生成/恢复会话误触收起 + wasNearBottom 边沿触发防每 tick 收起 + userExpandedCards 记忆态不被 collapseInactive 清除
- **待修项回流 todo**: **无**(主代核查全过,待审查 agent 独立复审)
- **复审结论(2026-06-17·审查 agent 亲跑 cargo+vue-tsc+test+独立 grep/read 核验)**: ✅ **PASS** — 🔴0 🟡0 ⚪2
- **验证**: `cargo check --workspace` EXIT 0(7 warning 全 pre-existing dead_code)/ `npx vue-tsc --noEmit` EXIT 0/ `cargo test -p devflow --lib` **88 passed**(无回归)。
- **①CR-51 yellow 并行化(非"移锁外") PASS**:修复前后 session 锁均由调用方(agentic.rs:839 `session_arc.lock().await`)持有至 process_tool_calls 返回,锁持有时长不变。**真正改进是并行化**:修复前 trust-hit 分支内 inline `.await execute` 串行执行每个工具(阻塞期间锁被持有,run_command 慢命令阻塞同会话所有触 state.ai_session 的 IPC);修复后收集 trust_hits Vec→join_all 并行执行(锁仍被持有,但多工具并行减少总阻塞时间)。audit.rs:664-713 实现(trust_hits 收集 L568+join_all 闭包 L671-701+串行回填 L704-712)正确,注释"锁外"应理解为"闭包内不访问 session"而非"锁已释放"
- **②语义不变 9 条全 PASS**:真执行(audit.rs:676 tools.execute)+emit toast(L599 AiToolAutoApproved)+emit completed(L680)+emit_data_changed(L687)+audit auto_trust(L711 decided_by=Some("auto_trust"))+Med-High 非 trust 路径零改动(L630-660 pending 分支)+Low risk 零改动(L718-764 独立并行分支)+pending_count 不变(L630 仅 trust-hit 跳过,pending 仍累加)+错误信息格式不变(L680/690 AiToolCallCompleted 结构一致)。
- **③UX-01 isToolFailure 判定可靠 PASS**:run_command 路径(ToolCard.vue:213-217 parseResult 命中合法 JSON 看 exit_code≠0,可靠)+通用工具路径(ToolCard.vue:220-224 parseResult 返回 null 后正则 `/执行失败|failed|error[:\s]/i` 启发式匹配,对齐后端 AR-6 err_msg 格式 `format!("工具 {} 执行失败: {}", draft.name, e)` audit.rs:744)。正则可能误判含 "error"/"failed" 字样的成功结果(如 "no errors found"、"error handling passed"),但这是启发式检测已知取舍,非阻断(误报概率低,最坏多一次视觉提示)
- **④UX-01 视觉完整 PASS**:--failed 红框(ToolCard.vue:727-730 border-color+box-shadow)+banner(L119)+红底结果框(L745-751 background+border)+状态点(L20 failed 分类)+副标题(L17 failed 标签)。CSS 三类(.ai-tool-card--failed/.ai-tool-status-dot--failed/.ai-tool-result--failed)完整
- **⑤UX-03 streaming watch 守卫 PASS**:AiChat.vue:1807 `generatingConvId===activeConversationId` 防后台生成误触+防恢复会话 onMounted 时 streaming 初值 true 的误触(仅当前视图正在生成时触发收起)。守卫正确。
- **⑥UX-03 wasNearBottom 边沿检测 PASS**:AiChat.vue:1749 初值 true+1760 `if (near && !wasNearBottom)` 转换瞬间触发一次+1763 `wasNearBottom = near` 更新。边沿检测防每 scroll tick 收起,正确。
- **⑦UX-03 userExpandedCards 记忆态 PASS**:ToolCardList.vue:71 定义+197 展开时 add+193 收起时 delete+180 isCardExpanded 优先读+221 collapseInactive 不清除。记忆态防"刚在看被收起",正确。
- **⑧join_all 保序 PASS**:trust_hits 收集按 tc_list 原始 index 顺序(audit.rs:533 sort_unstable_by_key+540 retain 后顺序不变)→join_all 保持输入顺序→串行回填按结果顺序。LLM 按 tool_call_id 匹配 tool_result,不依赖到达顺序,保序非必需但一致性更优。
- **⚪ CR-53-1(low,注释"锁外"有误导性)**:audit.rs:664 注释"execute + 即时 emit 在闭包内(不持 session 锁)"易误解为"锁已释放",实则是"闭包内不访问 session"。session 锁在整个 process_tool_calls 期间由调用方持有(agentic.rs:839 `session_arc.lock().await` 至 L840 `process_tool_calls(...).await` 返回),修复前后锁持有时长不变。改进是**并行化**非"移锁外"。
- **⚪ CR-53-2(low,UX-01 通用工具文本匹配启发式误报风险)**:ToolCard.vue:223 正则 `/执行失败|failed|error[:\s]/i` 可能误判含这些字样的成功结果(如 "error handling passed"、"no errors found")。但这是启发式检测已知取舍,后端 err_msg 格式固定(AR-6 `format!("工具 {} 执行失败: {}", draft.name, e)`),误报概率低,最坏多一次视觉提示不阻断执行,非阻断性问题。
- **待修项回流 todo**: **无**(2 low 全非阻断:注释误导性可后续澄清/启发式误报风险可接受)。
### CR-260616-54 波12 F-04多Provider负载均衡池(数据层+选择器+并发原语) + CR-52白项清理(79b6a43) — 🟡 待审
### CR-260616-54 波12 F-04多Provider负载均衡池(数据层+选择器+并发原语) + CR-52白项清理(79b6a43) — ✅ 已审(PASS)
- **范围**: 2 agent workflow w7krn220n,文件域零重叠且不碰波11(audit/前端)。①**F-04 基础设施**(79b6a43):storage 层——V19 迁移幂等补 ai_providers.enabled(DEFAULT 1)+weight(DEFAULT 50)列(PRAGMA column_exists 探测,对齐 v17/v18)+ models.rs AiProviderRecord 加 enabled:bool+weight:u32(serde default 兼容老 JSON)+ crud.rs ai_provider_from_row(i32→bool/u32 解析,unwrapor 默认)+insert/update SQL 加新列 + 4 处 AiProviderRecord 构造点补字段(ai_node test/secret test/crud test/commands.rs ai_save_provider 新建默认 enabled=true weight=50)。provider_pool.rs(新,270 行)——ProviderPool::select(providers, model_id) 过滤 enabled&&weight>0,3 键稳定排序(主键模型亲和含 model_id 的 provider 排前保 F-01 路由结果 / 次键 weight 降序 / 末键 is_default 兜底)+ 9 单测。state.rs LlmConcurrency 加 per_provider HashMap<String,Arc<Semaphore>>+acquire_for_provider(返 Option,None 时无限流分支)+set_provider_caps。agentic.rs 循环入口 ProviderPool::select 选主候选(单 provider 零变化,首位=唯一)+流式块 acquire_for_provider permit。②**CR-52 white**:tool_registry.rs 删 `let _db = db.clone();` 死代码(防御 handler 闭包未引用,run_workflow 真执行经 ai_approve→execute_run_workflow_for_tool→run_workflow_inner)+ workflow.rs run_workflow_inner 加 triggered_by:&str 参数(命令层 run_workflow 传"manual"前端人工 / execute_run_workflow_for_tool 传"ai")+ mod.rs execute_run_workflow_for_tool L481 传"ai"+pub mod provider_pool。③**诚实拆波**:fallback 实际切换(InitFailed retryable 耗尽切备用 provider)未实施——agentic.rs L436-530 流式重试块嵌套 for iteration 内有自己的 per-provider 重试,fallback 需外层包 for candidate 循环+resolved_model 重算路由,回归面大,拆 F-04b 独立批。CR-52 项3 mod.rs canonicalize lexical normalize(安全边界 trust_key 归一化)+ 项4 agentic.rs 诊断日志语义(超文件域)均不做(留独立项)。
- **维度**: ①select 排序正确性(模型亲和优先保 F-01 路由结果不换走含模型 provider / weight 降序同亲和内 / is_default 兜底启动行为不变)+ 边界(0 enabled→空 Vec 调用方兜底 / 1 enabled→单元素无 fallback / model_id None→全亲和纯 weight) ②单 provider 零变化(池仅 1 enabled→select 单元素首位=唯一→build_provider 不变,老库迁移 enabled=1 行为同 F-01 前) ③per_provider permit Option 安全(未配 set_provider_caps 时 acquire_for_provider 返 None,drop(None) 无副作用,无限流分支) ④triggered_by 透传链完整(workflow.rs run_workflow_inner 加参 + 命令层"manual"+mod "ai",grep 仅 2 调用点) ⑤fallback 拆波诚实(F-04b 独立批,本轮端到端可用基础设施+9 单测) ⑥FR-S1 不破坏(resolve_provider_secret/ensure_resolved_key 闭环未动,api_key 不入 enabled/weight 字段/日志) ⑦V19 迁移幂等(PRAGMA column_exists 探测+DEFAULT 老库零变化+新库 V9 SQL 同步加列)
- **commit**: 79b6a43
- **主代核查(2026-06-17)**: cargo check --workspace EXIT 0(7 warning 全 pre-existing dead_code)/ cargo test -p devflow --lib **88 passed**(79 基线+9 provider_pool 新增全过)/ grep 核验形态(provider_pool.rs select 过滤 enabled&&weight>0+3键稳定排序 / agentic.rs L146 ProviderPool::select 取首位+L435 acquire_for_provider permit / state.rs LlmConcurrency per_provider HashMap / V19 迁移 PRAGMA 探测幂等)
- **审查 agent 待复审重点**: ①provider_pool select 3 键排序——核验模型亲和(含 model_id 的 provider 排前保 F-01 路由)+ weight 降序 + is_default 兜底,排序稳定不依赖输入顺序 ②单 provider 零变化——核验 select 单元素首位=唯一 provider 路径 + 老库迁移 enabled=1 行为等价 F-01 前 + 空池兜底 get_active_provider ③per_provider permit——核验 acquire_for_provider 返 Option(None 无限流)+set_provider_caps 未被调用(本轮无消费方,待 F-04c 前端)+drop(None) 无副作用 ④agentic 接入——核验 ProviderPool::select 取首位主候选 + acquire_for_provider permit 在流式块 + 不破坏单 provider 路径 ⑤triggered_by 透传——核验 run_workflow_inner 签名加参 + 2 调用点(manual/ai)更新无遗漏 + grep 仅 2 调用点 ⑥V19 迁移——核验 PRAGMA column_exists 幂等 + DEFAULT 1/50 老库零变化 + V9 建表 SQL 同步加列 ⑦fallback 拆波诚实——确认本轮未实施 fallback 实际切换(留 F-04b),基础设施可用
- **待修项回流 todo**: **无**(主代核查全过,待审查 agent 独立复审;F-04b fallback + F-04c 前端已记拆波)
- **复审结论(2026-06-17·CR-54 agent 亲跑 cargo+test+vue-tsc+独立 grep/read 核验)**: ✅ **PASS** — 🔴0 🟡0 ⚪0
- **验证**: `cargo check --workspace` EXIT 0(7 warning 全 pre-existing dead_code)/ `cargo test -p devflow --lib` **88 passed**(79 基线+9 provider_pool 新增全过)/ `cargo test -p df-storage` **33 passed**/ `npx vue-tsc --noEmit` EXIT 0
- **①provider_pool select 3键排序完备 PASS**:provider_pool.rs:57-91 过滤 `enabled && weight>0`(:64) + 稳定 3 键(:71-88 模型亲和 true 排前 / weight 降序 / is_default 兜底)。9 单测全覆盖(empty_pool_returns_singleton/single/disabled/zero_weight/model_affinity/model_id_none/weight_tie/is_default/full_scenario),亲跑全过。边界完备:model_id None 全亲和(:73-76 map_or true)/ 0 enabled 空 Vec 调用方兜底(:346-352)/ 1 enabled 单元素首位=唯一(:136-138)
- **②单 provider 零变化 PASS**:agentic.rs:342-345 ProviderPool::select 取首位,单 provider 池返回 `[p1]`,首位 `p1` 等价原 `provider_config`。老库迁移 V19 migrations.rs:343-356 `DEFAULT 1`/`DEFAULT 50`,crud.rs:1072-1073 unwrap_or(1)/unwrap_or(50),老行启用后等价 F-01 前。空池兜底(:346-352)用 `provider_config.clone()` 作唯一候选,同启动行为
- **③per_provider permit Option 安全 PASS**:state.rs:112 HashMap 定义 + :158-172 acquire_for_provider 返 `Option<OwnedSemaphorePermit>`,map.get?+acquireOwned(:164-171)。未配 set_provider_caps 时 HashMap 空,返 None,drop(None) 无副作用(:671 `_provider_permit` 绑定作用域自动释放)。本轮 set_provider_caps 无消费方(留 F-04c 前端),单 provider 零变化达成
- **④agentic 接入完整 PASS**:agentic.rs:342 `ProviderPool::select` 选主候选 + :346-352 split_first 取 primary + candidates + :663-710 `for candidate in &candidate_chain` 循环 + :667 `acquire_for_provider` permit + stream_one_provider(:669-670) 单 provider 流式 + :698 F-04b 日志"切换下一 provider"(基建就位,本轮候选链仅 primary 不触发切换)。单 provider 路径不破坏
- **⑤triggered_by 透传链完整 PASS**:workflow.rs:93 `run_workflow_inner``triggered_by: &str` 参数 + :133 落 `triggered_by: Some(triggered_by.to_string())` 入库 + :80 命令层传 "manual"(前端人工) + mod.rs:475 ai 路径传 "ai"(execute_run_workflow_for_tool)。grep 核验仅 2 调用点,无遗漏。tool_registry.rs:547-560 删 `let _db = db.clone();` 死代码,handler 仅返 Err 引导走审批路径,正确。
- **⑥V19 迁移幂等 PASS**:migrations.rs:342-360 `PRAGMA column_exists` 探测 enabled/weight + :345 `DEFAULT 1`/ :352 `DEFAULT 50` + V9 建表 SQL(:548-549 同步加列)。老库 ALTER 后取 DEFAULT,新库直接有列。crud.rs:1072-1073 ai_provider_from_row `unwrap_or(1)`/`unwrap_or(50)` 兼容缺失列(幂等测试场景)。insert/update SQL(:1159-1168/:1177-1178) 含 enabled/weight 列。
- **⑦fallback 拆波诚实 PASS**:agentic.rs:330-334 注释明确"F-04b 切换下一 provider"(本 candidate 重试耗尽切下一),:663-710 `for candidate in &candidate_chain` 循环已就位(:698 continue),仅候选链仅 primary(无 fallback 配置)不触发真实切换。基础设施可用,留 F-04b 独立批实际启用多候选切换。
- **⑧4 处构造点补字段 PASS**:commands.rs:986-1001(ai_save_provider 新建默认 enabled=true/weight=50)/ crud.rs:1762-1777(单测)/ secret.rs:209-215(单测)/ ai_node.rs:714-729(单测),4 处全补 `enabled: true, weight: 50`
- **待修项回流 todo**: **无**(7 维度全 PASS,零问题)。
### CR-260616-55 波13 B-21工具卡重复治本(audit tc_list按id去重保证emit Started唯一)(247dc5d) — 🟡 待审
- **范围**: workflow wice28c6y(前端守卫 agent + 后端走查 agent)。①**前端治标守卫**(HEAD 已有,非本轮新加):useAiEvents.ts:205 AiToolCallStarted push 前 `if (!findToolCall(event.id))` 幂等守卫(对齐 startToolSlowTimer:58)。②**后端走查结论**(不改代码):候选A确认——LLM 流式输出两个 tool_use block 不同 content_block index 但相同 id → stream_recv.rs:225 按 index(u32 键非 id)分桶 → 两条同 id 不同 index draft 并存 → audit.rs:538-549 每 draft emit Started → 同 id 两次 → 前端 push 两卡 → Completed 按 id 只 update 首张 → 次张残留 running 0行。候选B排除(provider 每轮新 id)。③**治本兜底(commit 247dc5d)**:audit.rs:533 sort 后加 `seen_ids HashSet + tc_list.retain(draft.id)` 按 id 去重,同 id 保留最小 index 首个,emit Started 唯一。前端守卫双保险。详 docs/02-架构设计/B-260616-21排查方案。
- **维度**: ①去重正确性(同 id 保留最小 index 首个,retain 后 drafts 唯一 id emit) ②不破坏正常多工具(不同 id 不受影响) ③HashSet import(audit.rs:3 加) ④与 F-05(High risk 去重缓存)无冲突(不同维度)。
- **commit**: 247dc5d。
- **主代核查**: cargo check EXIT 0 + 88 passed + grep retain 形态。
- **审查 agent 待复审重点**: ①retain 去重——同 id 保留首个(sort 后=最小 index),emit Started 唯一 ②不同 id 多工具不受影响 ③与 F-05 不同维度无冲突(F-05 不同 id 同命令去重,本条同 id 去重)。
- **待修项回流 todo**: **无**(治标守卫 HEAD 已有 + 治本去重落地,待独立复审)。
### CR-260616-56 波14 F-04c后端(provider池enabled/weight更新IPC+set_caps启动接入)(b3684f4) — 🟡 待审
- **范围**: workflow wodppv1hd。①ai_save_provider(commands.rs:929-948)编辑路径保 enabled/weight(修原硬编码清回默认 bug)。②ai_update_provider_pool IPC(commands.rs:1022-1056)轻量专用更新 enabled/weight 经 update_full,weight clamp[0,100],落库后 reload_provider_caps 重建 caps。lib.rs:128 注册。③set_provider_caps 启动接入(state.rs:297 AppState::init 调 reload_provider_caps L305-323 从 DB enabled providers 建 per_provider HashMap,cap=global_cap=3)。单点真理 helper(state 不依赖 commands 避免环形)。
- **维度**: ①ai_update_provider_pool 不触 ai_save_provider 密钥迁移分支(避免空 api_key 重发整表) ②reload_provider_caps 单点真理 ③cap=global_cap=3 单 provider 零变化 ④ai_save_provider 编辑保字段(新建默认 true/50)。
- **commit**: b3684f4。
- **主代核查**: cargo check EXIT 0 + 88 passed。
- **审查 agent 待复审重点**: ①ai_update_provider_pool update_full 落库(weight clamp) ②reload_provider_caps init 接入 + 变更重建 ③ai_save_provider 编辑保字段 ④set_provider_caps cap 策略(本轮 global_cap,F-04d 差异化留)。
- **待修项回流 todo**: **无**(后端就绪,前端 Settings UI 留波16)。
### CR-260616-57 波15 F-04b多provider fallback切换(stream_one_provider+候选链+resolved_model重算)(80c0955) — 🟡 待审
- **范围**: workflow w3xow3x3l agent A,agentic.rs +287/-112。①抽 stream_one_provider(agentic.rs:155-298)封装单 candidate:build_provider_for(resolve+ensure+build,key Err→Fatal emit Auth)+ select_model_id 在 candidate.model_configs 重算 resolved_model(provider 切换模型池不同,防 400/404)+ 流式 stream_recv + 流前 InitFailed retryable 重试,返回 StreamOutcome(Success{resolved_model}/InitFailedExhausted/Fatal)。②外层 for candidate in &candidate_chain(L663)包裹,候选链 [primary,...candidates] 预 clone primary 入链避 E0506。③InitFailedExhausted(retryable 耗尽)→ continue 'candidate 切下一候选,helper 内重建 build_provider + 重算 resolved_model。④Fatal(4xx 非429)立即放弃整个 fallback(L703 guard.reset+return)。⑤全 candidate 耗尽→沿用单 provider 失败语义。per-provider permit 候选循环内取(切换 Drop 释放旧槽);retry_deadline 30s 各 candidate 共享预算(防串行累加)。
- **维度**: ①stream_one_provider 抽取语义(resolved_model 在新 provider 重算保 F-01 路由) ②候选链外层 + InitFailed 切换 ③Fatal 立即放弃(对齐 retry.rs) ④per-provider permit 切换释放 ⑤retry_deadline 共享预算 ⑥单 provider 零变化(候选链仅 primary 不触发切换)。
- **commit**: 80c0955。
- **主代核查**: cargo check EXIT 0 + 88 passed(保基线零回归)。
- **审查 agent 待复审重点**: ①stream_one_provider——resolved_model 在新 candidate.model_configs 重算 select_model_id(防 400/404) ②for candidate + InitFailedExhausted continue 切换 ③Fatal 立即放弃 + 全耗尽报错 ④per-provider permit acquire_for_provider 切换 Drop ⑤retry_deadline 共享 ⑥不破单 provider 路径。
- **待修项回流 todo**: **无**(fallback 完整实施,待独立复审)。
---