1use std::collections::HashMap;
22use std::fmt;
23
24use serde::{Deserialize, Serialize};
25
26use crate::space::SpaceId;
27use crate::types::AgentId;
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
34pub struct CapabilityId(pub uuid::Uuid);
35
36impl CapabilityId {
37 pub fn new() -> Self {
39 Self(uuid::Uuid::new_v4())
40 }
41}
42
43impl fmt::Display for CapabilityId {
44 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45 write!(f, "cap:{}", self.0)
46 }
47}
48
49impl Default for CapabilityId {
50 fn default() -> Self {
51 Self::new()
52 }
53}
54
55#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
60#[serde(tag = "issuer", content = "id")]
61pub enum Issuer {
62 Kernel,
64 Agent(AgentId),
66}
67
68impl fmt::Display for Issuer {
69 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
70 match self {
71 Issuer::Kernel => write!(f, "kernel"),
72 Issuer::Agent(id) => write!(f, "agent:{}", id),
73 }
74 }
75}
76
77#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
91#[serde(transparent)]
92pub struct Rights(pub u8);
93
94impl Rights {
95 pub const NONE: Rights = Rights(0x00);
97 pub const READ: Rights = Rights(0x01);
99 pub const WRITE: Rights = Rights(0x02);
101 pub const EXECUTE: Rights = Rights(0x04);
103 pub const DELEGATE: Rights = Rights(0x08);
105 pub const ALL: Rights = Rights(0x0F);
107
108 pub fn contains(self, other: Rights) -> bool {
110 (self.0 & other.0) == other.0
111 }
112
113 pub fn union(self, other: Rights) -> Rights {
115 Rights(self.0 | other.0)
116 }
117
118 pub fn intersect(self, other: Rights) -> Rights {
120 Rights(self.0 & other.0)
121 }
122
123 pub fn is_empty(self) -> bool {
125 self.0 == 0
126 }
127}
128
129impl fmt::Display for Rights {
130 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
131 if self.0 == Rights::ALL.0 {
132 return write!(f, "ALL");
133 }
134 if self.0 == Rights::NONE.0 {
135 return write!(f, "NONE");
136 }
137 let mut parts = Vec::new();
138 if self.contains(Rights::READ) {
139 parts.push("R");
140 }
141 if self.contains(Rights::WRITE) {
142 parts.push("W");
143 }
144 if self.contains(Rights::EXECUTE) {
145 parts.push("X");
146 }
147 if self.contains(Rights::DELEGATE) {
148 parts.push("D");
149 }
150 write!(f, "{}", parts.join("|"))
151 }
152}
153
154impl std::ops::BitOr for Rights {
155 type Output = Self;
156 fn bitor(self, rhs: Self) -> Self {
157 Rights(self.0 | rhs.0)
158 }
159}
160
161impl std::ops::BitAnd for Rights {
162 type Output = Self;
163 fn bitand(self, rhs: Self) -> Self {
164 Rights(self.0 & rhs.0)
165 }
166}
167
168#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
173#[serde(tag = "kind", content = "ref")]
174pub enum ResourceRef {
175 KernelDomain {
177 domain: String,
179 },
180 Program {
182 name: String,
184 },
185 Space {
187 id: SpaceId,
189 },
190 Agent {
192 id: AgentId,
194 },
195 Exec {
197 mode: String,
199 },
200 Browser,
202 A2a,
204 Mcp {
206 server: String,
208 },
209}
210
211impl fmt::Display for ResourceRef {
212 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
213 match self {
214 ResourceRef::KernelDomain { domain } => write!(f, "kernel:{}", domain),
215 ResourceRef::Program { name } => write!(f, "program:{}", name),
216 ResourceRef::Space { id } => write!(f, "space:{}", id),
217 ResourceRef::Agent { id } => write!(f, "agent:{}", id),
218 ResourceRef::Exec { mode } => write!(f, "exec:{}", mode),
219 ResourceRef::Browser => write!(f, "browser"),
220 ResourceRef::A2a => write!(f, "a2a"),
221 ResourceRef::Mcp { server } => write!(f, "mcp:{}", server),
222 }
223 }
224}
225
226#[derive(Debug, Clone, Serialize, Deserialize)]
237pub struct Capability {
238 pub id: CapabilityId,
240 pub resource: ResourceRef,
242 pub rights: Rights,
244 pub issuer: Issuer,
246}
247
248impl Capability {
249 pub fn kernel(resource: ResourceRef, rights: Rights) -> Self {
251 Self {
252 id: CapabilityId::new(),
253 resource,
254 rights,
255 issuer: Issuer::Kernel,
256 }
257 }
258
259 pub fn delegated(resource: ResourceRef, rights: Rights, issuer: AgentId) -> Self {
261 Self {
262 id: CapabilityId::new(),
263 resource,
264 rights,
265 issuer: Issuer::Agent(issuer),
266 }
267 }
268
269 pub fn grants(&self, required: Rights) -> bool {
271 self.rights.contains(required)
272 }
273}
274
275#[derive(Debug, Clone, Serialize, Deserialize)]
282pub struct CSpace {
283 pub agent_id: AgentId,
285 caps: HashMap<CapabilityId, Capability>,
287}
288
289impl CSpace {
290 pub fn new(agent_id: AgentId) -> Self {
292 Self {
293 agent_id,
294 caps: HashMap::new(),
295 }
296 }
297
298 pub fn insert(&mut self, cap: Capability) -> Option<Capability> {
300 self.caps.insert(cap.id, cap)
301 }
302
303 pub fn remove(&mut self, id: &CapabilityId) -> Option<Capability> {
305 self.caps.remove(id)
306 }
307
308 pub fn get(&self, id: &CapabilityId) -> Option<&Capability> {
310 self.caps.get(id)
311 }
312
313 pub fn can(&self, resource: &ResourceRef, required: Rights) -> bool {
316 self.caps
317 .values()
318 .any(|cap| &cap.resource == resource && cap.grants(required))
319 }
320
321 pub fn iter(&self) -> impl Iterator<Item = &Capability> {
323 self.caps.values()
324 }
325
326 pub fn len(&self) -> usize {
328 self.caps.len()
329 }
330
331 pub fn is_empty(&self) -> bool {
333 self.caps.is_empty()
334 }
335
336 pub fn retain(&mut self, pred: impl Fn(&Capability) -> bool) {
338 self.caps.retain(|_, cap| pred(cap));
339 }
340
341 pub fn filter_resource(&self, f: impl Fn(&ResourceRef) -> bool) -> Vec<&Capability> {
343 self.caps.values().filter(|c| f(&c.resource)).collect()
344 }
345
346 pub fn active_domains(&self) -> Vec<&str> {
350 let mut domains: Vec<&str> = self
351 .caps
352 .values()
353 .filter_map(|cap| match &cap.resource {
354 ResourceRef::KernelDomain { domain } => Some(domain.as_str()),
355 ResourceRef::Exec { .. } => Some("exec"),
356 ResourceRef::Browser => Some("browser"),
357 _ => None,
358 })
359 .collect();
360 domains.sort();
361 domains.dedup();
362 domains
363 }
364}
365
366#[cfg(test)]
367mod tests {
368 use super::*;
369
370 #[test]
371 fn rights_bit_ops() {
372 let rw = Rights::READ | Rights::WRITE;
373 assert!(rw.contains(Rights::READ));
374 assert!(rw.contains(Rights::WRITE));
375 assert!(!rw.contains(Rights::EXECUTE));
376
377 let r = rw.intersect(Rights::READ);
378 assert_eq!(r, Rights::READ);
379 }
380
381 #[test]
382 fn rights_display() {
383 assert_eq!(format!("{}", Rights::ALL), "ALL");
384 assert_eq!(format!("{}", Rights::NONE), "NONE");
385 assert_eq!(format!("{}", Rights::READ | Rights::EXECUTE), "R|X");
386 }
387
388 #[test]
389 fn cspace_can_check() {
390 let agent = AgentId::new_v4();
391 let mut cs = CSpace::new(agent);
392 let cap = Capability::kernel(
393 ResourceRef::Exec {
394 mode: "shell".into(),
395 },
396 Rights::READ | Rights::EXECUTE,
397 );
398 cs.insert(cap);
399
400 assert!(cs.can(
401 &ResourceRef::Exec {
402 mode: "shell".into()
403 },
404 Rights::EXECUTE
405 ));
406 assert!(!cs.can(
407 &ResourceRef::Exec {
408 mode: "shell".into()
409 },
410 Rights::WRITE
411 ));
412 assert!(!cs.can(&ResourceRef::Browser, Rights::READ));
413 }
414
415 #[test]
416 fn capability_delegation() {
417 let issuer = AgentId::new_v4();
418 let cap = Capability::delegated(
419 ResourceRef::Agent { id: issuer },
420 Rights::READ | Rights::DELEGATE,
421 issuer,
422 );
423 assert!(matches!(cap.issuer, Issuer::Agent(_)));
424 assert!(cap.grants(Rights::DELEGATE));
425 }
426}