Files
DevFlow/docs/02-架构设计/工作流审批审查报告-2026-06-14.md
绝尘 04032a2a8d 重构: 文档汇总+进度看板+孤儿任务清理脚本+gitignore 噪音排除
- 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/ 噪音排除
2026-06-15 05:14:21 +08:00

8.2 KiB
Raw Blame History

工作流审批子系统对抗审查报告

2026-06-14 · 多代理审查(31 agents / 5 维度 × 对抗验证) + 主代理独立复核 + 二轮对抗论证 范围: df-workflow/{executor,state,eventbus}.rs · df-nodes/human_node.rs · df-core/events.rs · src-tauri/{commands/workflow,state}.rs · src/stores/project.ts · src/api/{types,workflow}.ts · src/views/ProjectDetail.vue 方法: workflow fan-out 审查 → 每发现独立对抗验证(默认反驳) → 主代理读源码复核 criticals → 二轮对抗论证(存在性/可达性/危害三维)

TL;DR

  1. 头号 bug [P0 阻断]: human_node.rs:41HumanApprovalRequest.awaitEventBus::send 是 async fnlet _ = async_fn() 丢弃 Future 未 poll → body 不执行 → Request 从未进入 channel。审批链最上游断裂。
  2. ①②(前端契约失配) 被头号遮蔽: Request 没发 → 前端 onEvent 收不到 → type 匹配分支不可达。修 41 行后 ①② 才显形为 critical。
  3. 当前 UI 无 human 节点 DAG 入口: demoDag 仅 script 节点AI 工具 run_workflow 返回提示不执行。审批相关 9 项发现现实触发率=0全部潜伏。
  4. 根因: 审批功能前端从未端到端跑通41 行 await 漏掉即铁证),单测绿但不覆盖 human→前端弹窗→审批→返回链路。

0. 头号发现 [P0]

0.1 缺陷定位

crates/df-nodes/src/human_node.rs:41-47

let _ = ctx.event_bus.send(WorkflowEvent::HumanApprovalRequest {
    execution_id: ctx.execution_id.clone(),
    node_id: ctx.node_id.clone(),
    title: title.to_string(),
    description: description.to_string(),
    options: options.clone(),
});   // ← 无 .await

0.2 机制

  • EventBus::send 签名 (eventbus.rs:33): pub async fn send(&self, event: WorkflowEvent) — async fn。
  • let _ = async_fn() 求值得 Future绑定 _ 后语句结束立即 dropFuture 零 poll
  • async fn body (self.sender.send(event)) 仅在 Future 被 poll 时执行 → 此处永不执行 → Request 未进 broadcast channel。

0.3 对抗自检(排除假阳)

质疑 核实
测试为何 pass? normal_approval_returns_decision 的 helper send_responsesend(...).await(human_node.rs:172 有 await) 发的是 ResponseHumanNode 的 Request 那行没 await。测试只断言 Response 被 rx 收到并返回 decision不验证 Request 是否发出。绿测不能证伪。
编译器为何不报? let _ = expr 合法通配符绑定async fn 生成的 Future 默认无 #[must_use],零 warning。
是否误用同步 fn? eventbus.rs:44 emit_human_approval_request 是同步 fn(返 Result),但 HumanNode 未用它,用的是 async send
多代理为何漏? 审查聚焦前端契约(①②)与串扰(③),未逐行核 HumanNode 内 send 调用点是否 await。主代理二轮读 human_node.rs 全文才发现。

0.4 触发路径

human_node.rs:38 subscribe → :41 send(无await) → Request 未发 → workflow.rs 转发任务 rx 收不到 → 前端 onEvent 不触发 HumanApprovalRequest 分支 → pendingApproval 恒 null → 弹窗不开 → HumanNode select! 阻塞至 :86 sleep_until(deadline) 默认 3600s 超时 → Err → 工作流 failed。

0.5 修复

- let _ = ctx.event_bus.send(WorkflowEvent::HumanApprovalRequest { ... });
+ ctx.event_bus.send(WorkflowEvent::HumanApprovalRequest { ... }).await;

1. 反转结论

1.1 ①② 被头号遮蔽(降级: 当前不可达)

  • project.ts:214 payload.event?.type === 'HumanApprovalRequest'大驼峰vs 后端 events.rs:18 #[serde(tag="type", rename_all="snake_case")] → 序列化值 'human_approval_request',永不匹配。
  • project.ts:215 payload.event.data —— event 是 WorkflowEvent 本体扁平结构({type, execution_id, node_id, title, description, options}),无 data 包装层 → undefined。
  • 两者代码层面确凿,但 Request 未发(§0 遮蔽) → 前端收不到事件 → 分支不可达。修 41 行后 ①② 立即变 critical

1.2 当前 UI 无 human 节点入口(多数发现现实触发率=0

证据 位置
前端唯一 DAG = demoDag仅 3 个 script 节点 ProjectDetail.vue:271-273
AI 工具 run_workflow 返回提示不执行 DAG tool_registry.rs:346-350
runDemoWorkflow 按钮 disabled=workflowRunning 防重入 ProjectDetail.vue:281-289

→ 审批相关发现(①②④⑤⑦⑧⑨⑩)全部潜伏在未接入路径。


2. 逐条对抗裁定

# 定位 存在性(代码层) 现实可达性 危害校正 裁定
0 human_node.rs:41 确凿 缺await human DAG 运行即必现UI 无入口 Request 不发,链最上游断 真·潜伏头号 P0
project.ts:214 确凿 snake_case 被0遮蔽+无human入口 潜伏修0后才显形 降级: 当前不可达
project.ts:215 确凿 flat无data 同① 双重遮蔽 潜伏 降级: 同①
workflow.rs:67-97 确凿 全局bus+Node*无exec_id+matches!只看变体 需并发工作流UI防重入+AI不执行 审批路由不坏(Request自带exec_id),仅日志串扰+提前break 真实架构缺陷现实触发0危害被夸大
project.ts:15-21 确凿 单槽 三重遮蔽(0+无入口+单流) 潜伏 真实,潜伏
project.ts:208-261 确凿 无终态监听 同④;"稍后"按钮可视觉关 UX退化非卡死 真实,危害偏低
state.rs:106 确凿 直接insert 需⑦竞态命中 snapshot()零调用方(死代码)DB不受影响 真实但无害
project.ts:228 确凿 无互斥 需0+①②+入口全通+双击+500ms窗口 窄窗口 真实,潜伏
workflow.rs:171 确凿 无校验(对比cancel有registry守卫) 需human+超时窗口 诊断损失非功能损坏 真实,潜伏
human_node.rs:73-81 确凿 warn+continue 需256积压当前无节点发NodeProgress每节点≤2事件需128+并发节点 概率近0 真实,当前不可达
workflow.rs:91-97 确凿 Lagged+Closed死代码(bus持于AppState全程) 同⑨ 需256积压 DB终态仍正确仅task泄漏 真实,当前不可达
workflow.rs:140 确凿 String::new() demoDag失败即触发 低危: error字段含first_err.context"节点X失败"failed_node冗余空 真实且当前可达,危害低

3. 根因

审批功能前端从未端到端验证。证据链:

  1. B-260614-03a 重写 HumanNode execute(subscribe→send→select!) 时引入 :41 await 缺失7 单测全绿未抓(单测不覆盖 Request 发出)。
  2. 前端无 human 节点 DAG 入口,无任何集成测试覆盖 human→弹窗→审批→返回链路。
  3. ①② 是前后端契约层断裂,types.ts:127 event.type: string 弱类型无编译期拦截。

→ 单测绿 + 无端到端测试 = 这批潜伏 bug 的存活土壤。


4. 修复优先级

动作 优先级 依赖 会暴露
1 补 human 节点端到端集成测试(含human的DAG→运行→断言前端收到Request+弹窗开) P0 0+①+②
2 human_node.rs:41 加 .await P0 测试暴露后修
3 project.ts:214 type→snake_case; :215 取 event 本体字段; types.ts event.type 收窄字面量联合 P0(修2后) 2
4 ③ Node*事件补 execution_id + 转发过滤 P2 并发工作流时
5 ④⑤ pendingApproval 改 Map + 终态清空 P2 2③后
6 ⑥ set_cancelled 查终态 no-op P3 无(无消费者零危害)
7 ⑦⑧⑨⑩⑪ P2-P3 见§2

附: 多代理 workflow 元数据

  • 31 agents / 5 维度(concurrency / state-machine / error-handling / lifecycle / frontend-contract) × 对抗验证
  • 26 原始发现 → 12 确认 / 11 反驳 / 3 验证代理因 API 限流未跑(主代理手动补判)
  • 验证阶段正确识别 executor.rs:124 三条为 not-a-bug(B-03b-R1 已修,:127 有 is_cancelled guard + test_cancelled_node_skips_set_failed)
  • 漏抓头号(§0): 因未核 send await主代理二轮复核补