优化: 边界加固(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:
@@ -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)?;
|
||||
|
||||
Reference in New Issue
Block a user