oxios_kernel/capability/
template.rs1use crate::space::SpaceId;
28use crate::types::AgentId;
29
30use super::types::{CSpace, Capability, CapabilityId, Issuer, ResourceRef, Rights};
31
32#[derive(Debug, Clone)]
38pub struct CapabilityTemplate {
39 caps: Vec<(ResourceRef, Rights)>,
40}
41
42impl CapabilityTemplate {
43 pub fn worker() -> Self {
49 let mut t = Self { caps: Vec::new() };
50 t.caps.push((
51 ResourceRef::Exec {
52 mode: "shell".into(),
53 },
54 Rights::EXECUTE | Rights::READ,
55 ));
56 t.caps
57 .push((ResourceRef::Browser, Rights::READ | Rights::EXECUTE));
58 t
59 }
60
61 pub fn standard() -> Self {
66 let mut t = Self::worker();
67 t.caps.push((
68 ResourceRef::KernelDomain {
69 domain: "memory".into(),
70 },
71 Rights::READ,
72 ));
73 t
74 }
75
76 pub fn operator() -> Self {
82 let mut t = Self::standard();
83 let extra = vec![
84 (
85 ResourceRef::Space { id: SpaceId::nil() },
86 Rights::READ | Rights::WRITE,
87 ),
88 (
89 ResourceRef::Agent { id: AgentId::nil() },
90 Rights::READ | Rights::WRITE,
91 ),
92 (
93 ResourceRef::A2a,
94 Rights::READ | Rights::WRITE | Rights::EXECUTE,
95 ),
96 (
97 ResourceRef::KernelDomain {
98 domain: "persona".into(),
99 },
100 Rights::READ | Rights::WRITE,
101 ),
102 (
103 ResourceRef::KernelDomain {
104 domain: "program".into(),
105 },
106 Rights::READ | Rights::WRITE | Rights::EXECUTE,
107 ),
108 (
109 ResourceRef::Mcp { server: "*".into() },
110 Rights::READ | Rights::EXECUTE,
111 ),
112 (
114 ResourceRef::KernelDomain {
115 domain: "memory".into(),
116 },
117 Rights::READ | Rights::WRITE,
118 ),
119 ];
120 t.caps.extend(extra);
121 t
122 }
123
124 pub fn supervisor() -> Self {
129 let mut t = Self::operator();
130 let admin = vec![
131 (
132 ResourceRef::KernelDomain {
133 domain: "security".into(),
134 },
135 Rights::ALL,
136 ),
137 (
138 ResourceRef::KernelDomain {
139 domain: "budget".into(),
140 },
141 Rights::READ | Rights::WRITE,
142 ),
143 (
144 ResourceRef::KernelDomain {
145 domain: "resource".into(),
146 },
147 Rights::READ | Rights::WRITE,
148 ),
149 (
150 ResourceRef::KernelDomain {
151 domain: "cron".into(),
152 },
153 Rights::READ | Rights::WRITE | Rights::EXECUTE,
154 ),
155 ];
156 t.caps.extend(admin);
157 t
158 }
159
160 pub fn with_programs(names: &[&str]) -> Self {
166 let mut t = Self::worker();
167 for name in names {
168 t.caps.push((
169 ResourceRef::Program {
170 name: (*name).into(),
171 },
172 Rights::EXECUTE | Rights::READ,
173 ));
174 }
175 t
176 }
177
178 pub fn with(mut self, resource: ResourceRef, rights: Rights) -> Self {
182 self.caps.push((resource, rights));
183 self
184 }
185
186 pub fn build(&self) -> CSpace {
188 self.build_for(AgentId::new_v4())
189 }
190
191 pub fn build_for(&self, agent_id: AgentId) -> CSpace {
193 let mut cspace = CSpace::new(agent_id);
194 for (resource, rights) in &self.caps {
195 let cap = Capability {
196 id: CapabilityId::new(),
197 resource: resource.clone(),
198 rights: *rights,
199 issuer: Issuer::Kernel,
200 };
201 cspace.insert(cap);
202 }
203 cspace
204 }
205
206 pub fn len(&self) -> usize {
208 self.caps.len()
209 }
210
211 pub fn is_empty(&self) -> bool {
213 self.caps.is_empty()
214 }
215}
216
217impl Default for CapabilityTemplate {
218 fn default() -> Self {
219 Self::worker()
220 }
221}
222
223#[cfg(test)]
224mod tests {
225 use super::*;
226
227 #[test]
228 fn worker_has_exec_and_browser() {
229 let cs = CapabilityTemplate::worker().build();
230 assert!(cs.can(
231 &ResourceRef::Exec {
232 mode: "shell".into()
233 },
234 Rights::EXECUTE
235 ));
236 assert!(cs.can(&ResourceRef::Browser, Rights::READ));
237 assert_eq!(cs.len(), 2);
238 }
239
240 #[test]
241 fn standard_adds_memory_read() {
242 let cs = CapabilityTemplate::standard().build();
243 assert!(cs.can(
244 &ResourceRef::KernelDomain {
245 domain: "memory".into()
246 },
247 Rights::READ
248 ));
249 assert!(!cs.can(
250 &ResourceRef::KernelDomain {
251 domain: "memory".into()
252 },
253 Rights::WRITE
254 ));
255 }
256
257 #[test]
258 fn operator_has_a2a_and_mcp() {
259 let cs = CapabilityTemplate::operator().build();
260 assert!(cs.can(&ResourceRef::A2a, Rights::EXECUTE));
261 assert!(cs.can(&ResourceRef::Mcp { server: "*".into() }, Rights::EXECUTE));
262 }
263
264 #[test]
265 fn supervisor_has_security_all() {
266 let cs = CapabilityTemplate::supervisor().build();
267 assert!(cs.can(
268 &ResourceRef::KernelDomain {
269 domain: "security".into()
270 },
271 Rights::ALL
272 ));
273 }
274
275 #[test]
276 fn with_programs_scoped() {
277 let cs = CapabilityTemplate::with_programs(&["git", "gh"]).build();
278 assert!(cs.can(
279 &ResourceRef::Program { name: "git".into() },
280 Rights::EXECUTE
281 ));
282 assert!(cs.can(&ResourceRef::Program { name: "gh".into() }, Rights::EXECUTE));
283 assert!(!cs.can(
284 &ResourceRef::Program {
285 name: "curl".into()
286 },
287 Rights::EXECUTE
288 ));
289 }
290
291 #[test]
292 fn builder_chaining() {
293 let cs = CapabilityTemplate::worker()
294 .with(
295 ResourceRef::KernelDomain {
296 domain: "custom".into(),
297 },
298 Rights::READ,
299 )
300 .build();
301 assert!(cs.can(
302 &ResourceRef::KernelDomain {
303 domain: "custom".into()
304 },
305 Rights::READ
306 ));
307 }
308
309 #[test]
310 fn build_for_specific_agent() {
311 let id = AgentId::new_v4();
312 let cs = CapabilityTemplate::worker().build_for(id);
313 assert_eq!(cs.agent_id, id);
314 }
315}