oxios_kernel/capability/
resolve.rs1use crate::types::AgentId;
21
22use super::template::CapabilityTemplate;
23use super::types::CSpace;
24
25const ROLE_WORKER: &str = "worker";
27const ROLE_STANDARD: &str = "standard";
28const ROLE_OPERATOR: &str = "operator";
29const ROLE_SUPERVISOR: &str = "supervisor";
30
31pub fn resolve_cspace(
49 cspace_hint: Option<&str>,
50 persona_role: Option<&str>,
51 default_template: Option<&str>,
52 agent_id: AgentId,
53) -> CSpace {
54 if let Some(hint) = cspace_hint {
56 let trimmed = hint.trim();
57 if !trimmed.is_empty() {
58 return resolve_from_template_name(trimmed, agent_id);
59 }
60 }
61
62 if let Some(role) = persona_role {
64 let trimmed = role.trim().to_lowercase();
65 if !trimmed.is_empty() {
66 return resolve_from_template_name(&trimmed, agent_id);
67 }
68 }
69
70 let fallback = default_template.unwrap_or(ROLE_WORKER);
72 resolve_from_template_name(fallback, agent_id)
73}
74
75fn resolve_from_template_name(name: &str, agent_id: AgentId) -> CSpace {
81 match name {
82 ROLE_WORKER => CapabilityTemplate::worker().build_for(agent_id),
83 ROLE_STANDARD => CapabilityTemplate::standard().build_for(agent_id),
84 ROLE_OPERATOR => CapabilityTemplate::operator().build_for(agent_id),
85 ROLE_SUPERVISOR => CapabilityTemplate::supervisor().build_for(agent_id),
86 _ => {
87 if name.starts_with('{') {
90 tracing::warn!(
91 "JSON cspace_hint not yet supported, falling back to worker: {}",
92 name
93 );
94 } else {
95 tracing::warn!(
96 "Unknown capability template '{}', falling back to worker",
97 name
98 );
99 }
100 CapabilityTemplate::worker().build_for(agent_id)
101 }
102 }
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108
109 #[test]
110 fn hint_takes_priority_over_role() {
111 let id = AgentId::new_v4();
112 let cs = resolve_cspace(Some("supervisor"), Some("worker"), None, id);
113 use super::super::types::{ResourceRef, Rights};
115 assert!(cs.can(
116 &ResourceRef::KernelDomain {
117 domain: "security".into()
118 },
119 Rights::ALL,
120 ));
121 }
122
123 #[test]
124 fn role_used_when_no_hint() {
125 let id = AgentId::new_v4();
126 let cs = resolve_cspace(None, Some("operator"), None, id);
127 use super::super::types::{ResourceRef, Rights};
128 assert!(cs.can(&ResourceRef::A2a, Rights::EXECUTE));
129 }
130
131 #[test]
132 fn default_is_worker() {
133 let id = AgentId::new_v4();
134 let cs = resolve_cspace(None, None, None, id);
135 use super::super::types::{ResourceRef, Rights};
136 assert!(cs.can(
137 &ResourceRef::Exec {
138 mode: "shell".into()
139 },
140 Rights::EXECUTE
141 ));
142 assert!(!cs.can(&ResourceRef::A2a, Rights::READ));
144 }
145
146 #[test]
147 fn custom_default_template() {
148 let id = AgentId::new_v4();
149 let cs = resolve_cspace(None, None, Some("standard"), id);
150 use super::super::types::{ResourceRef, Rights};
151 assert!(cs.can(
152 &ResourceRef::KernelDomain {
153 domain: "memory".into()
154 },
155 Rights::READ
156 ));
157 }
158
159 #[test]
160 fn empty_hint_falls_through() {
161 let id = AgentId::new_v4();
162 let cs = resolve_cspace(Some(""), Some("operator"), None, id);
163 use super::super::types::{ResourceRef, Rights};
164 assert!(cs.can(&ResourceRef::A2a, Rights::EXECUTE));
165 }
166
167 #[test]
168 fn unknown_name_falls_back_to_worker() {
169 let id = AgentId::new_v4();
170 let cs = resolve_cspace(Some("nonexistent"), None, None, id);
171 use super::super::types::{ResourceRef, Rights};
172 assert!(cs.can(
173 &ResourceRef::Exec {
174 mode: "shell".into()
175 },
176 Rights::EXECUTE
177 ));
178 }
179
180 #[test]
181 fn json_hint_falls_back_gracefully() {
182 let id = AgentId::new_v4();
183 let cs = resolve_cspace(Some(r#"{"custom": true}"#), None, None, id);
184 use super::super::types::ResourceRef;
185 assert!(cs.can(
187 &ResourceRef::Exec {
188 mode: "shell".into()
189 },
190 super::super::types::Rights::EXECUTE
191 ));
192 }
193}