use crate::governance::agent_action::AgentAction;
use crate::storage::GovernanceRefusal;
pub type WireCheckHook = Box<dyn Fn(&AgentAction) -> std::result::Result<(), String> + Send + Sync>;
pub static GOVERNANCE_PRE_ACTION: std::sync::OnceLock<WireCheckHook> = std::sync::OnceLock::new();
#[inline]
pub fn check(action: &AgentAction) -> std::result::Result<(), GovernanceRefusal> {
if let Some(hook) = GOVERNANCE_PRE_ACTION.get() {
if let Err(reason) = hook(action) {
return Err(GovernanceRefusal { reason });
}
}
Ok(())
}
#[inline]
pub fn check_anyhow(action: &AgentAction) -> anyhow::Result<()> {
if let Err(refusal) = check(action) {
return Err(anyhow::Error::new(refusal));
}
Ok(())
}
#[doc(hidden)]
#[cfg(test)]
pub fn install_for_test(hook: WireCheckHook) -> std::result::Result<(), ()> {
GOVERNANCE_PRE_ACTION.set(hook).map_err(|_| ())
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::PathBuf;
fn mock_dispatch(
hook: &WireCheckHook,
action: &AgentAction,
) -> std::result::Result<(), GovernanceRefusal> {
match hook(action) {
Ok(()) => Ok(()),
Err(reason) => Err(GovernanceRefusal { reason }),
}
}
fn refuse_sentinel_hook() -> WireCheckHook {
Box::new(|action: &AgentAction| match action {
AgentAction::Bash { command, .. } if command.contains("__refuse__") => {
Err("bash sentinel".to_string())
}
AgentAction::FilesystemWrite { path, .. }
if path.to_string_lossy().contains("__refuse__") =>
{
Err("fs sentinel".to_string())
}
AgentAction::NetworkRequest { host, .. } if host.contains("__refuse__") => {
Err("net sentinel".to_string())
}
AgentAction::ProcessSpawn { binary, .. } if binary.contains("__refuse__") => {
Err("spawn sentinel".to_string())
}
AgentAction::Custom { custom_kind, .. } if custom_kind.contains("__refuse__") => {
Err("custom sentinel".to_string())
}
_ => Ok(()),
})
}
#[test]
fn mock_dispatch_bash_refuse() {
let hook = refuse_sentinel_hook();
let action = AgentAction::Bash {
command: "echo __refuse__".into(),
cwd: None,
};
let err = mock_dispatch(&hook, &action).expect_err("expected refuse");
assert_eq!(err.reason, "bash sentinel");
assert!(format!("{err}").contains("governance-refused"));
}
#[test]
fn mock_dispatch_filesystem_write_refuse() {
let hook = refuse_sentinel_hook();
let action = AgentAction::FilesystemWrite {
path: PathBuf::from("/scratch/__refuse__.txt"),
byte_estimate: None,
};
let err = mock_dispatch(&hook, &action).expect_err("expected refuse");
assert_eq!(err.reason, "fs sentinel");
}
#[test]
fn mock_dispatch_network_request_refuse() {
let hook = refuse_sentinel_hook();
let action = AgentAction::NetworkRequest {
host: "__refuse__.example.com".into(),
scheme: "https".into(),
};
let err = mock_dispatch(&hook, &action).expect_err("expected refuse");
assert_eq!(err.reason, "net sentinel");
}
#[test]
fn mock_dispatch_process_spawn_refuse() {
let hook = refuse_sentinel_hook();
let action = AgentAction::ProcessSpawn {
binary: "__refuse__".into(),
args: vec!["build".into()],
};
let err = mock_dispatch(&hook, &action).expect_err("expected refuse");
assert_eq!(err.reason, "spawn sentinel");
}
#[test]
fn mock_dispatch_custom_refuse() {
let hook = refuse_sentinel_hook();
let action = AgentAction::Custom {
custom_kind: "__refuse__-deploy".into(),
payload: serde_json::json!({}),
};
let err = mock_dispatch(&hook, &action).expect_err("expected refuse");
assert_eq!(err.reason, "custom sentinel");
}
#[test]
fn mock_dispatch_allow_non_sentinel() {
let hook = refuse_sentinel_hook();
let actions = [
AgentAction::Bash {
command: "true".into(),
cwd: None,
},
AgentAction::FilesystemWrite {
path: PathBuf::from("/Users/x/safe.txt"),
byte_estimate: Some(0),
},
AgentAction::NetworkRequest {
host: "good.example.com".into(),
scheme: "https".into(),
},
AgentAction::ProcessSpawn {
binary: "cargo".into(),
args: vec![],
},
AgentAction::Custom {
custom_kind: "memory_write".into(),
payload: serde_json::json!({}),
},
];
for a in &actions {
assert!(
mock_dispatch(&hook, a).is_ok(),
"expected allow for {:?}",
a.kind()
);
}
}
#[test]
fn check_no_hook_branch_allow() {
if GOVERNANCE_PRE_ACTION.get().is_none() {
let action = AgentAction::Bash {
command: "ls".into(),
cwd: None,
};
assert!(check(&action).is_ok());
assert!(check_anyhow(&action).is_ok());
}
}
#[test]
fn check_anyhow_wraps_refusal_into_downcastable_error() {
let refusal = GovernanceRefusal {
reason: "unit test reason".to_string(),
};
let e = anyhow::Error::new(refusal);
let downcast = e
.downcast_ref::<GovernanceRefusal>()
.expect("downcast to GovernanceRefusal");
assert_eq!(downcast.reason, "unit test reason");
assert!(format!("{e}").contains("governance-refused"));
}
#[test]
fn install_for_test_idempotent_after_first_call() {
let first = install_for_test(Box::new(|_| Ok(())));
let second = install_for_test(Box::new(|_| Err("late".into())));
if first.is_ok() {
assert!(second.is_err(), "double-install must fail");
} else {
assert!(
second.is_err(),
"if first failed (already installed), second must also fail"
);
}
}
}