1use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
10pub struct Capability(pub String);
11
12impl Capability {
13 pub fn new(value: impl Into<String>) -> Self {
14 Self(value.into())
15 }
16
17 pub fn fs_read(glob: &str) -> Self {
18 Self(format!("fs:read:{glob}"))
19 }
20
21 pub fn fs_write(glob: &str) -> Self {
22 Self(format!("fs:write:{glob}"))
23 }
24
25 pub fn net_egress(host: &str) -> Self {
26 Self(format!("net:egress:{host}"))
27 }
28
29 pub fn exec(command: &str) -> Self {
30 Self(format!("exec:cmd:{command}"))
31 }
32
33 pub fn secrets(scope: &str) -> Self {
34 Self(format!("secrets:read:{scope}"))
35 }
36
37 pub fn as_str(&self) -> &str {
38 &self.0
39 }
40}
41
42#[derive(Debug, Clone, Serialize, Deserialize)]
44pub struct PolicySet {
45 pub allow_capabilities: Vec<Capability>,
46 pub gate_capabilities: Vec<Capability>,
47 pub max_tool_runtime_secs: u64,
48 pub max_events_per_turn: u64,
49}
50
51impl Default for PolicySet {
52 fn default() -> Self {
53 Self {
54 allow_capabilities: vec![
55 Capability::fs_read("/session/**"),
56 Capability::fs_write("/session/artifacts/**"),
57 Capability::exec("git"),
58 ],
59 gate_capabilities: vec![Capability::new("payments:initiate")],
60 max_tool_runtime_secs: 30,
61 max_events_per_turn: 256,
62 }
63 }
64}
65
66#[derive(Debug, Clone, Serialize, Deserialize)]
68pub struct PolicyEvaluation {
69 pub allowed: Vec<Capability>,
70 pub requires_approval: Vec<Capability>,
71 pub denied: Vec<Capability>,
72}
73
74#[cfg(test)]
75mod tests {
76 use super::*;
77
78 #[test]
79 fn capability_factory_methods() {
80 assert_eq!(Capability::fs_read("/tmp").as_str(), "fs:read:/tmp");
81 assert_eq!(Capability::fs_write("/out").as_str(), "fs:write:/out");
82 assert_eq!(
83 Capability::net_egress("api.com").as_str(),
84 "net:egress:api.com"
85 );
86 assert_eq!(Capability::exec("git").as_str(), "exec:cmd:git");
87 assert_eq!(Capability::secrets("prod").as_str(), "secrets:read:prod");
88 }
89
90 #[test]
91 fn policy_set_default() {
92 let ps = PolicySet::default();
93 assert_eq!(ps.allow_capabilities.len(), 3);
94 assert_eq!(ps.gate_capabilities.len(), 1);
95 assert_eq!(ps.max_tool_runtime_secs, 30);
96 }
97
98 #[test]
99 fn capability_serde_roundtrip() {
100 let cap = Capability::fs_read("/session/**");
101 let json = serde_json::to_string(&cap).unwrap();
102 let back: Capability = serde_json::from_str(&json).unwrap();
103 assert_eq!(cap, back);
104 }
105}