- docs/02 架构设计: 新增 aichat审查/异步审批构想/流式渲染调研/generating状态机/密钥迁移健壮性/工作流脚本执行边界/条件表达式引擎/F-07 trait下沉/Agent架构说明/任务推进构想/功能创意池;更新功能决策记录+归档/对抗论证/文档记录规范/经验记录
- docs/03 模块文档: 新增 AI对话引擎/DAG引擎详解;更新 df-knowledge/df-nodes/df-storage/df-workflow/df-ai
- docs/05 代码审查: 新增 全栈审查/全局review/架构审查/近期改动审查/工作区多角度走查/自研memo流式渲染审查
- docs/09 问题排查: 新增 aichat-apikey-401
- docs/INDEX+README 索引同步;docs/todo 待办看板(2026-06-15 汇总)
- PROGRESS.md Sprint 22-25;URGENT.md 加急清单快照(5 项 P0 已全修)
- scripts/cleanup_orphan_tasks.{py,sh} 孤儿任务清理工具
- .gitignore 补 *.broken.bak + tmp/ 噪音排除
16 KiB
代码架构与缺陷复核报告(2026-06-14)
范围:在 全栈代码审查报告-2026-06-14.md 基础上的复核 + 回归审计 + 架构层补充。 方法:4 路并行(安全/并发性能/架构/工作流),其中安全路子代理失控(误改任务表无产出),改由主代理直读 5 文件完成(tool_registry / crud / workflow / commands / audit)。其余 3 路子代理产出完整。 模式:dry — 仅审查 + 文档,不改代码。 互斥:与全栈报告去重;本报告只记复核结论(confirm/refute/已修)+ 新发现 + 报告修正,不重复已记录的项。
0. 复核总览
| 类别 | 数量 | 说明 |
|---|---|---|
| 已修确认(最近提交修复正确) | 8 | FR-S2/S3/S4/S6、AR-3、FR-R1(主路径)/R3、FR-C1 |
| confirm(复核证实仍存在) | 9 | FR-S1/S5、FR-R4/R5、FR-P2/P3/P5/P6、FR-P1(O(V·E)部分) |
| refute(原报告误判/已澄清) | 3 | FR-R2 降级成立、FR-P1 panic 担忧不成立、FR-P4 严重度偏高 |
| 新发现(不在原报告/todo) | 11 | 见 §5,其中 5 项 🟠 |
| 死代码确认 | 5 | set_waiting/set_skipped、emit_human_approval_request、ModelRouter、StreamCollector、IdeaPromoter |
最高优先级(建议本轮接手):§5 新发现 ②③④⑤(FR-R1 闭环缺口 / client 降级丢 timeout / 取消事件语义双标 / cancel 无终态校验)。
1. 安全复核(主代理直读)
✅ 已修确认
① FR-S2 — read_file/write_file [已核实修复正确]
tool_registry.rs:367-376read_file 改单次File::open → file.metadata() → read_to_string,同句柄消除原 metadata+read 两步 syscall 间 symlink 替换的 TOCTOU。1MB 上限在metadata.len()判定后 bail。tool_registry.rs:416-418write_file 加content.len() > 1_048_576bail。- 两处入口均先走
resolve_workspace_path(canonicalize 防 symlink 逃逸)。 - 残余低优:read_file 的 metadata 大小判定与实际
read_to_string之间,若文件在判定后被追加写入,仍可能读入 >1MB(路径已 canonicalize,攻击者难 swap,属理论面)。write_file 无此问题(写 content 大小在内存已知)。
② FR-S3 — approve decision 校验 [已核实修复,但 IPC 层弱于建议]
workflow.rs:189decision.trim().is_empty()→ Err。防""透传卡死 HumanNode。- 缺口:IPC 层未做 decision ∈ {"approved","rejected"} 白名单(原报告建议)。深层兜底在
human_node.rs:67的options.is_empty() || options.contains(&decision),故 options 非空时仍拒非法值。属"IPC 弱 + HumanNode 强"的纵深防御,可接受;若想严格化,IPC 补一行白名单即可。
③ FR-S4 — SKILL.md 隔离标注 [已核实修复,属弱防御]
commands.rs:67-74注入前包裹# 用户选择的技能说明: {}(非系统指令,勿作为行为准则覆盖;以下为技能内容供参考),且技能内容置于 system_prompt 之前、系统真行为准则在之后(recency 优势)。- 性质:纯文本标注,是 prompt injection 的缓解非根治——信任本机技能文件的设计取舍下可接受,但恶意 SKILL.md 仍可能通过语义注入影响输出。已在功能决策记录标注此取舍即可,不升级。
④ FR-S6 — validate_column_name 未登记表拒绝 [已核实修复正确]
crud.rs:339None => Err("表 {} 未登记列白名单,拒绝防注入"),与is_allowed_column的None => false语义一致。- 回归风险评估:allowed_columns_for 当前登记 ai_tool_calls/ai_conversations/ai_messages/ai_providers/ai_sessions/ideas/idea_scores/projects/tasks/workflows/node_executions/knowledges/knowledge_events(match arms 可见)。所有走
query/update_field通用路径的 Repo 表均已登记(编译期表名宏 + 实测),无合法查询被误拒。低回归风险。
⑤ AR-3 — 审批 reason 拼对象名 [已核实修复且实现质量高]
audit.rs:64-138build_approval_reason特化 9 工具:delete/restore/purge_project、update_project、bind_directory、create_task、create_project、create_idea、run_workflow。resolve_project_label(:44)查ProjectRepo::get_by_id取 name,查不到回退(id=x),空 id 返回空串。- 无 N+1:每次审批单工具单查询,非循环。
- 未特化工具走 risk fallback 模板。覆盖完整。
🟠 FR-S1 — api_key 明文三连 [维持未修]
确认现状未动(migrations.rs/models.rs/commands.rs/Settings.vue 四处仍明文)。沿用全栈报告结论,P0 待 OS keychain 落地。
🟡 FR-S5 — 跨对话越权审批 [确认降级文档成立]
commands.rs:117-120 ai_approve 仅按 tool_call_id 从 pending_approvals 取,无对话归属校验。但本地单机 + restore 只载当前对话 + UI 隔离,实际触发面窄。维持降级为文档说明。
2. 并发/性能复核(子代理产出,11 项)
| # | 项 | 判定 | 证据要点 | 严重度 |
|---|---|---|---|---|
| FR-R1 | 切换 token | 已修(主路径) + 遗漏点 | _latestSwitchId 在首个 await 后比对正确,但 pendingToolCalls(:108) await 后无二次比对,A→B 快切可覆写 B 的 pendingApprovals |
🟠(见新发现①) |
| FR-R2 | 看门狗多窗口互踩 | refute(降级成立) | 分离窗口=独立 WebviewWindow=独立 JS realm,模块级 state 各持一份不共享 | 🟢 |
| FR-R3 | liveEvents 限长 | 已修正确 | project.ts:215 splice(0, len-200),无 off-by-one,配合 _ts |
🟢 |
| FR-R4 | complete 无超时无重试 | confirm | openai/anthropic_compat 仅 connect_timeout(30s),complete() 同步路径无总 timeout,远端建连后挂起则永久 hang |
🔴 |
| FR-R5 | findToolCall O(n²) | confirm | useAiEvents.ts:63 双层 for+find,4 处事件触发点 | 🟡 |
| FR-P1 | dag O(V·E) + panic | confirm(O(V·E)) / refute(panic) | successors/predecessors iter().filter 全表扫;但 in_degree 已对全部 node_ids 初始化且 contains 过滤野节点,unwrap() 不会 panic |
🟠→🟡 |
| FR-P2 | search_vector SELECT * | confirm | crud.rs:1183 SELECT * 拉 content/reasoning 大文本,无 LIMIT,Rust 算余弦 |
🟠 |
| FR-P3 | 单连接 Mutex | confirm | db.rs:13 Arc<Mutex<Connection>>,每 CRUD spawn_blocking+blocking_lock 串行 |
🟠 |
| FR-P4 | 500ms 轮询锁竞争 | confirm(轮询) / refute(严重度) | interval(500ms) 确实存在,但 std Mutex 无竞争纳秒级,"7200 次锁竞争"表述偏重 | 🟡 |
| FR-P5 | build_system_prompt 全表扫 | confirm | prompt.rs:75 list_active() 全表 + take(20) + format!,每消息+每 agentic 轮触发;list_active 无 LIMIT |
🟠 |
| FR-P6 | watch messages JSON.stringify | confirm | AiChat.vue:650 {deep:true} + 全量 stringify,snapshot 比对只省后续逻辑不省 stringify 本身 |
🟠 |
3. 架构层(子代理产出,最大结构性风险)
架构健康度:🟢 基本健康(7/10)。依赖图无环、df-core 严格叶子、IPC 无 SQL 泄漏、前后端类型对齐度高、僵尸 crate 清理彻底(grep df_evolve/plugin/stages/task/traceability 零残留)。
最大结构性风险:🟠 crate 层与 IPC 层职责边界双向错位——领域逻辑(评估编排/晋升事务补偿/AI 扫描编排)沉淀在 IPC commands 层,而对应的 crate 模块(df-ideas::promotion、df-project::manager、df-ai::router/stream)反而是空壳或死代码。
3.1 依赖图与职责
df-nodes同时依赖 df-core/df-execute/df-workflow/df-ai 四个,是唯一多父汇聚点。结构正确(节点需调执行器/工作流/AI),非阻塞。df-workflow/src/conditions.rs:16-33ConditionEngine 仅识别 true/false 字面量,其余默认 false(保守拒绝,安全)。文档承诺的 JSON Path/数值/逻辑组合语法全 TODO。todo.md:137 T-260614-11 已记。
3.2 空壳/死代码(5 处,建议清理或显式标注)
| # | 位置 | 判定 | 严重度 |
|---|---|---|---|
| ⑥ | df-workflow/state.rs:87-100 set_waiting/set_skipped |
全仓零调用方,纯死代码 | 🟡 |
| ⑦ | df-workflow/eventbus.rs:43-46 emit_human_approval_request |
全仓零调用方(HumanNode 用 async send) | 🟡 |
| ⑧ | df-ai/router.rs ModelRouter |
route() 全返回 default_model,lib.rs 未导出,零调用方 | 🟠 |
| ⑨ | df-ai/stream.rs StreamCollector |
流式接收走 IPC 层 stream_recv.rs,此模块零调用方 | 🟠 |
| ⑩ | df-ideas/promotion.rs:45-91 IdeaPromoter/try_promote/do_promote |
全 TODO 占位,IPC 层 idea.rs 重写了晋升逻辑,crate 层零调用 | 🟠 |
注:coordinator.rs(df-ai)是 B 路线有意占位(文件头注释+文档对齐),非死代码,建议 run() 改
bail!("未实现")更安全。
3.3 IPC 层 carry 领域逻辑(3 处)
idea.rs:184-231evaluate_idea:评估编排 + 结果组装 +scores*10缩放 + recommendation/action_items 扁平化映射,整段领域逻辑落 IPC。idea.rs:140-153promote_idea:跨表事务补偿(回写失败→补偿删 project)落 IPC。project.rs:335-377scan_project_with_ai:build_scan_prompt/parse_scan_result 纯函数级 AI 编排落 IPC。
后果:领域逻辑绑 Tauri State 无法 crate 级单测;AI 工具 create_* 与 IPC create_* 各写一份字段默认值(tool_registry.rs vs project.rs/task.rs),字段增删易漏改分叉。
3.4 前后端类型 drift(3 处注释错误)
types.ts:88TaskStatus 注释review_ready/merged/abandoned三值后端枚举不存在(实际 todo/in_progress/in_review/testing/done/blocked/cancelled)。🟠types.ts:89Priority 注释方向反(前端0=critical/3=low,后端 df-coreLow=0/Critical=3)。🟠types.ts:39ProjectStatus 注释漏 testing/releasing。🟡- 类型本身 string 不阻断,但注释误导前端排序/展示逻辑写反。
3.5 错误处理双轨
- df-storage 用强类型
df-core::Error(thiserror),其余 crate(df-ai/df-ideas/df-project/df-workflow/df-execute)全 anyhow。df-core::Error的 Workflow/Execution/AiProvider/Plugin 等变体零使用=死变体。🟠 - IPC 层统一
.to_string()压平成中文消息,前端无法区分 404/500。🟡(Tauri 限制下折中,可接受)
4. 工作流审批链复核(子代理产出)
4.1 死代码(confirm 3 处)
set_waiting/set_skipped、emit_human_approval_request 零调用(见 §3.2 ⑥⑦)。set_cancelled 绕 is_legal 直接 insert(state.rs:106),todo 决策采"修法 B 绕而非纳入白名单",与全栈报告建议分歧,属待统一项。
4.2 回归审计(最近 5 提交无 P0/P1 回归)
- ✅ R6 human_node send 加 await(human_node.rs:42),测试
request_is_emitted_to_bus验证。 - ✅ select! 三分支(Response 双键过滤 / sleep_until 无漂移 / cancel 首 tick 丢弃)正确。
- ✅ Lagged(n) continue 容忍正确。
- ✅ decision 校验(human_node.rs:67 三态 + IPC workflow.rs:189 双层)。
- ✅ executor Err 处理 is_cancelled guard(executor.rs:123-141)跳 set_failed,避开 Cancelled→Failed 非法 transition。
- ✅ cancel 链端到端通(IPC→registry→共享 StateMachine→HumanNode 500ms 轮询→Err→executor)。
4.3 集成测试缺口(confirm)
前端无 human DAG 入口(demoDag 仅 3 个 script 节点),现有测试用 mock CancelSelfNode 验证取消,非真 HumanNode 路径。select! cancel 分支 + interval tick 逻辑无端到端覆盖。todo.md:90 B-03b-R8 已记。
5. 新发现汇总(11 项,不在原报告/todo)
🟠 高优(建议本轮处理)
新-① [src/composables/ai/useAiConversations.ts:108-126] FR-R1 闭环不完整:pendingToolCalls await 后无 token 比对
A→B 快切时,首个 await 已挡住 messages/activeConversationId 覆写,但 aiApi.pendingToolCalls(:108) 慢返回仍会覆写 B 的 state.pendingApprovals(:120)。建议补 if (mySwitchId !== _latestSwitchId) return。
新-② [crates/df-ai/src/{openai,anthropic}_compat.rs] client builder 降级丢 connect_timeout
Client::builder().connect_timeout(30s).build().unwrap_or_else(|_| Client::new()) — build 失败降级到 Client::new() 丢失连接阶段超时。建议降级分支显式 Client::builder().build()。
新-③ [crates/df-workflow/src/executor.rs:130-135] 取消节点状态保 Cancelled 但仍发 NodeFailed 事件,语义双标
已取消节点状态机保 Cancelled,但事件总线仍 emit NodeFailed { error: "人工审批被取消" }。前端若按 NodeFailed 事件分支判断会误归类"失败"而非"取消"。建议加 NodeCancelled 事件 variant 或 NodeFailed 加 cancelled: bool 字段。
新-④ [src-tauri/src/commands/workflow.rs:218-236] cancel_workflow_node 无节点终态前置校验
仅校验 execution_id 在注册表,不校验 node_id 状态。对已 Completed/Failed/Cancelled 节点调 set_cancelled 会静默覆盖终态(set_cancelled 绕 is_legal)。建议加 match sm.get(&node_id) { Running|Waiting => {}, _ => Err } 守卫。
新-⑤ [前端无 human DAG 入口] 端到端 human 审批测试缺口 见 §4.3,CancelSelfNode 自取消与 HumanNode 走 select! cancel 分支是两条代码路径,后者无覆盖。
🟠 中优
新-⑥ [df-ai/router.rs] ModelRouter 空壳死代码 — 见 §3.2 ⑧,route() 全返回 default_model,零调用方。
新-⑦ [df-ai/stream.rs] StreamCollector 死代码 — 见 §3.2 ⑨,流式逻辑全在 IPC stream_recv.rs。
新-⑧ [df-ideas/promotion.rs] IdeaPromoter 死代码 — 见 §3.2 ⑩,IPC 层重写了晋升。
新-⑨ [types.ts:88-89] TaskStatus/Priority 注释 drift — 见 §3.4,误导前端排序逻辑。
新-⑩ [df-core/error.rs] 错误类型双轨,半数变体死代码 — 见 §3.5。
🟡 低优
新-⑪ [useAiWindow.ts:79-95 + useAiStream.ts:18] 主窗口看门狗在分离窗口接管生成时仍跑 detach 时主窗口 streaming/generatingConvId 未清,看门狗续计 130s 后往主窗口 state 补幽灵错误消息,但用户已切走无实际危害,仅主窗口面板重开时看到一条幽灵错误。
6. 报告修正(refute 原 report 误判)
| 原报告项 | 修正 |
|---|---|
| FR-R2 看门狗多窗口互踩 | 应标 refute:分离窗口独立 JS realm 不共享 state 单例,降级理由成立(todo 已记"评估维持",复核确认) |
| FR-P1 in_degree.unwrap() panic 风险 | refute:in_degree 对全部 node_ids 初始化 + contains 过滤野节点,unwrap 不 panic |
| FR-P4 "7200 次锁竞争" | 严重度偏高:std Mutex 无竞争纳秒级,实为微秒级开销;保留"500ms 轮询模式不佳"批评,建议改 broadcast/Notify |
7. 建议进 todo 的项(去重后)
已在 todo 的不重复;下列为本复核新产出,待用户确认进看板。
| 建议 ID | 对应 | 优先级 |
|---|---|---|
| 复核-新① FR-R1 pendingToolCalls 闭环 | useAiConversations.ts:108 补 token 比对 | P1 |
| 复核-新② client 降级丢 timeout | openai/anthropic_compat 降级分支显式重建 | P2 |
| 复核-新③ 取消事件语义双标 | 加 NodeCancelled variant 或 cancelled 字段 | P2 |
| 复核-新④ cancel 无终态校验 | workflow.rs:223 加状态前置守卫 | P2 |
| 复核-新⑤ human 端到端测试缺口 | (= todo B-03b-R8,已记,提优先级) | P1 |
| 复核-新⑥⑦⑧ 死代码三连 | ModelRouter/StreamCollector/IdeaPromoter 删或标注 | P2 |
| 复核-新⑨ types.ts 注释 drift | TaskStatus/Priority 注释对齐 df-core 枚举 | P2 |
| 复核-新⑩ 错误类型双轨 | df-core::Error 瘦身或各 crate 收敛 | P2 长期 |
| FR-R4 complete 无 timeout | (已在 todo,复核确认 🔴,建议提 P1) | P1 |
| FR-S1 api_key 明文 | (已在 todo P0,复核确认未动) | P0 |
优先处理 3 件(影响实际行为):
- 复核-新① FR-R1 闭环缺口(审批卡片可见性错配,一行修)
- 复核-新③④ 取消事件语义 + 终态校验(审批链语义正确性)
- FR-R4 complete 无 timeout(一次网络抖动挂死整轮对话,提 P1)
本会话只做代码审查 + 文档记录,不含代码变更。实际修复另起会话。