- B5 引擎: 看板轮询自动领单(G2 门禁 fail-closed, 领单原子写), parked 自动恢复优先, 收敛回读续作, 3 次熔断自停, KV 配置默认关
- 引擎配套: 提交冻结同步(df-commit-frozen), 工具结果外载过期清理, 工具失败快速记忆, 自监控快照飞书推送(df-monitor-chat-id)
- 领单判据收紧: 正文以 🔴 开头才判高危(正文提及字样不误判)
- 28 注入末轮竞态: 补充指令落末轮时顺延轮次预算(封顶 8)
- 停靠卡继续伪失败根修: ai_continue_loop 非 active 时分流 parked 恢复内核(recover_parked_inner 单一事实源)
- 实测: 写板 70 秒自动领单, 领单→执行→销账→回读→领下一单 全链闭环, 2050 测试过
192 lines
7.8 KiB
Rust
192 lines
7.8 KiB
Rust
//! 提交冻结机械闸(39 治理) — AI 引擎派发命令的 git commit 拦截。
|
|
//!
|
|
//! 冻结期间(用户 🔒 令)禁止引擎/挂机会话执行 git commit:命令在 spawn 前被拒,
|
|
//! 改动留在工作区。用户本人终端不受影响(本闸只作用于引擎 execute 入口)。
|
|
//!
|
|
//! 冻结标志解析(优先级:环境变量 DEVFLOW_COMMIT_FROZEN > env 内 DevFlow 标志):
|
|
//! - env::var("DEVFLOW_COMMIT_FROZEN")="1"/"true" → 冻结
|
|
//! - ShellRequest.env 同名键同样生效(测试/调用方定向注入)
|
|
//! 默认不冻结(用户本人正常提交流程零影响)。
|
|
//!
|
|
//! 拦截范围:命令子串含 `git commit`(含 `git -C x commit`)即拒;`git commit --amend`
|
|
//! 同拒。`git log/diff/status/add/push` 不拦(只冻结提交动作)。
|
|
//!
|
|
//! 提示文案给 LLM 指明出路:改留工作区,等「攒批提交通知」。
|
|
|
|
use std::sync::atomic::{AtomicBool, Ordering};
|
|
|
|
use crate::shell::ShellRequest;
|
|
|
|
/// 进程级冻结标志(引擎侧写入:unattended 轮询每轮按看板 🔒 行 + KV 覆盖同步)。
|
|
/// 用静态位而非 std::env::set_var(Rust 2024 edition set_var unsafe 且多线程 UB)。
|
|
static FROZEN: AtomicBool = AtomicBool::new(false);
|
|
|
|
/// 引擎侧设置/解除冻结(unattended 轮询每轮调用)。
|
|
pub fn set_frozen(v: bool) {
|
|
FROZEN.store(v, Ordering::SeqCst);
|
|
}
|
|
|
|
/// 环境变量真值判定:"1"/"true"(含大小写变体)为冻结,其余("0"/"false"/空)为不冻结。
|
|
fn env_val_is_truthy(v: &str) -> bool {
|
|
matches!(v, "1" | "true" | "TRUE" | "True")
|
|
}
|
|
|
|
/// 是否启用冻结:静态标志(引擎同步) 或 环境变量 DEVFLOW_COMMIT_FROZEN(调用方/测试定向注入)。
|
|
pub fn is_frozen(extra_env: Option<&std::collections::HashMap<String, String>>) -> bool {
|
|
if FROZEN.load(Ordering::SeqCst) {
|
|
return true;
|
|
}
|
|
if let Some(v) = extra_env.and_then(|m| m.get("DEVFLOW_COMMIT_FROZEN")) {
|
|
return env_val_is_truthy(v);
|
|
}
|
|
std::env::var("DEVFLOW_COMMIT_FROZEN")
|
|
.map(|v| env_val_is_truthy(&v))
|
|
.unwrap_or(false)
|
|
}
|
|
|
|
/// 命令是否为 git commit 提交动作(token 判据,含 -C 前缀形态与复合命令)。
|
|
pub fn is_commit_command(command: &str) -> bool {
|
|
// 按常见命令分隔符切段( ; && || | 换行),每段独立判 git commit(防 "x; git commit" 绕过)
|
|
command
|
|
.split(|c: char| matches!(c, ';' | '|' | '&' | '\n'))
|
|
.any(is_commit_segment)
|
|
}
|
|
|
|
/// 单段判据:token 找 "git",跳过全局选项(-C path 等)后是否接 "commit"。
|
|
fn is_commit_segment(segment: &str) -> bool {
|
|
let tokens: Vec<String> = segment
|
|
.split_whitespace()
|
|
.map(|t| t.trim_matches(|c: char| c == '"' || c == '\'').to_lowercase())
|
|
.collect();
|
|
for (i, t) in tokens.iter().enumerate() {
|
|
if t == "git" {
|
|
let mut j = i + 1;
|
|
while j < tokens.len() {
|
|
match tokens[j].as_str() {
|
|
// `-c key=val` 两 token 形态:值在下一 token,跳 2
|
|
"-c" => j += 2,
|
|
// `--git-dir=path`/`--work-tree=path` 单 token 合并形态:自含值,跳 1
|
|
t if t.starts_with("--git-dir=") || t.starts_with("--work-tree=") => j += 1,
|
|
// `--git-dir path`/`--work-tree path` 两 token 形态:跳 2
|
|
"--git-dir" | "--work-tree" => j += 2,
|
|
"commit" => return true,
|
|
_ => break,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
false
|
|
}
|
|
|
|
/// 冻结闸检查入口:冻结在场且命令是 git commit → 返回拒绝 Err(不 spawn)。
|
|
pub fn check(request: &ShellRequest) -> Result<(), String> {
|
|
if !is_frozen(Some(&request.env)) {
|
|
return Ok(());
|
|
}
|
|
if is_commit_command(&request.command) {
|
|
return Err(format!(
|
|
"[提交冻结令] 检测到 git commit 命令,当前处于提交冻结期(用户 🔒 令),已拒绝执行: \
|
|
改动保留在工作区,等用户「攒批提交通知」后才可提交。命令: {}",
|
|
truncate_cmd(&request.command)
|
|
));
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn truncate_cmd(s: &str) -> String {
|
|
if s.chars().count() <= 120 {
|
|
s.to_string()
|
|
} else {
|
|
let cut: String = s.chars().take(120).collect();
|
|
format!("{}…", cut)
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use std::collections::HashMap;
|
|
|
|
fn req(cmd: &str) -> ShellRequest {
|
|
ShellRequest {
|
|
command: cmd.to_string(),
|
|
working_dir: None,
|
|
env: HashMap::new(),
|
|
timeout_secs: None,
|
|
shell_type: None,
|
|
}
|
|
}
|
|
|
|
fn req_env(cmd: &str, env: &[(&str, &str)]) -> ShellRequest {
|
|
let mut r = req(cmd);
|
|
r.env = env
|
|
.iter()
|
|
.map(|(k, v)| (k.to_string(), v.to_string()))
|
|
.collect();
|
|
r
|
|
}
|
|
|
|
#[test]
|
|
fn commit_detection_covers_variants() {
|
|
assert!(is_commit_command("git commit -m 'x'"));
|
|
assert!(is_commit_command("git -C E:/repo commit -m x"));
|
|
assert!(is_commit_command("git commit --amend"));
|
|
assert!(is_commit_command("git commit -m y"));
|
|
assert!(is_commit_command("git commit; git push"));
|
|
// PowerShell / sh 包裹形态
|
|
assert!(is_commit_command("powershell -Command git commit -m x"));
|
|
assert!(is_commit_command("cd repo && git commit -m x"));
|
|
assert!(is_commit_command("git add . && git commit -m x"));
|
|
}
|
|
|
|
#[test]
|
|
fn non_commit_git_not_blocked() {
|
|
assert!(!is_commit_command("git log -1"));
|
|
assert!(!is_commit_command("git status"));
|
|
assert!(!is_commit_command("git diff --stat"));
|
|
assert!(!is_commit_command("git add ."));
|
|
assert!(!is_commit_command("git push origin main"));
|
|
assert!(!is_commit_command("cargo test"));
|
|
// commitment 等误命中防护:commit 后无后续 token 才算
|
|
assert!(!is_commit_command("git log --grep commit"));
|
|
}
|
|
|
|
#[test]
|
|
fn freeze_gate_blocks_commit_when_frozen() {
|
|
let r = req_env("git commit -m '细项批'", &[("DEVFLOW_COMMIT_FROZEN", "1")]);
|
|
assert!(check(&r).is_err(), "冻结在场 commit 必须被拒");
|
|
let r2 = req_env("git log -1", &[("DEVFLOW_COMMIT_FROZEN", "1")]);
|
|
assert!(check(&r2).is_ok(), "冻结只拦 commit 不拦只读命令");
|
|
}
|
|
|
|
/// 解冻态放行用例:持与 static_flag_toggle 同一把串行闸——静态位是进程级单源,
|
|
/// 置位瞬间并行跑到本用例会误判冻结(实测互踩),凡依赖「静态位=false」断言的用例一律持闸。
|
|
#[test]
|
|
fn no_freeze_no_block() {
|
|
let _gate = crate::shell::PWSH_CACHE_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
|
|
let r = req("git commit -m '用户本人提交'");
|
|
assert!(check(&r).is_ok(), "无冻结标志零影响(用户正常提交不受影响)");
|
|
}
|
|
|
|
#[test]
|
|
fn frozen_false_value_no_block() {
|
|
let _gate = crate::shell::PWSH_CACHE_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
|
|
let r = req_env("git commit -m x", &[("DEVFLOW_COMMIT_FROZEN", "0")]);
|
|
assert!(check(&r).is_ok(), "显式关闭标志不拦(解冻态)");
|
|
}
|
|
|
|
/// 静态标志(引擎同步路径)切换:置位拦 commit,复位恢复。
|
|
/// 串行闸:静态位是进程级单源,置位期间并行的 env 用例(check 走 is_frozen 静态位短路)会互踩,
|
|
/// 持锁串行 + 进出恢复默认(false)防污染。
|
|
#[test]
|
|
fn static_flag_toggle() {
|
|
// 复用 PWSH_CACHE_TEST_LOCK 串行闸(进程级互斥,专用闸后续有需要再单立)
|
|
let _gate = crate::shell::PWSH_CACHE_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
|
|
set_frozen(true);
|
|
assert!(check(&req("git commit -m x")).is_err(), "静态冻结位拦 commit");
|
|
assert!(check(&req("git diff")).is_ok(), "静态冻结位不拦只读命令");
|
|
set_frozen(false);
|
|
assert!(check(&req("git commit -m x")).is_ok(), "复位后恢复放行");
|
|
}
|
|
}
|