Skip to main content

oxios_kernel/capability/
types.rs

1//! Core capability types for the Oxios capability system.
2//!
3//! Capabilities are unforgeable tokens that encode authority over specific
4//! resources. An agent's capability space (CSpace) is the complete set of
5//! capabilities it holds.
6//!
7//! # Design
8//!
9//! Inspired by capability-based security (seL4, Capsicum), each capability
10//! binds a set of rights to a specific resource. Capabilities cannot be
11//! forged — they are issued by the kernel or by agents with DELEGATE rights.
12//!
13//! ```
14//! use oxios_kernel::capability::types::*;
15//! use oxios_kernel::capability::template::CapabilityTemplate;
16//!
17//! let cspace = CapabilityTemplate::worker().build();
18//! assert!(!cspace.is_empty());
19//! ```
20
21use std::collections::HashMap;
22use std::fmt;
23
24use serde::{Deserialize, Serialize};
25
26use crate::space::SpaceId;
27use crate::types::AgentId;
28
29/// Unique identifier for a capability.
30///
31/// Each capability receives a random UUID at creation time, making
32/// capabilities unforgeable — an agent cannot guess a valid CapabilityId.
33#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
34pub struct CapabilityId(pub uuid::Uuid);
35
36impl CapabilityId {
37    /// Generate a new random capability ID.
38    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/// Who issued a capability.
56///
57/// The kernel is the root authority. Agents with DELEGATE rights can
58/// issue derived capabilities scoped to their own authority.
59#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
60#[serde(tag = "issuer", content = "id")]
61pub enum Issuer {
62    /// The Oxios kernel — root authority.
63    Kernel,
64    /// An agent that delegated a subset of its own authority.
65    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/// Bit-flag rights encoded in a capability.
78///
79/// Rights are represented as a `u8` bitmask. Standard combinations are
80/// provided as associated constants.
81///
82/// | Flag     | Value |
83/// |----------|-------|
84/// | NONE     | 0x00  |
85/// | READ     | 0x01  |
86/// | WRITE    | 0x02  |
87/// | EXECUTE  | 0x04  |
88/// | DELEGATE | 0x08  |
89/// | ALL      | 0x0F  |
90#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
91#[serde(transparent)]
92pub struct Rights(pub u8);
93
94impl Rights {
95    /// No rights at all.
96    pub const NONE: Rights = Rights(0x00);
97    /// Read access to a resource.
98    pub const READ: Rights = Rights(0x01);
99    /// Write / mutate access to a resource.
100    pub const WRITE: Rights = Rights(0x02);
101    /// Execute a resource (run a program, invoke a tool).
102    pub const EXECUTE: Rights = Rights(0x04);
103    /// Right to delegate a subset of this capability to another agent.
104    pub const DELEGATE: Rights = Rights(0x08);
105    /// All rights combined.
106    pub const ALL: Rights = Rights(0x0F);
107
108    /// Returns `true` if this set contains all the given rights.
109    pub fn contains(self, other: Rights) -> bool {
110        (self.0 & other.0) == other.0
111    }
112
113    /// Returns a new Rights set with the given flags added.
114    pub fn union(self, other: Rights) -> Rights {
115        Rights(self.0 | other.0)
116    }
117
118    /// Returns a new Rights set with only the flags present in both.
119    pub fn intersect(self, other: Rights) -> Rights {
120        Rights(self.0 & other.0)
121    }
122
123    /// Returns true if no rights are set.
124    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/// A reference to a protected resource.
169///
170/// Resources are the objects that capabilities govern. Each variant
171/// identifies a distinct resource class in the Oxios kernel.
172#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
173#[serde(tag = "kind", content = "ref")]
174pub enum ResourceRef {
175    /// A kernel domain (e.g., "memory", "scheduler").
176    KernelDomain {
177        /// Domain name within the kernel.
178        domain: String,
179    },
180    /// An installed program.
181    Program {
182        /// Program name as registered in the program store.
183        name: String,
184    },
185    /// A workspace space.
186    Space {
187        /// Space identifier.
188        id: SpaceId,
189    },
190    /// Another agent (for inter-agent communication).
191    Agent {
192        /// Agent identifier.
193        id: AgentId,
194    },
195    /// Command execution (shell or structured).
196    Exec {
197        /// Execution mode: "shell" or "structured".
198        mode: String,
199    },
200    /// Headless browser access.
201    Browser,
202    /// Agent-to-agent communication channel.
203    A2a,
204    /// MCP (Model Context Protocol) server bridge.
205    Mcp {
206        /// MCP server name.
207        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/// An unforgeable token encoding authority over a specific resource.
227///
228/// A capability binds a set of `Rights` to a `ResourceRef`, and records
229/// who issued it. Agents present capabilities to the `AccessManager` to
230/// prove they are authorised to perform an action.
231///
232/// # Immutability
233///
234/// Capabilities are immutable once created. To change rights, create a
235/// new capability and replace the old one in the CSpace.
236#[derive(Debug, Clone, Serialize, Deserialize)]
237pub struct Capability {
238    /// Unique identifier for this capability.
239    pub id: CapabilityId,
240    /// The resource this capability governs.
241    pub resource: ResourceRef,
242    /// The rights granted over the resource.
243    pub rights: Rights,
244    /// Who issued this capability.
245    pub issuer: Issuer,
246}
247
248impl Capability {
249    /// Creates a new kernel-issued capability with the given resource and rights.
250    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    /// Creates a new agent-issued capability (delegation).
260    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    /// Returns true if this capability grants the requested rights.
270    pub fn grants(&self, required: Rights) -> bool {
271        self.rights.contains(required)
272    }
273}
274
275/// An agent's **capability space**: the complete set of capabilities it holds.
276///
277/// The CSpace is a `HashMap<CapabilityId, Capability>`. When the
278/// `AccessManager` checks whether an agent may perform an action, it
279/// scans the agent's CSpace for a capability matching the target
280/// resource with sufficient rights.
281#[derive(Debug, Clone, Serialize, Deserialize)]
282pub struct CSpace {
283    /// The agent that owns this capability space.
284    pub agent_id: AgentId,
285    /// Map from capability ID to capability.
286    caps: HashMap<CapabilityId, Capability>,
287}
288
289impl CSpace {
290    /// Creates an empty CSpace for the given agent.
291    pub fn new(agent_id: AgentId) -> Self {
292        Self {
293            agent_id,
294            caps: HashMap::new(),
295        }
296    }
297
298    /// Inserts a capability into this space, returning the old one if replaced.
299    pub fn insert(&mut self, cap: Capability) -> Option<Capability> {
300        self.caps.insert(cap.id, cap)
301    }
302
303    /// Removes a capability by ID.
304    pub fn remove(&mut self, id: &CapabilityId) -> Option<Capability> {
305        self.caps.remove(id)
306    }
307
308    /// Looks up a capability by ID.
309    pub fn get(&self, id: &CapabilityId) -> Option<&Capability> {
310        self.caps.get(id)
311    }
312
313    /// Returns true if any capability in this space matches the resource
314    /// and grants the requested rights.
315    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    /// Returns an iterator over all capabilities in this space.
322    pub fn iter(&self) -> impl Iterator<Item = &Capability> {
323        self.caps.values()
324    }
325
326    /// Returns the number of capabilities.
327    pub fn len(&self) -> usize {
328        self.caps.len()
329    }
330
331    /// Returns true if there are no capabilities.
332    pub fn is_empty(&self) -> bool {
333        self.caps.is_empty()
334    }
335
336    /// Retains only capabilities for which `pred` returns true.
337    pub fn retain(&mut self, pred: impl Fn(&Capability) -> bool) {
338        self.caps.retain(|_, cap| pred(cap));
339    }
340
341    /// Returns capabilities matching a specific resource type filter.
342    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    /// Returns the unique kernel domain names active in this CSpace.
347    ///
348    /// Useful for building a kernel manifest for the system prompt.
349    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}