新增: 无人值守P0任务边界全链
- 任务级授权边界判定与审批旁路单点插桩 - boundary_json 随会话落库(V45 可逆迁移) - 六路非正常终局统一安全停靠与托盘通知 - 每五轮进度事件与会话头部进度条 - 前端边界配置卡侧栏徽标及 UX 校验补齐
This commit is contained in:
@@ -296,15 +296,23 @@ fn match_task_audit_row(tool_name: &str, result: Option<&str>, task_id: &str) ->
|
||||
/// 故走专用 SELECT(与 state.rs 清理孤儿 pending 消息同款 app 层直查先例);SQL 里的工具名单
|
||||
/// 是 TASK_CONV_TOOLS 的硬拷贝,单测覆盖三个名字防漂移。
|
||||
///
|
||||
/// P2-3 task_id 预筛下沉 SQL:`result LIKE '%"id":"<task_id>"%'` 只作预筛(缩小扫描窗口,
|
||||
/// 参数化绑定防注入),精确判等仍在 Rust 侧 match_task_audit_row(解析 JSON 取顶层 id 字段
|
||||
/// 判等,「禁裸 LIKE 判等」不破)。预筛只会多放进相关行(嵌套 id/巧合同形文本),由 Rust
|
||||
/// 侧剔除;不会漏放——audit result 均由 serde_json 紧凑序列化落库(无空格),顶层 id 字段
|
||||
/// 必然以 `"id":"<id>"` 形态出现。若无预筛,LIMIT 窗口取的是全局最近 1000 行,高流量下
|
||||
/// 目标任务的关联行会被无关行挤出窗外(关联静默消失);预筛后窗口=含该任务 id 形文本的
|
||||
/// 最近 1000 行,早期关联仍可命中(有单测锁定)。
|
||||
///
|
||||
/// 过滤:① 命中行按会话去重(行按 requested_at 倒序,首见=最近一次);② 子代理会话
|
||||
/// (origin='sub_agent',V44)剔除,v1 只列主会话;③ 会话已不在库的 id 一并剔除
|
||||
/// (switchConversation 对不存在 id 会走"新建兜底",跳过去是空会话,不如不列)。
|
||||
/// LIMIT 1000 扫描上界:抽屉每次打开都查,封顶防超大审计表全表拖慢;取最近 1000 行,
|
||||
/// 更早的关联对"跳回最近上下文"场景无意义。
|
||||
/// LIMIT 1000 扫描上界:抽屉每次打开都查,封顶防超大审计表全表拖慢。
|
||||
pub(crate) async fn find_task_conversations(
|
||||
db: &Database,
|
||||
task_id: &str,
|
||||
) -> Result<Vec<TaskConversationRef>, String> {
|
||||
let pattern = format!("%\"id\":\"{}\"%", task_id);
|
||||
let conn = db.conn();
|
||||
let rows: Vec<(String, String, Option<String>, String)> =
|
||||
tokio::task::spawn_blocking(move || -> Result<Vec<(String, String, Option<String>, String)>, String> {
|
||||
@@ -315,11 +323,12 @@ pub(crate) async fn find_task_conversations(
|
||||
"SELECT conversation_id, tool_name, result, requested_at FROM ai_tool_executions \
|
||||
WHERE tool_name IN ('create_task','update_task','advance_task') \
|
||||
AND conversation_id IS NOT NULL \
|
||||
AND result LIKE ?1 \
|
||||
ORDER BY CAST(requested_at AS INTEGER) DESC LIMIT 1000",
|
||||
)
|
||||
.map_err(|e| e.to_string())?;
|
||||
let mapped = stmt
|
||||
.query_map([], |row| {
|
||||
.query_map([&pattern], |row| {
|
||||
Ok((
|
||||
row.get::<_, String>(0)?,
|
||||
row.get::<_, String>(1)?,
|
||||
@@ -590,6 +599,7 @@ mod tests {
|
||||
created_at: "1".to_string(),
|
||||
updated_at: "1".to_string(),
|
||||
origin: origin.map(|s| s.to_string()),
|
||||
boundary_json: None,
|
||||
})
|
||||
.await
|
||||
.expect("测试会话插入应成功");
|
||||
@@ -637,6 +647,36 @@ mod tests {
|
||||
assert_eq!(out[0].requested_at, (t - 10).to_string());
|
||||
}
|
||||
|
||||
/// P2-3 窗口外早期关联仍可命中:目标任务行之后灌 1000+ 条更新的无关任务工具行,
|
||||
/// 旧的「全局最近 1000 条」窗口会把早期关联挤出(静默消失);task_id 预筛下沉后,
|
||||
/// 窗口按预筛结果截取,早期关联仍须命中(精确判等仍在 Rust 侧,误命中不发生)。
|
||||
#[tokio::test]
|
||||
async fn find_task_conversations_early_hit_survives_window_flood() {
|
||||
let db = Database::open_in_memory().await.expect("in-memory db 初始化失败");
|
||||
let repo = AiToolExecutionRepo::new(&db);
|
||||
let t_early = 1_000_000_000_000i64;
|
||||
|
||||
insert_conversation(&db, "conv-early", Some("早期会话"), None).await;
|
||||
insert_conv_audit(&repo, "conv-early", "create_task",
|
||||
Some(r#"{"id":"t-early","title":"E"}"#.into()), t_early).await;
|
||||
// 灌 1100 条更新的无关任务工具行(其他任务 id),把全表推过 1000 行窗口
|
||||
let t_flood = t_early + 1_000_000;
|
||||
for i in 0..1100i64 {
|
||||
insert_conv_audit(&repo, "conv-noise", "update_task",
|
||||
Some(format!(r#"{{"id":"noise-{}"}}"#, i)), t_flood + i).await;
|
||||
}
|
||||
|
||||
let out = find_task_conversations(&db, "t-early").await.unwrap();
|
||||
assert_eq!(out.len(), 1, "早期关联不得被 1000 行窗口挤出");
|
||||
assert_eq!(out[0].conversation_id, "conv-early");
|
||||
assert_eq!(out[0].title.as_deref(), Some("早期会话"));
|
||||
// 预筛不放无关行进来:noise 任务查不到任何关联
|
||||
assert!(
|
||||
find_task_conversations(&db, "noise-1").await.unwrap().is_empty(),
|
||||
"noise 会话不在库,即使 id 前缀巧合也不得列出"
|
||||
);
|
||||
}
|
||||
|
||||
/// S4 纯函数判定表:名单外工具 / 非 JSON / 缺 id / id 不等 均不命中,精确判等才命中。
|
||||
#[test]
|
||||
fn match_task_audit_row_truth_table() {
|
||||
|
||||
Reference in New Issue
Block a user