优化: 边界加固(AI loop竞态根治+数据/审批/反馈/并发/错误分类)

AI loop 竞态(P0):per-conv epoch/owner token + 存活心跳治 force_send 双loop + stop 3s兜底误判;旧loop stale 全跳过(guard/emit/save)

agentic 收尾(A2-B8):Fatal 退出落库user消息(镜像Exhausted)+ 入口早退补save + usage is_estimated 打标 + emit_ai_completed_once 单点收敛清审批残留

聊天清理(A2-B9):clearChat 先停loop→DB单事务→内存清(clear_conversation_atomic)+ 前端错误气泡

循环并发(A2-B11):三态 ProviderAcquire(NotConfigured/Acquired/Exhausted)+ 候选循环非阻塞+防抖3次饱和降级+单测

错误分类(A2-B12):stream error帧接入 classify_status_or_class + 关键词保守降级 + 7单测

数据(G1.2/G1.4):purge_with_descendants 级联补全(11表单事务+存在性守卫)+ move_task_queue 单事务收口(两调用方共用)

git只读(G3.1):run_git_status/diff/log success判定(exit_code差异语义,失败结构化{success:false,error})

安全(G5.2/G5.6):create_project 目录Err+name校验 + module.rs 路径遍历DRY(分段匹配修a..b.rs误伤)

幂等(V2/V32):裸ALTER全守卫化 + v1..v40全链重跑幂等测试(16过)

附:remote_bridge await 临时引用修(E0716)+ agentic emit 收敛 E0716 app_state 绑定修
This commit is contained in:
lxy
2026-08-05 22:10:32 +08:00
parent ec9f0bf1ea
commit 5667da6cf4
16 changed files with 1696 additions and 215 deletions
+70 -7
View File
@@ -414,22 +414,85 @@ impl ProjectRepo {
.map_err(storage_err)?
}
/// 彻底删除:事务级联删 branches→releases→tasks→projects(不可恢复)
/// 彻底删除:事务级联删全部关联子表→projects(不可恢复)
///
/// SQLite 已开 PRAGMA foreign_keys=ON 但表无 ON DELETE CASCADE,ALTER 改不了 FK 约束,
/// 故应用层级联:子表先于父表删,单事务保证一致性。
///
/// **级联范围**(G1.2 补全):V1-V35 全部 `REFERENCES projects(id)` 表——
/// branches / releases / tasks / workflow_executions(V2) + 知识图谱 V29-V35 新增
/// task_links / project_events / project_services / project_modules / module_dependencies,
/// 以及经它们间接到项目的 node_executions(REFERENCES workflow_executions)/
/// module_dependencies(REFERENCES project_modules)。此前只删四表,带 modules/事件/服务的
/// 工程 purge 在 foreign_keys=ON 下必 FK 违例回滚(latent bug)。
///
/// **删除顺序 = FK 拓扑 最深子表→父**(先删被引用方,防 foreign_keys=ON 下 FK 违例):
/// node_executions → task_links → branches → module_dependencies → workflow_executions
/// → tasks → project_events → project_services → project_modules → releases → projects。
///
/// 表存在性守卫:逐表先查 `sqlite_master` 存在才 DELETE(防老库缺 V29-V35 新表时
/// `no such table` 中断整个事务回滚)。
pub async fn purge_with_descendants(&self, id: &str) -> Result<bool> {
let conn = self.conn.clone();
let id = id.to_owned();
tokio::task::spawn_blocking(move || {
let mut guard = conn.blocking_lock();
let tx = guard.transaction().map_err(storage_err)?;
tx.execute("DELETE FROM branches WHERE project_id = ?1", params![id])
.map_err(storage_err)?;
tx.execute("DELETE FROM releases WHERE project_id = ?1", params![id])
.map_err(storage_err)?;
tx.execute("DELETE FROM tasks WHERE project_id = ?1", params![id])
.map_err(storage_err)?;
// (表名, 删除 SQL)。表名/SQL 均为编译期常量,无注入风险。
// node_executions / task_links 无 project_id 列,经其父表子查询收敛到本项目;
// workflow_executions 兼删 task_id 命中(project_id 可空,补齐 task_id 关联路径)。
const CASCADE: &[(&str, &str)] = &[
// node_executions REFERENCES workflow_executions(id):先于 workflow_executions 删
(
"node_executions",
"DELETE FROM node_executions WHERE workflow_id IN \
(SELECT id FROM workflow_executions WHERE project_id = ?1 \
OR task_id IN (SELECT id FROM tasks WHERE project_id = ?1))",
),
// task_links REFERENCES tasks(id):先于 tasks 删(source/target 任一端属本项目)
(
"task_links",
"DELETE FROM task_links WHERE source_id IN \
(SELECT id FROM tasks WHERE project_id = ?1) OR target_id IN \
(SELECT id FROM tasks WHERE project_id = ?1)",
),
// branches REFERENCES tasks(id) + projects(id):先于 tasks 删
("branches", "DELETE FROM branches WHERE project_id = ?1"),
// module_dependencies REFERENCES project_modules(id) + projects(id):先于 project_modules 删
("module_dependencies", "DELETE FROM module_dependencies WHERE project_id = ?1"),
// workflow_executions REFERENCES projects(id)(可空,兼删 task_id 关联)
(
"workflow_executions",
"DELETE FROM workflow_executions WHERE project_id = ?1 \
OR task_id IN (SELECT id FROM tasks WHERE project_id = ?1)",
),
// tasks REFERENCES projects(id)(自引用 parent_id 同语句删,单语句末校验不违例)
("tasks", "DELETE FROM tasks WHERE project_id = ?1"),
// 以下直接 REFERENCES projects(id),无子表依赖,顺序任意
("project_events", "DELETE FROM project_events WHERE project_id = ?1"),
("project_services", "DELETE FROM project_services WHERE project_id = ?1"),
("project_modules", "DELETE FROM project_modules WHERE project_id = ?1"),
("releases", "DELETE FROM releases WHERE project_id = ?1"),
];
for (table, sql) in CASCADE {
// 表存在性守卫:老库可能缺 V29-V35 新表,缺表时 DELETE 报 no such table
// 中断事务回滚;探测存在才删,防老库 purge 失败。
let exists: bool = tx
.query_row(
"SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?1",
params![*table],
|_| Ok(true),
)
.optional()
.map_err(storage_err)?
.unwrap_or(false);
if exists {
tx.execute(sql, params![id]).map_err(storage_err)?;
}
}
let affected = tx
.execute("DELETE FROM projects WHERE id = ?1", params![id])
.map_err(storage_err)?;