use std::collections::HashMap;
use std::fmt;
use serde::{Deserialize, Serialize};
use crate::space::SpaceId;
use crate::types::AgentId;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct CapabilityId(pub uuid::Uuid);
impl CapabilityId {
pub fn new() -> Self {
Self(uuid::Uuid::new_v4())
}
}
impl fmt::Display for CapabilityId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "cap:{}", self.0)
}
}
impl Default for CapabilityId {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(tag = "issuer", content = "id")]
pub enum Issuer {
Kernel,
Agent(AgentId),
}
impl fmt::Display for Issuer {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Issuer::Kernel => write!(f, "kernel"),
Issuer::Agent(id) => write!(f, "agent:{}", id),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(transparent)]
pub struct Rights(pub u8);
impl Rights {
pub const NONE: Rights = Rights(0x00);
pub const READ: Rights = Rights(0x01);
pub const WRITE: Rights = Rights(0x02);
pub const EXECUTE: Rights = Rights(0x04);
pub const DELEGATE: Rights = Rights(0x08);
pub const ALL: Rights = Rights(0x0F);
pub fn contains(self, other: Rights) -> bool {
(self.0 & other.0) == other.0
}
pub fn union(self, other: Rights) -> Rights {
Rights(self.0 | other.0)
}
pub fn intersect(self, other: Rights) -> Rights {
Rights(self.0 & other.0)
}
pub fn is_empty(self) -> bool {
self.0 == 0
}
}
impl fmt::Display for Rights {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.0 == Rights::ALL.0 {
return write!(f, "ALL");
}
if self.0 == Rights::NONE.0 {
return write!(f, "NONE");
}
let mut parts = Vec::new();
if self.contains(Rights::READ) {
parts.push("R");
}
if self.contains(Rights::WRITE) {
parts.push("W");
}
if self.contains(Rights::EXECUTE) {
parts.push("X");
}
if self.contains(Rights::DELEGATE) {
parts.push("D");
}
write!(f, "{}", parts.join("|"))
}
}
impl std::ops::BitOr for Rights {
type Output = Self;
fn bitor(self, rhs: Self) -> Self {
Rights(self.0 | rhs.0)
}
}
impl std::ops::BitAnd for Rights {
type Output = Self;
fn bitand(self, rhs: Self) -> Self {
Rights(self.0 & rhs.0)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(tag = "kind", content = "ref")]
pub enum ResourceRef {
KernelDomain {
domain: String,
},
Program {
name: String,
},
Space {
id: SpaceId,
},
Agent {
id: AgentId,
},
Exec {
mode: String,
},
Browser,
A2a,
Mcp {
server: String,
},
}
impl fmt::Display for ResourceRef {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ResourceRef::KernelDomain { domain } => write!(f, "kernel:{}", domain),
ResourceRef::Program { name } => write!(f, "program:{}", name),
ResourceRef::Space { id } => write!(f, "space:{}", id),
ResourceRef::Agent { id } => write!(f, "agent:{}", id),
ResourceRef::Exec { mode } => write!(f, "exec:{}", mode),
ResourceRef::Browser => write!(f, "browser"),
ResourceRef::A2a => write!(f, "a2a"),
ResourceRef::Mcp { server } => write!(f, "mcp:{}", server),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Capability {
pub id: CapabilityId,
pub resource: ResourceRef,
pub rights: Rights,
pub issuer: Issuer,
}
impl Capability {
pub fn kernel(resource: ResourceRef, rights: Rights) -> Self {
Self {
id: CapabilityId::new(),
resource,
rights,
issuer: Issuer::Kernel,
}
}
pub fn delegated(resource: ResourceRef, rights: Rights, issuer: AgentId) -> Self {
Self {
id: CapabilityId::new(),
resource,
rights,
issuer: Issuer::Agent(issuer),
}
}
pub fn grants(&self, required: Rights) -> bool {
self.rights.contains(required)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CSpace {
pub agent_id: AgentId,
caps: HashMap<CapabilityId, Capability>,
}
impl CSpace {
pub fn new(agent_id: AgentId) -> Self {
Self {
agent_id,
caps: HashMap::new(),
}
}
pub fn insert(&mut self, cap: Capability) -> Option<Capability> {
self.caps.insert(cap.id, cap)
}
pub fn remove(&mut self, id: &CapabilityId) -> Option<Capability> {
self.caps.remove(id)
}
pub fn get(&self, id: &CapabilityId) -> Option<&Capability> {
self.caps.get(id)
}
pub fn can(&self, resource: &ResourceRef, required: Rights) -> bool {
self.caps
.values()
.any(|cap| &cap.resource == resource && cap.grants(required))
}
pub fn iter(&self) -> impl Iterator<Item = &Capability> {
self.caps.values()
}
pub fn len(&self) -> usize {
self.caps.len()
}
pub fn is_empty(&self) -> bool {
self.caps.is_empty()
}
pub fn retain(&mut self, pred: impl Fn(&Capability) -> bool) {
self.caps.retain(|_, cap| pred(cap));
}
pub fn filter_resource(&self, f: impl Fn(&ResourceRef) -> bool) -> Vec<&Capability> {
self.caps.values().filter(|c| f(&c.resource)).collect()
}
pub fn active_domains(&self) -> Vec<&str> {
let mut domains: Vec<&str> = self
.caps
.values()
.filter_map(|cap| match &cap.resource {
ResourceRef::KernelDomain { domain } => Some(domain.as_str()),
ResourceRef::Exec { .. } => Some("exec"),
ResourceRef::Browser => Some("browser"),
_ => None,
})
.collect();
domains.sort();
domains.dedup();
domains
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rights_bit_ops() {
let rw = Rights::READ | Rights::WRITE;
assert!(rw.contains(Rights::READ));
assert!(rw.contains(Rights::WRITE));
assert!(!rw.contains(Rights::EXECUTE));
let r = rw.intersect(Rights::READ);
assert_eq!(r, Rights::READ);
}
#[test]
fn rights_display() {
assert_eq!(format!("{}", Rights::ALL), "ALL");
assert_eq!(format!("{}", Rights::NONE), "NONE");
assert_eq!(format!("{}", Rights::READ | Rights::EXECUTE), "R|X");
}
#[test]
fn cspace_can_check() {
let agent = AgentId::new_v4();
let mut cs = CSpace::new(agent);
let cap = Capability::kernel(
ResourceRef::Exec {
mode: "shell".into(),
},
Rights::READ | Rights::EXECUTE,
);
cs.insert(cap);
assert!(cs.can(
&ResourceRef::Exec {
mode: "shell".into()
},
Rights::EXECUTE
));
assert!(!cs.can(
&ResourceRef::Exec {
mode: "shell".into()
},
Rights::WRITE
));
assert!(!cs.can(&ResourceRef::Browser, Rights::READ));
}
#[test]
fn capability_delegation() {
let issuer = AgentId::new_v4();
let cap = Capability::delegated(
ResourceRef::Agent { id: issuer },
Rights::READ | Rights::DELEGATE,
issuer,
);
assert!(matches!(cap.issuer, Issuer::Agent(_)));
assert!(cap.grants(Rights::DELEGATE));
}
}