use std::path::Path;
use std::sync::atomic::{AtomicBool, Ordering};
static PANIC_ACTIVE: AtomicBool = AtomicBool::new(false);
pub fn activate_panic() {
PANIC_ACTIVE.store(true, Ordering::SeqCst);
}
pub fn deactivate_panic() {
PANIC_ACTIVE.store(false, Ordering::SeqCst);
}
pub fn is_panic_active() -> bool {
PANIC_ACTIVE.load(Ordering::SeqCst)
}
pub fn panic_policy() -> crate::HushSpec {
let yaml = include_str!("../rulesets/panic.yaml");
crate::HushSpec::parse(yaml).expect("panic policy must be valid")
}
pub fn check_panic_sentinel(path: impl AsRef<Path>) -> bool {
let exists = path.as_ref().exists();
if exists {
activate_panic();
}
exists
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Mutex;
static TEST_LOCK: Mutex<()> = Mutex::new(());
#[test]
fn activate_and_deactivate() {
let _guard = TEST_LOCK.lock().unwrap();
deactivate_panic();
assert!(!is_panic_active());
activate_panic();
assert!(is_panic_active());
deactivate_panic();
assert!(!is_panic_active());
}
#[test]
fn panic_policy_parses() {
let spec = panic_policy();
assert_eq!(spec.name.as_deref(), Some("__hushspec_panic__"));
}
#[test]
fn sentinel_file_activates_panic() {
let _guard = TEST_LOCK.lock().unwrap();
deactivate_panic();
let dir = std::env::temp_dir().join("hushspec_panic_test");
let sentinel = dir.join(".hushspec_panic");
let _ = std::fs::create_dir_all(&dir);
std::fs::write(&sentinel, "").unwrap();
assert!(check_panic_sentinel(&sentinel));
assert!(is_panic_active());
let _ = std::fs::remove_file(&sentinel);
deactivate_panic();
}
#[test]
fn sentinel_file_missing_does_not_activate() {
let _guard = TEST_LOCK.lock().unwrap();
deactivate_panic();
let path = std::env::temp_dir().join("hushspec_nonexistent_sentinel");
let _ = std::fs::remove_file(&path);
assert!(!check_panic_sentinel(&path));
assert!(!is_panic_active());
}
#[test]
fn panic_mode_denies_all_action_types() {
let _guard = TEST_LOCK.lock().unwrap();
deactivate_panic();
activate_panic();
let spec = panic_policy();
let action_types = [
"tool_call",
"egress",
"file_read",
"file_write",
"patch_apply",
"shell_command",
"computer_use",
"unknown_action",
];
for action_type in action_types {
let action = crate::EvaluationAction {
action_type: action_type.to_string(),
target: Some("anything".to_string()),
..Default::default()
};
let result = crate::evaluate(&spec, &action);
assert_eq!(
result.decision,
crate::Decision::Deny,
"expected deny for action type '{}' during panic mode",
action_type
);
assert_eq!(result.matched_rule.as_deref(), Some("__hushspec_panic__"));
assert_eq!(
result.reason.as_deref(),
Some("emergency panic mode is active")
);
}
deactivate_panic();
}
#[test]
fn deactivate_restores_normal_evaluation() {
let _guard = TEST_LOCK.lock().unwrap();
deactivate_panic();
let yaml = r#"
hushspec: "0.1.0"
name: "permissive"
"#;
let spec = crate::HushSpec::parse(yaml).unwrap();
let action = crate::EvaluationAction {
action_type: "tool_call".to_string(),
target: Some("some_tool".to_string()),
..Default::default()
};
let result = crate::evaluate(&spec, &action);
assert_eq!(result.decision, crate::Decision::Allow);
activate_panic();
let result = crate::evaluate(&spec, &action);
assert_eq!(result.decision, crate::Decision::Deny);
deactivate_panic();
let result = crate::evaluate(&spec, &action);
assert_eq!(result.decision, crate::Decision::Allow);
}
}