# df-workflow 核心代码走查报告

> 走查日期: 2026-08-02
> 范围: crates/df-workflow/src/ (11 文件, ~108KB)
> 核心文件: executor.rs(258行), conditions.rs(934行), state.rs(257行), registry.rs(81行), node.rs(77行), dag.rs, dag_def.rs, eventbus.rs(57行)

---

## 问题汇总

| # | 等级 | 文件 | 类型 | 简述 |
|---|---|---|---|---|
| 1 | 🔴 P0 | conditions.rs | bug | 单引号字符串 `''` 转义为 `'` 时闭引号后再跟 `'` 会误判(如 `'it''s'` 跨行) |
| 2 | 🟡 P1 | conditions.rs | smell | 934 行单文件，tokenizer/parser/jsonpath/测试全混在一起，维护性差 |
| 3 | 🟡 P1 | conditions.rs | risk | 默认 feature `conditions-eval` 关闭，934 行代码实际不生效 |
| 4 | 🟡 P1 | executor.rs | risk | `condition` 边条件求值仅在 `eval_conditions` 为 true 时收集 inputs，但 `outputs.insert` 仍写入所有节点，已跳过的节点 output 可能被下游无条件边误消费 |
| 5 | 🟡 P1 | state.rs | risk | 锁中毒降级日志缺少 `node_id` 上下文，排查时难以定位哪个节点触发了中毒 |
| 6 | 🟢 P2 | node.rs | smell | `NodeContext` 的 `inputs` 类型为 `HashMap<String, NodeOutput>`，`NodeOutput` 仅含 `data: serde_json::Value`，工具调用结果无法区分来源 |
| 7 | 🟢 P2 | executor.rs | smell | 条件路由的 `eval_conditions` 标志取自 `cfg!(feature = "conditions-eval")`，编译期常量化后分支在运行时不可变，但 executor 仍用 `if eval_conditions` 运行时分支 |
| 8 | 🟢 P2 | eventbus.rs | smell | `send` 参数 `async fn` 但实际是同步调用（`broadcast::send` 同步），`async` 包装造成误导 |
| 9 | 🟢 P2 | executor.rs | smell | `adjacency_in` 预建索引仅用于 `condition` 场景，非条件场景下 `HashMap::new()` 占位浪费 |

---

## 🔴 P0 问题详述

### #1 conditions.rs 单引号转义边界 bug

**文件**: `conditions.rs:200-225`
**严重**: P0 — 可能产生错误的表达式求值结果

**现状**: `consume_quoted` 函数在遇到 `''` 时跳过（视为转义），但 `consume_key` 不处理单引号。

**影响**: 表达式 `'it''s' == 'value'` 中，`'it''s'` 被解析为 `it's`，但闭引号后紧跟 `'==` 可能导致 `consume_key` 截断异常。

---

## 🟡 P1 问题详述

### #2 conditions.rs 单文件 934 行

**建议**: 拆为 `tokenizer.rs` / `parser.rs` / `jsonpath.rs` / `tests.rs` 四个模块，类似 df-nodes 的拆分模式。

### #3 conditions-eval feature 默认关闭

**现状**: 934 行代码 + 全面测试 + 递归下降解析器 + JSON Path 引擎，全部在 feature gate 后面。executor 中 `eval_conditions` 编译期常量化，运行时条件路由不生效。

**建议**: 要么默认开启 feature，要么在 ARCHITECTURE.md 中说明为何写了不开。

### #4 executor 条件边 output 残留

**文件**: `executor.rs:188`
**风险**: 已跳过的节点（condition 不满足），`outputs.insert` 不会执行（因为 `continue` 了），但若后续层有**无条件边**引用该跳过的节点，`outputs.get` 返回 None，下游节点inputs 空。

**建议**: 确认无条件边是否有 fallback 处理，或补测试覆盖。

### #5 锁中毒日志缺少 node_id

**文件**: `state.rs:60-68`
**建议**: 在 `lock()` 失败时输出的 `tracing::error!` 中增加 `node_id` 参数，便于排查。

---

## 正面评价

1. **状态机设计优秀**: `is_legal` 转换表清晰，`set_cancelled` 作为唯一受控旁路，设计合理
2. **executor 取消处理**: Ok/Err 分支对称处理已取消节点，状态机与事件类型一致，TOCTOU 已修复
3. **EventBus broadcast 容量 256**: 审批低频场景设计合理
4. **conditions 求值失败保守 false**: 安全优先，文档清晰
5. **锁中毒降级不 panic**: 返回保守默认值，符合"无 panic"铁律
6. **测试覆盖**: state.rs 测试完整（合法/非法/克隆/取消），executor 测试在 executor_helpers.rs