oxios_kernel/approval/
resolver.rs1use std::sync::Arc;
4
5use parking_lot::RwLock;
6use serde_json::Value;
7
8use super::policy::ToolPolicy;
9
10pub trait ToolPolicyResolver: Send + Sync {
14 fn resolve(&self, args: &Value) -> Option<ToolPolicy>;
15}
16
17pub trait GlobalResolver: Send + Sync {
21 fn resolve(&self, call: &super::gate::ToolCall<'_>) -> Option<ToolPolicy>;
22}
23
24pub struct ExecPolicyResolver {
27 pub allowed_commands: Arc<RwLock<Vec<String>>>,
29}
30
31impl ToolPolicyResolver for ExecPolicyResolver {
32 fn resolve(&self, args: &Value) -> Option<ToolPolicy> {
33 let mode = args.get("mode")?.as_str()?;
34 let command = args
35 .get("command")
36 .and_then(|v| v.as_str())
37 .or_else(|| args.get("binary").and_then(|v| v.as_str()))?;
38 let allowed = self.allowed_commands.read();
39 match mode {
40 "structured" if allowed.iter().any(|c| c == command) => Some(ToolPolicy::Auto),
41 _ => Some(ToolPolicy::OnDemand),
42 }
43 }
44}
45
46#[cfg(test)]
47mod tests {
48 use super::*;
49 use serde_json::json;
50
51 fn resolver(allowed: &[&str]) -> ExecPolicyResolver {
52 ExecPolicyResolver {
53 allowed_commands: std::sync::Arc::new(parking_lot::RwLock::new(
54 allowed.iter().map(|s| s.to_string()).collect(),
55 )),
56 }
57 }
58
59 #[test]
60 fn structured_allowed_binary_is_auto() {
61 let r = resolver(&["curl", "ls"]);
62 assert_eq!(
63 r.resolve(&json!({"mode": "structured", "binary": "curl"})),
64 Some(ToolPolicy::Auto)
65 );
66 }
67
68 #[test]
69 fn structured_unknown_binary_is_ondemand() {
70 let r = resolver(&["curl"]);
71 assert_eq!(
72 r.resolve(&json!({"mode": "structured", "binary": "rm"})),
73 Some(ToolPolicy::OnDemand)
74 );
75 }
76
77 #[test]
78 fn shell_mode_is_ondemand() {
79 let r = resolver(&["curl"]);
80 assert_eq!(
81 r.resolve(&json!({"mode": "shell", "command": "ls -la"})),
82 Some(ToolPolicy::OnDemand)
83 );
84 }
85
86 #[test]
87 fn missing_mode_returns_none() {
88 let r = resolver(&["curl"]);
89 assert_eq!(r.resolve(&json!({"binary": "curl"})), None);
90 }
91}