修复: 压缩边界对齐与断路器学习语义
- 压缩边界按对话单元回退,不再劈开工具对 - 停止与接管退出补跳过结果,畸形不再新增 - 失败指纹纳入参数,改参重试不再累计熔断 - 成功断连失败段,红绿循环不再误熔断 - 脚本非零退出豁免,同命令死磕由签名熔断兜底 - 熔断前加自纠警示轮,临时路径写删豁免
This commit is contained in:
@@ -323,26 +323,47 @@ pub(crate) fn normalize_loop_path(p: &str) -> String {
|
||||
p.replace('\\', "/").to_lowercase()
|
||||
}
|
||||
|
||||
/// 从工具调用提取回环样本:write_file/delete_file 取 path 参数规范化,其他工具 None 占位。
|
||||
/// path 缺失/非字符串 → None(无路径无从判同路径,退化为占位)。
|
||||
/// 从工具调用提取回环样本:write_file/delete_file 记规范化 path;read_file 记规范化 path
|
||||
/// (该路径的「使用」证据);run_command 记整条命令(判定时按「命令含路径」匹配使用);
|
||||
/// 其他工具 None 占位。path/command 缺失/非字符串 → None(无从判,退化为占位)。
|
||||
pub(crate) fn destructive_loop_sample(name: &str, args: &serde_json::Value) -> DestructiveLoopSample {
|
||||
match name {
|
||||
"write_file" | "delete_file" => {
|
||||
let p = arg_str(args, "path");
|
||||
if p.is_empty() { None } else { Some((name.to_string(), normalize_loop_path(&p))) }
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
let target = match name {
|
||||
"write_file" | "delete_file" | "read_file" => arg_str(args, "path"),
|
||||
"run_command" => arg_str(args, "command"),
|
||||
_ => return None,
|
||||
};
|
||||
if target.is_empty() { None } else { Some((name.to_string(), normalize_loop_path(&target))) }
|
||||
}
|
||||
|
||||
/// 破坏性回环检测:窗口内「write_file(P) 后 GAP 步内 delete_file(P)」或反向 → 命中路径。
|
||||
///
|
||||
/// GAP=6(全工具调用序位):写删交替紧凑才判回环,间隔远的写与删是正常迭代修复不算。
|
||||
/// 两类豁免(降误伤,治 .trash 临时脚本卫生流程被误熔断):
|
||||
/// - 路径命中临时/回收模式(.trash、/temp/、/tmp/、/target/、.cache 等):写完即删是
|
||||
/// 产物卫生,整对豁免;
|
||||
/// - 写删对之间有对该路径的使用(read 精确命中 / run 命令含路径):用后清理,该对豁免。
|
||||
/// 真「写完即删用户文件」(非临时路径且中间无使用)仍拦。
|
||||
/// 返回首个命中对的规范化路径(警示/熔断文本可引用),无命中 → None。
|
||||
pub(crate) fn detect_destructive_loop(samples: &[DestructiveLoopSample]) -> Option<String> {
|
||||
/// 回环对最大间隔步数(按全部工具调用序位)。
|
||||
const DESTRUCTIVE_LOOP_GAP: usize = 6;
|
||||
|
||||
// 临时/回收产物路径(入参须为 normalize_loop_path 后的形式:分隔符统一 /、小写)。
|
||||
let is_temp_recycle = |p: &str| -> bool {
|
||||
const MARKERS: &[&str] = &[".trash", "/temp/", "/tmp/", "/target/", ".cache"];
|
||||
MARKERS.iter().any(|m| p.contains(m))
|
||||
|| p.starts_with("temp/")
|
||||
|| p.starts_with("tmp/")
|
||||
};
|
||||
// 写删对之间对该路径的使用:read 精确命中;run 按命令文本含路径匹配。
|
||||
let is_use_between = |s: &DestructiveLoopSample, p: &str| -> bool {
|
||||
match s.as_ref() {
|
||||
Some((n, v)) if n == "read_file" => v == p,
|
||||
Some((n, v)) if n == "run_command" => !p.is_empty() && v.contains(p),
|
||||
_ => false,
|
||||
}
|
||||
};
|
||||
|
||||
for i in 0..samples.len() {
|
||||
let (na, pa) = match samples[i].as_ref() {
|
||||
Some(s) => s,
|
||||
@@ -353,8 +374,14 @@ pub(crate) fn detect_destructive_loop(samples: &[DestructiveLoopSample]) -> Opti
|
||||
"delete_file" => "write_file",
|
||||
_ => continue,
|
||||
};
|
||||
if is_temp_recycle(pa) {
|
||||
continue;
|
||||
}
|
||||
// j 限 (i, i+GAP]:紧凑间隔内找反向同路径操作。
|
||||
for j in (i + 1)..samples.len().min(i + 1 + DESTRUCTIVE_LOOP_GAP) {
|
||||
if is_use_between(&samples[j], pa) {
|
||||
break; // 对内有使用=用后清理,该写删对豁免
|
||||
}
|
||||
if let Some((nb, pb)) = samples[j].as_ref() {
|
||||
if pb == pa && nb == opposite {
|
||||
return Some(pa.clone());
|
||||
@@ -614,6 +641,45 @@ mod tests {
|
||||
assert_eq!(detect_destructive_loop(&samples), Some("p.txt".to_string()), "间隔 6 步 = GAP 应命中");
|
||||
}
|
||||
|
||||
/// 豁免一:临时/回收路径(.trash、/tmp/ 等)写删是产物卫生流程,非自毁,不判回环。
|
||||
#[test]
|
||||
fn loop_temp_recycle_path_exempted() {
|
||||
for p in [".trash/analyze.py", "C:/data/tmp/analyze.py", ".cache/scratch.txt"] {
|
||||
let samples = loop_samples_of(&[
|
||||
("write_file", serde_json::json!({"path": p, "content": "x"})),
|
||||
("delete_file", serde_json::json!({"path": p})),
|
||||
]);
|
||||
assert_eq!(detect_destructive_loop(&samples), None, "临时路径 {} 写删应豁免", p);
|
||||
}
|
||||
}
|
||||
|
||||
/// 豁免二:写删对之间有对该路径的成功使用(run/read)是用后清理,非自毁。
|
||||
#[test]
|
||||
fn loop_use_between_pair_not_detected() {
|
||||
let run_between = loop_samples_of(&[
|
||||
("write_file", serde_json::json!({"path": "scripts/a.py"})),
|
||||
("run_command", serde_json::json!({"command": "python scripts/a.py"})),
|
||||
("delete_file", serde_json::json!({"path": "scripts/a.py"})),
|
||||
]);
|
||||
assert_eq!(detect_destructive_loop(&run_between), None, "写后运行过再删是用后清理");
|
||||
let read_between = loop_samples_of(&[
|
||||
("write_file", serde_json::json!({"path": "scripts/a.py"})),
|
||||
("read_file", serde_json::json!({"path": "scripts/a.py"})),
|
||||
("delete_file", serde_json::json!({"path": "scripts/a.py"})),
|
||||
]);
|
||||
assert_eq!(detect_destructive_loop(&read_between), None, "写后读过再删是确认后清理");
|
||||
}
|
||||
|
||||
/// 真自毁仍拦:非临时路径写完即删,对内无任何使用。
|
||||
#[test]
|
||||
fn loop_bare_write_delete_still_detected() {
|
||||
let samples = loop_samples_of(&[
|
||||
("write_file", serde_json::json!({"path": "out.txt", "content": "x"})),
|
||||
("delete_file", serde_json::json!({"path": "out.txt"})),
|
||||
]);
|
||||
assert_eq!(detect_destructive_loop(&samples), Some("out.txt".to_string()), "无使用的写完即删仍判自毁");
|
||||
}
|
||||
|
||||
/// 场景样本不足:仅 3 个签名(即使全同)。判 false(不判)。
|
||||
#[test]
|
||||
fn scenario_insufficient_sample_not_repetitive() {
|
||||
|
||||
Reference in New Issue
Block a user