use crate::space::SpaceId;
use crate::types::AgentId;
use super::types::{CSpace, Capability, CapabilityId, Issuer, ResourceRef, Rights};
#[derive(Debug, Clone)]
pub struct CapabilityTemplate {
caps: Vec<(ResourceRef, Rights)>,
}
impl CapabilityTemplate {
pub fn worker() -> Self {
let mut t = Self { caps: Vec::new() };
t.caps.push((
ResourceRef::Exec {
mode: "shell".into(),
},
Rights::EXECUTE | Rights::READ,
));
t.caps
.push((ResourceRef::Browser, Rights::READ | Rights::EXECUTE));
t
}
pub fn standard() -> Self {
let mut t = Self::worker();
t.caps.push((
ResourceRef::KernelDomain {
domain: "memory".into(),
},
Rights::READ,
));
t
}
pub fn operator() -> Self {
let mut t = Self::standard();
let extra = vec![
(
ResourceRef::Space { id: SpaceId::nil() },
Rights::READ | Rights::WRITE,
),
(
ResourceRef::Agent { id: AgentId::nil() },
Rights::READ | Rights::WRITE,
),
(
ResourceRef::A2a,
Rights::READ | Rights::WRITE | Rights::EXECUTE,
),
(
ResourceRef::KernelDomain {
domain: "persona".into(),
},
Rights::READ | Rights::WRITE,
),
(
ResourceRef::KernelDomain {
domain: "program".into(),
},
Rights::READ | Rights::WRITE | Rights::EXECUTE,
),
(
ResourceRef::Mcp { server: "*".into() },
Rights::READ | Rights::EXECUTE,
),
(
ResourceRef::KernelDomain {
domain: "memory".into(),
},
Rights::READ | Rights::WRITE,
),
];
t.caps.extend(extra);
t
}
pub fn supervisor() -> Self {
let mut t = Self::operator();
let admin = vec![
(
ResourceRef::KernelDomain {
domain: "security".into(),
},
Rights::ALL,
),
(
ResourceRef::KernelDomain {
domain: "budget".into(),
},
Rights::READ | Rights::WRITE,
),
(
ResourceRef::KernelDomain {
domain: "resource".into(),
},
Rights::READ | Rights::WRITE,
),
(
ResourceRef::KernelDomain {
domain: "cron".into(),
},
Rights::READ | Rights::WRITE | Rights::EXECUTE,
),
];
t.caps.extend(admin);
t
}
pub fn with_programs(names: &[&str]) -> Self {
let mut t = Self::worker();
for name in names {
t.caps.push((
ResourceRef::Program {
name: (*name).into(),
},
Rights::EXECUTE | Rights::READ,
));
}
t
}
pub fn with(mut self, resource: ResourceRef, rights: Rights) -> Self {
self.caps.push((resource, rights));
self
}
pub fn build(&self) -> CSpace {
self.build_for(AgentId::new_v4())
}
pub fn build_for(&self, agent_id: AgentId) -> CSpace {
let mut cspace = CSpace::new(agent_id);
for (resource, rights) in &self.caps {
let cap = Capability {
id: CapabilityId::new(),
resource: resource.clone(),
rights: *rights,
issuer: Issuer::Kernel,
};
cspace.insert(cap);
}
cspace
}
pub fn len(&self) -> usize {
self.caps.len()
}
pub fn is_empty(&self) -> bool {
self.caps.is_empty()
}
}
impl Default for CapabilityTemplate {
fn default() -> Self {
Self::worker()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn worker_has_exec_and_browser() {
let cs = CapabilityTemplate::worker().build();
assert!(cs.can(
&ResourceRef::Exec {
mode: "shell".into()
},
Rights::EXECUTE
));
assert!(cs.can(&ResourceRef::Browser, Rights::READ));
assert_eq!(cs.len(), 2);
}
#[test]
fn standard_adds_memory_read() {
let cs = CapabilityTemplate::standard().build();
assert!(cs.can(
&ResourceRef::KernelDomain {
domain: "memory".into()
},
Rights::READ
));
assert!(!cs.can(
&ResourceRef::KernelDomain {
domain: "memory".into()
},
Rights::WRITE
));
}
#[test]
fn operator_has_a2a_and_mcp() {
let cs = CapabilityTemplate::operator().build();
assert!(cs.can(&ResourceRef::A2a, Rights::EXECUTE));
assert!(cs.can(&ResourceRef::Mcp { server: "*".into() }, Rights::EXECUTE));
}
#[test]
fn supervisor_has_security_all() {
let cs = CapabilityTemplate::supervisor().build();
assert!(cs.can(
&ResourceRef::KernelDomain {
domain: "security".into()
},
Rights::ALL
));
}
#[test]
fn with_programs_scoped() {
let cs = CapabilityTemplate::with_programs(&["git", "gh"]).build();
assert!(cs.can(
&ResourceRef::Program { name: "git".into() },
Rights::EXECUTE
));
assert!(cs.can(&ResourceRef::Program { name: "gh".into() }, Rights::EXECUTE));
assert!(!cs.can(
&ResourceRef::Program {
name: "curl".into()
},
Rights::EXECUTE
));
}
#[test]
fn builder_chaining() {
let cs = CapabilityTemplate::worker()
.with(
ResourceRef::KernelDomain {
domain: "custom".into(),
},
Rights::READ,
)
.build();
assert!(cs.can(
&ResourceRef::KernelDomain {
domain: "custom".into()
},
Rights::READ
));
}
#[test]
fn build_for_specific_agent() {
let id = AgentId::new_v4();
let cs = CapabilityTemplate::worker().build_for(id);
assert_eq!(cs.agent_id, id);
}
}