oxios_kernel/approval/
policy.rs1use std::collections::HashMap;
8
9use serde::{Deserialize, Serialize};
10
11#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
13#[serde(rename_all = "lowercase")]
14pub enum ToolPolicy {
15 #[default]
17 Auto,
18 OnDemand,
20 Always,
22}
23
24impl ToolPolicy {
25 pub fn max(self, other: Self) -> Self {
27 use ToolPolicy::*;
28 match (self, other) {
29 (Always, _) | (_, Always) => Always,
30 (OnDemand, _) | (_, OnDemand) => OnDemand,
31 _ => Auto,
32 }
33 }
34}
35
36#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
38#[serde(rename_all = "kebab-case")]
39pub enum ApprovalMode {
40 #[default]
42 Manual,
43 AllowList,
45 AutoRun,
47}
48
49#[derive(Debug, Clone, Default, Serialize, Deserialize)]
51pub struct ApprovalConfig {
52 #[serde(default)]
54 pub mode: ApprovalMode,
55 #[serde(default)]
57 pub allow_list: Vec<String>,
58 #[serde(default)]
60 pub tool_overrides: HashMap<String, ToolPolicy>,
61}
62
63pub const DEFAULT_TOOL_POLICIES: &[(&str, ToolPolicy)] = &[
65 ("read", ToolPolicy::Auto),
66 ("ls", ToolPolicy::Auto),
67 ("grep", ToolPolicy::Auto),
68 ("find", ToolPolicy::Auto),
69 ("get_search_results", ToolPolicy::Auto),
70 ("write", ToolPolicy::OnDemand),
71 ("edit", ToolPolicy::OnDemand),
72 ("exec", ToolPolicy::OnDemand),
73 ("web_search", ToolPolicy::OnDemand),
74 ("browser", ToolPolicy::OnDemand),
75 ("mcp", ToolPolicy::OnDemand),
76 ("a2a_delegate", ToolPolicy::OnDemand),
77];
78
79#[cfg(test)]
80mod tests {
81 use super::*;
82
83 #[test]
84 fn max_returns_stronger_policy() {
85 assert_eq!(
86 ToolPolicy::Auto.max(ToolPolicy::OnDemand),
87 ToolPolicy::OnDemand
88 );
89 assert_eq!(
90 ToolPolicy::OnDemand.max(ToolPolicy::Auto),
91 ToolPolicy::OnDemand
92 );
93 assert_eq!(ToolPolicy::Auto.max(ToolPolicy::Always), ToolPolicy::Always);
94 assert_eq!(ToolPolicy::Always.max(ToolPolicy::Auto), ToolPolicy::Always);
95 assert_eq!(
96 ToolPolicy::OnDemand.max(ToolPolicy::Always),
97 ToolPolicy::Always
98 );
99 assert_eq!(ToolPolicy::Auto.max(ToolPolicy::Auto), ToolPolicy::Auto);
100 }
101
102 #[test]
103 fn default_tool_policies_cover_core_tools() {
104 let names: Vec<_> = DEFAULT_TOOL_POLICIES.iter().map(|(n, _)| *n).collect();
105 for required in ["read", "write", "edit", "exec", "web_search", "grep", "ls"] {
106 assert!(
107 names.contains(&required),
108 "missing default policy for {required}"
109 );
110 }
111 }
112
113 #[test]
114 fn approval_config_defaults_to_manual_empty() {
115 let c = ApprovalConfig::default();
116 assert_eq!(c.mode, ApprovalMode::Manual);
117 assert!(c.allow_list.is_empty());
118 assert!(c.tool_overrides.is_empty());
119 }
120
121 #[test]
122 fn approval_mode_serde_kebab_case() {
123 let s = serde_json::to_string(&ApprovalMode::AllowList).unwrap();
124 assert_eq!(s, "\"allow-list\"");
125 let m: ApprovalMode = serde_json::from_str("\"auto-run\"").unwrap();
126 assert_eq!(m, ApprovalMode::AutoRun);
127 }
128}