新增: 批次工作落地(推进链/评估闭环/事件总线/并发/加固) + 技术债清理 + 文档整理
后端: - 工作流推进链(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:
@@ -92,7 +92,7 @@ impl Node for AiNode {
|
||||
});
|
||||
|
||||
// 决策 a(AiNode 自审闭环):有 task_id 时落产出到 task.output_json。
|
||||
// 走通用 update_field(output_json 已在 tasks 白名单,crud.rs:342-346),
|
||||
// 走通用 update_field(output_json 已在 tasks 白名单,crud/mod.rs update_field 宏(impl_repo!)),
|
||||
// 非 status 状态机收口字段,合法可写。落库失败不阻断节点返回(产出已在内存,工作流可继续)。
|
||||
if let Some(task_id) = ctx.config.get("task_id").and_then(|v| v.as_str()) {
|
||||
if let Ok(json_str) = serde_json::to_string(&output) {
|
||||
|
||||
@@ -60,7 +60,7 @@ pub(crate) struct ResolvedProvider {
|
||||
/// 2. **老路径兼容(过渡)**:config 显式含 `base_url` + `api_key` 明文(老调用方)→ 走原路径
|
||||
/// + `tracing::warn!`(明文 api_key 经 config 注入已废弃)。兼容期保留,后续移除。
|
||||
/// 3. **空兜底**:无 provider_id 也无明文 → 取 `is_default=true` 首条 provider(对齐
|
||||
/// src-tauri commands/idea.rs:265-279 build_default_provider 模式),走同 resolve+ensure 链。
|
||||
/// src-tauri commands/idea.rs build_default_provider 模式),走同 resolve+ensure 链。
|
||||
/// 无任何 provider → 友好错误「未配置 AI Provider」。
|
||||
///
|
||||
/// model:config["model"] 非空用之,否则 record.default_model,再否则 "gpt-4o-mini" 占位。
|
||||
|
||||
@@ -11,7 +11,7 @@ use df_types::events::{SelectType, WorkflowEvent};
|
||||
|
||||
// 抽离的纯函数/常量(glob 引入,tests `use super::*` 间接可见)
|
||||
#[allow(unused_imports)]
|
||||
use crate::human_node_helpers::{contains_reject, is_reject_decision, REJECT_KEYWORDS};
|
||||
use crate::human_node_helpers::{contains_reject, is_reject_decision};
|
||||
|
||||
/// 人工审批节点(阻塞节点)
|
||||
pub struct HumanNode;
|
||||
@@ -286,7 +286,7 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn request_is_emitted_to_bus() {
|
||||
// B-03b-R8: 验证 HumanNode 真发出 HumanApprovalRequest 到事件总线
|
||||
// (R6 修复前 :41 send 缺 await → Request 未进 channel,此测会超时失败)
|
||||
// (R6 修复前 execute 内 event_bus.send().await 缺 await → Request 未进 channel,此测会超时失败)
|
||||
let bus = EventBus::new();
|
||||
let mut rx = bus.subscribe(); // subscribe 先于 execute send(broadcast 不回放)
|
||||
let ctx = make_ctx(&bus, "exec-1", "node-h", json!({ "timeout_secs": 1 }));
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
pub mod ai_node;
|
||||
pub mod ai_self_review_node;
|
||||
#[allow(dead_code)]
|
||||
mod ai_node_helpers;
|
||||
pub mod human_node;
|
||||
mod human_node_helpers;
|
||||
|
||||
@@ -376,7 +376,9 @@ mod tests {
|
||||
//
|
||||
// workflow.rs 的 spawn 闭包回调依赖三段纯计算:
|
||||
// 1. template_for(target) 选模板(已由 task_workflow_templates.rs 测试覆盖)
|
||||
// 2. regression_target(target) 推失败退回态(workflow.rs 私有,此处镜像锁定契约)
|
||||
// 2. regression_target(target) 推失败退回态(已收敛到 task_state_machine::regression_target
|
||||
// pub 契约,workflow.rs 与本测试均从此导入,单一可信源;映射表由 task_state_machine
|
||||
// 测试模块锁定)
|
||||
// 3. advance_task_atomic(repo, tid, to) 落库(本模块核心,下方覆盖各回调 to)
|
||||
//
|
||||
// workflow.rs spawn 闭包集成测试需 mock AppState/AppHandle/EventBus,df-nodes crate
|
||||
@@ -384,60 +386,23 @@ mod tests {
|
||||
// 唯一未覆盖的是「闭包组装 + tracing」胶水代码(非业务逻辑)。
|
||||
// ============================================================
|
||||
|
||||
/// 镜像 workflow.rs::regression_target(workflow.rs L44-54 私有函数)的失败退回映射。
|
||||
///
|
||||
/// 此处是契约镜像(非导入):若 workflow.rs 改映射而忘同步,这里会红,提醒两处一致。
|
||||
/// 映射:testing→in_review / in_review→in_progress / in_progress→None / 其他→None。
|
||||
///
|
||||
/// 为什么 in_progress → None? 状态机禁止 in_progress→todo
|
||||
/// (task_state_machine.rs backward_to_todo_rejected),失败退回 todo 会被
|
||||
/// advance_task_atomic 的 InvalidState 拦截;改为 None 则回调跳过推进,
|
||||
/// 留 in_progress 等人介入(决策 CR-13-O1-b)。
|
||||
///
|
||||
/// 为什么不 pub regression_target 复用? 它是 workflow.rs(app crate)的私有函数,
|
||||
/// df-nodes 不依赖 app crate(单向依赖:app → df-nodes)。把退回映射当 df-nodes 的
|
||||
/// 业务契约之一镜像锁定,符合「推进链业务逻辑落 df-nodes」(D-260616-03)定位。
|
||||
fn regression_target(target: &str) -> Option<&'static str> {
|
||||
match target {
|
||||
"testing" => Some("in_review"),
|
||||
"in_review" => Some("in_progress"),
|
||||
// in_progress 失败不自动退回(状态机不允许→todo),留 in_progress 等人介入
|
||||
"in_progress" => None,
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// 镜像 workflow.rs spawn 闭包的回调决策(completed→target / failed→regression_target)。
|
||||
/// 抽出纯函数便于直接断言,与 workflow.rs L267-273 的 match 逻辑一一对应。
|
||||
/// 抽出纯函数便于直接断言,与 workflow.rs 闭包内 match 逻辑一一对应。
|
||||
///
|
||||
/// regression_target 现已收敛为 df-nodes::task_state_machine::regression_target 的
|
||||
/// pub 契约(单一可信源),此处直接复用,不再镜像副本(防 DRY 漂移)。
|
||||
///
|
||||
/// 返回 Option<String>(非 &str):completed 分支回传入的 target_status(非 'static),
|
||||
/// failed 分支回 regression_target 的常量。统一 String 避免生命周期冲突。
|
||||
/// failed 分支回 regression_target 的 &'static str 常量。统一 String 避免生命周期冲突。
|
||||
fn callback_advance_target(status: &str, target_status: &str) -> Option<String> {
|
||||
match status {
|
||||
"completed" => Some(target_status.to_string()),
|
||||
"failed" => regression_target(target_status).map(String::from),
|
||||
"failed" => crate::task_state_machine::regression_target(target_status)
|
||||
.map(String::from),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn regression_target_mapping_matches_contract() {
|
||||
// 锁定 workflow.rs ②-4 失败退回映射表(逐项)。
|
||||
assert_eq!(regression_target("testing"), Some("in_review"));
|
||||
assert_eq!(regression_target("in_review"), Some("in_progress"));
|
||||
// CR-13-O1-b: in_progress 失败不退回(状态机禁止→todo),留 in_progress 等人介入
|
||||
assert_eq!(regression_target("in_progress"), None);
|
||||
// done 是终态前向目标,失败无可退态(workflow.rs 注释:done→None)
|
||||
assert_eq!(regression_target("done"), None);
|
||||
// todo 是起点态无可退;blocked/cancelled 非推进链目标 → None
|
||||
assert_eq!(regression_target("todo"), None);
|
||||
assert_eq!(regression_target("blocked"), None);
|
||||
assert_eq!(regression_target("cancelled"), None);
|
||||
// 未知态防御性 → None
|
||||
assert_eq!(regression_target("merged"), None);
|
||||
assert_eq!(regression_target(""), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn callback_completed_advances_to_target() {
|
||||
// ②-3:工作流 completed → 推进到 target_status(无论 target 是哪个)
|
||||
|
||||
@@ -122,6 +122,31 @@ pub fn is_regression(from: &str, to: &str) -> bool {
|
||||
matches!((from, to), (IN_REVIEW, IN_PROGRESS) | (TESTING, IN_REVIEW))
|
||||
}
|
||||
|
||||
/// 工作流联动任务「失败退一步」的目标态映射(F-260616-06 ②-4)。
|
||||
///
|
||||
/// 工作流失败时,任务不应停留在失败前向目标态,需回退到上一闸门重做。映射表:
|
||||
/// - testing → in_review(测试失败退回重审)
|
||||
/// - in_review → in_progress(审查失败退回修改)
|
||||
/// - in_progress → None(状态机禁止 in_progress→todo,见 [`can_transition`] 的
|
||||
/// backward_to_todo_rejected;失败退回 todo 会被 advance_task_atomic
|
||||
/// 的 InvalidState 拦截,任务原地保留 in_progress 无信号。改为 None
|
||||
/// 则回调跳过推进,留 in_progress 等人介入 — 决策 CR-13-O1-b)
|
||||
/// - 其他(done/blocked/cancelled/todo 等非推进链前向目标) → None(无退回映射)
|
||||
///
|
||||
/// 设计归属:此映射属状态机业务契约(失败退一步的目标态),居 df-nodes 与
|
||||
/// [`can_transition`] / [`is_regression`] 同位,统一管理推进链的状态语义。
|
||||
/// 此前该函数在 workflow.rs(app crate 私有) 与 task_advance_node.rs(测试镜像)
|
||||
/// 各存一份(DRY 漂移),现收敛为单一可信源,调用方均从此导入。
|
||||
pub fn regression_target(target: &str) -> Option<&'static str> {
|
||||
match target {
|
||||
TESTING => Some(IN_REVIEW),
|
||||
IN_REVIEW => Some(IN_PROGRESS),
|
||||
// in_progress 失败不自动退回(状态机不允许→todo),留 in_progress 等人介入
|
||||
IN_PROGRESS => None,
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 单元测试 — 转换矩阵 + 边界
|
||||
// ============================================================
|
||||
@@ -231,6 +256,44 @@ mod tests {
|
||||
assert!(!is_regression(TESTING, IN_PROGRESS));
|
||||
}
|
||||
|
||||
// ---------- regression_target(失败退一步映射) ----------
|
||||
|
||||
#[test]
|
||||
fn regression_target_mapping_matches_contract() {
|
||||
// 锁定 ②-4 失败退回映射表(逐项)
|
||||
assert_eq!(regression_target(TESTING), Some(IN_REVIEW));
|
||||
assert_eq!(regression_target(IN_REVIEW), Some(IN_PROGRESS));
|
||||
// CR-13-O1-b: in_progress 失败不退回(状态机禁止→todo),留 in_progress 等人介入
|
||||
assert_eq!(regression_target(IN_PROGRESS), None);
|
||||
// done 是终态前向目标,失败无可退态 → None
|
||||
assert_eq!(regression_target(DONE), None);
|
||||
// todo 是起点态无可退;blocked/cancelled 非推进链目标 → None
|
||||
assert_eq!(regression_target(TODO), None);
|
||||
assert_eq!(regression_target(BLOCKED), None);
|
||||
assert_eq!(regression_target(CANCELLED), None);
|
||||
// 未知态防御性 → None
|
||||
assert_eq!(regression_target("merged"), None);
|
||||
assert_eq!(regression_target(""), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn regression_target_returned_state_is_valid_constant() {
|
||||
// 防漂移的真正增量(上例已逐项锁值,本例锁性质):
|
||||
// 任何非 None 返回值都必须是 ALL_STATES 内的合法状态常量。
|
||||
// 一旦 regression_target 误返回非状态字符串(拼写漂移 / 拼了历史态如 "merged"),
|
||||
// 该字符串过不了 is_valid_state,本测试立即失败定位。
|
||||
// 覆盖全部 7 态入参,不依赖上例已锁定的具体期望值。
|
||||
for target in ALL_STATES {
|
||||
match regression_target(target) {
|
||||
Some(ret) => assert!(
|
||||
is_valid_state(ret),
|
||||
"regression_target({target:?}) 返回 {ret:?} 不是合法状态常量(ALL_STATES 漂移)"
|
||||
),
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- is_valid_state ----------
|
||||
|
||||
#[test]
|
||||
@@ -253,4 +316,47 @@ mod tests {
|
||||
fn all_states_has_seven_entries() {
|
||||
assert_eq!(ALL_STATES.len(), 7);
|
||||
}
|
||||
|
||||
// ---------- 双源一致性校验 ----------
|
||||
//
|
||||
// task_state_machine 维护一份独立的 7 态字符串常量集(本模块 ALL_STATES / TODO / ...),
|
||||
// 与 df-types::TaskStatus::as_str / valid_values() 同语义但不复用枚举(独立模块定位,
|
||||
// 推进链判定不耦合存储枚举)。两源无编译期绑定,若任一处改拼写或增删状态值,
|
||||
// 状态机判定会与数据库 status 列存值静默脱节(按常量判合法但落库值对不上)。
|
||||
//
|
||||
// 此处锁定「值必须严格一致」契约:ALL_STATES 必须与 df-types::TaskStatus::valid_values()
|
||||
// 逐项相等(顺序与值)。df-nodes 单向依赖 df-types,无环依赖风险。一旦未来某方改动
|
||||
// 导致两源漂移,本测试立即失败并精确定位,避免运行时静默脱节。
|
||||
|
||||
#[test]
|
||||
fn all_states_matches_df_types_valid_values() {
|
||||
use df_types::types::TaskStatus;
|
||||
let expected: &[&str] = TaskStatus::valid_values();
|
||||
// 逐项比对(顺序敏感):位置 i 的值必须与 df-types 一致,失败时报具体索引便于定位
|
||||
assert_eq!(
|
||||
ALL_STATES.len(),
|
||||
expected.len(),
|
||||
"ALL_STATES 与 TaskStatus::valid_values 长度不一致(状态值数量漂移)"
|
||||
);
|
||||
for (i, (got, want)) in ALL_STATES.iter().zip(expected.iter()).enumerate() {
|
||||
assert_eq!(
|
||||
got, want,
|
||||
"ALL_STATES[{i}] = {got:?} 与 TaskStatus::valid_values()[{i}] = {want:?} 不一致(状态值漂移)"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn each_state_constant_matches_task_status_as_str() {
|
||||
// 逐变体锁定:常量值必须与对应 TaskStatus 变体的 as_str 产出严格相等。
|
||||
// 比上面整表比对更精细,单点漂移能直接指向具体常量行,加速定位。
|
||||
use df_types::types::TaskStatus;
|
||||
assert_eq!(TODO, TaskStatus::Todo.as_str());
|
||||
assert_eq!(IN_PROGRESS, TaskStatus::InProgress.as_str());
|
||||
assert_eq!(IN_REVIEW, TaskStatus::InReview.as_str());
|
||||
assert_eq!(TESTING, TaskStatus::Testing.as_str());
|
||||
assert_eq!(DONE, TaskStatus::Done.as_str());
|
||||
assert_eq!(BLOCKED, TaskStatus::Blocked.as_str());
|
||||
assert_eq!(CANCELLED, TaskStatus::Cancelled.as_str());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user