新增: 批次工作落地(推进链/评估闭环/事件总线/并发/加固) + 技术债清理 + 文档整理

后端:
- 工作流推进链(D-03):advance_task/状态机/闸门走 df-nodes Node trait,conditions 条件引擎扩展
- 想法评估闭环:启发式评分+对抗评估,df-ideas/scoring + df-storage/idea_eval_repo + idea 前端打通
- 全局事件数据总线:df-ai/context+context_helpers+augmentation 跨模块解耦
- AI planner/plan_hint/intent:aichat B 路线并行多轮基础
- patch_file 加固(TD-03/04):读改写整体锁防 lost update,expected_hash 合约闭环
- 压缩超时兜底(F-15 卡死根治)
- F-09 多会话并发:LlmConcurrency per-conv + streamingGuard 前端守护 + verify 脚本
- 知识注入 DRY/skills/audit 扩展

清理:
- aichat 技术债(误报 allow/死导入/过时注释 30 项)
- URGENT.md 删除(11 项加急全解决/迁 todo)
- 文档整理(todo/待决策/待审查/ARCHITECTURE/INDEX + 总线/技术债审查新文档)
This commit is contained in:
2026-06-21 20:51:26 +08:00
parent 330bb7f505
commit bd6a41fe6e
111 changed files with 11932 additions and 1034 deletions

View File

@@ -1,13 +1,15 @@
//! DagExecutor 测试套件 — 从 executor.rs 抽离(纯搬迁,零行为变更)
//!
//! 抽离说明:executor.rs 原 513 行中 tests mod 占 305 行(60%),主体 run 仅 157 行。
//! 独立成文件降低阅读 run 主体的滚动噪音,测试逻辑、断言、节点 mock 全部原样搬迁。
//! 抽离说明:executor.rs 主体 run 逻辑之外曾带大量内联测试,占比偏高(抽离前的历史
//! 行数分布不再维护),独立成文件降低阅读 run 主体的滚动噪音测试逻辑、断言、节点 mock
//! 全部原样搬迁。
//! 通过 `#[path = "executor_helpers.rs"]` 内联回 executor.rs 的 #[cfg(test)] mod,
//! 不进 lib.rs、不改 pub 路径、不影响外部调用方。
//!
//! SW-01 TOCTOU 相关测试(test_cancelled_node_skips_set_failed /
//! test_cancelled_node_skips_set_completed / test_cancelled_node_emits_node_cancelled_event)
//! 原样保留,锁定 executor.rs:136/138-153 的 Ok/Err 分支 is_cancelled 短路行为
//! 原样保留,锁定 executor.rs 节点执行完毕后 Ok/Err 分支 is_cancelled 短路行为
//! (已取消节点跳过 set_completed/set_failed,保持 Cancelled 终态)。
use super::*;
use crate::node::{Node, NodeResult, NodeSchema};
@@ -312,3 +314,206 @@ async fn test_cancelled_node_emits_node_cancelled_event() {
NodeStatus::Cancelled
);
}
// ============================================================================
// conditions-eval feature:边条件路由测试(feature flag 开启时才编译)
// ============================================================================
/// 测试节点:把 config.ok 作为 bool 输出到 { "ok": <bool> }。
/// 边 condition "$.ok == true" 据此判定路由。
#[cfg(feature = "conditions-eval")]
struct BoolFlagNode {
ok: bool,
}
#[cfg(feature = "conditions-eval")]
#[async_trait]
impl Node for BoolFlagNode {
async fn execute(&self, _ctx: NodeContext) -> NodeResult {
Ok(NodeOutput::from_value(serde_json::json!({ "ok": self.ok })))
}
fn schema(&self) -> NodeSchema {
NodeSchema {
params: serde_json::Value::Null,
output: serde_json::Value::Null,
}
}
fn node_type(&self) -> &str {
"bool_flag"
}
}
/// 测试节点:执行时记录到共享 Mutex,用于断言节点是否被执行。
#[cfg(feature = "conditions-eval")]
struct RecordingNode {
flag: std::sync::Arc<std::sync::Mutex<Vec<String>>>,
name: String,
}
#[cfg(feature = "conditions-eval")]
#[async_trait]
impl Node for RecordingNode {
async fn execute(&self, _ctx: NodeContext) -> NodeResult {
self.flag.lock().unwrap().push(self.name.clone());
Ok(NodeOutput::empty())
}
fn schema(&self) -> NodeSchema {
NodeSchema {
params: serde_json::Value::Null,
output: serde_json::Value::Null,
}
}
fn node_type(&self) -> &str {
"recording"
}
}
/// conditions-eval 开启时:边 condition 求值 true → 下游执行。
///
/// 结构:branch(true) --condition "$.ok == true"--> target_run
/// branch(true) --condition "$.ok == false"--> target_skip
/// branch.ok==true:第一条边满足 → target_run 执行;第二条边 false → target_skip 跳过。
#[cfg(feature = "conditions-eval")]
#[tokio::test]
async fn conditions_eval_routes_by_edge_condition() {
let mut dag = Dag::new();
dag.add_node("branch".to_string(), Box::new(BoolFlagNode { ok: true }));
let ran = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
let ran_run = ran.clone();
let ran_skip = ran.clone();
dag.add_node(
"target_run".to_string(),
Box::new(RecordingNode { flag: ran_run, name: "target_run".to_string() }),
);
dag.add_node(
"target_skip".to_string(),
Box::new(RecordingNode { flag: ran_skip, name: "target_skip".to_string() }),
);
// 两条带条件的出边:branch.ok==true 命中第一条
dag.add_edge_with_condition(
"branch".to_string(),
"target_run".to_string(),
"$.ok == true".to_string(),
);
dag.add_edge_with_condition(
"branch".to_string(),
"target_skip".to_string(),
"$.ok == false".to_string(),
);
let mut executor = DagExecutor::new(EventBus::new(), "test-cond-route".to_string());
let outputs = executor
.run(&dag, serde_json::Value::Null)
.await
.expect("run");
// target_run 被执行(条件命中);target_skip 被跳过(条件不满足,不入 outputs)
assert!(
outputs.contains_key("target_run"),
"条件命中的下游应执行,outputs: {:?}",
outputs.keys().collect::<Vec<_>>()
);
assert!(
!outputs.contains_key("target_skip"),
"条件不满足的下游应跳过(不入 outputs),outputs: {:?}",
outputs.keys().collect::<Vec<_>>()
);
let ran = ran.lock().unwrap();
assert!(
ran.contains(&"target_run".to_string()),
"target_run 应被执行,ran: {:?}",
ran
);
assert!(
!ran.contains(&"target_skip".to_string()),
"target_skip 不应被执行,ran: {:?}",
ran
);
}
/// conditions-eval 开启时:节点所有入边均带 condition 且全部 false → 跳过执行。
/// 验证状态机保持 Pending(跳过节点不 set_running/Completed/Failed)。
#[cfg(feature = "conditions-eval")]
#[tokio::test]
async fn conditions_eval_skip_keeps_pending() {
use df_types::types::NodeStatus;
let mut dag = Dag::new();
dag.add_node("src".to_string(), Box::new(BoolFlagNode { ok: true }));
dag.add_node(
"skip_target".to_string(),
Box::new(RecordingNode {
flag: std::sync::Arc::new(std::sync::Mutex::new(Vec::new())),
name: "skip_target".to_string(),
}),
);
// src.ok==true,但边要求 == false → 不满足 → 跳过
dag.add_edge_with_condition(
"src".to_string(),
"skip_target".to_string(),
"$.ok == false".to_string(),
);
let mut executor = DagExecutor::new(EventBus::new(), "test-cond-skip".to_string());
let outputs = executor
.run(&dag, serde_json::Value::Null)
.await
.expect("run");
assert!(!outputs.contains_key("skip_target"), "skip_target 应被跳过不入 outputs");
// 跳过的节点保持 Pending(未被状态机转换)
assert_eq!(
executor.state_machine.get(&"skip_target".to_string()),
NodeStatus::Pending,
"跳过的节点应保持 Pending"
);
assert_eq!(
executor.state_machine.get(&"src".to_string()),
NodeStatus::Completed,
"源节点应正常完成"
);
}
/// conditions-eval 开启时:无条件入边 + 条件入边混合,无条件的总放行(不被跳过)。
#[cfg(feature = "conditions-eval")]
#[tokio::test]
async fn conditions_eval_unconditional_edge_always_passes() {
let mut dag = Dag::new();
dag.add_node("a".to_string(), Box::new(BoolFlagNode { ok: true }));
dag.add_node("b".to_string(), Box::new(BoolFlagNode { ok: false }));
dag.add_node(
"mix".to_string(),
Box::new(RecordingNode {
flag: std::sync::Arc::new(std::sync::Mutex::new(Vec::new())),
name: "mix".to_string(),
}),
);
// 一条无条件边(b → mix)+ 一条条件边(a → mix, 要求 a.ok==false 不满足)
dag.add_edge("b".to_string(), "mix".to_string());
dag.add_edge_with_condition(
"a".to_string(),
"mix".to_string(),
"$.ok == false".to_string(),
);
let mut executor = DagExecutor::new(EventBus::new(), "test-cond-mix".to_string());
let outputs = executor
.run(&dag, serde_json::Value::Null)
.await
.expect("run");
// 存在无条件入边 → mix 必执行(无条件边不被跳过逻辑计入 has_any_cond_edge 的「全部 false」)
assert!(
outputs.contains_key("mix"),
"存在无条件入边时节点应执行,outputs: {:?}",
outputs.keys().collect::<Vec<_>>()
);
}