1use std::{collections::BTreeSet, fmt, sync::Arc};
8
9use crate::{
10 error::{Error, Result},
11 id::Symbol,
12};
13
14#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
29pub struct CapabilityName(Arc<str>);
30
31impl CapabilityName {
32 pub fn new(name: impl Into<Arc<str>>) -> Self {
34 Self(name.into())
35 }
36
37 pub fn as_str(&self) -> &str {
39 &self.0
40 }
41
42 pub fn as_symbol(&self) -> Symbol {
44 Symbol::qualified("capability", self.as_str().to_owned())
45 }
46}
47
48impl fmt::Display for CapabilityName {
49 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50 f.write_str(self.as_str())
51 }
52}
53
54#[derive(Clone, Debug, Default, PartialEq, Eq)]
67pub struct CapabilitySet {
68 granted: BTreeSet<CapabilityName>,
69}
70
71impl CapabilitySet {
72 pub fn new() -> Self {
74 Self::default()
75 }
76
77 pub fn grant(mut self, capability: CapabilityName) -> Self {
79 self.granted.insert(capability);
80 self
81 }
82
83 pub fn insert(&mut self, capability: CapabilityName) {
85 self.granted.insert(capability);
86 }
87
88 pub fn contains(&self, capability: &CapabilityName) -> bool {
90 self.granted.contains(capability)
91 }
92
93 pub fn iter(&self) -> impl Iterator<Item = &CapabilityName> {
95 self.granted.iter()
96 }
97}
98
99#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
104pub enum TrustLevel {
105 #[default]
107 Untrusted,
108 TrustedSource,
110 HostInternal,
112}
113
114#[derive(Clone, Debug, Default, PartialEq, Eq)]
119pub struct ReadPolicy {
120 pub trust: TrustLevel,
122 pub capabilities: CapabilitySet,
124}
125
126impl ReadPolicy {
127 pub fn require(&self, capability: &CapabilityName) -> Result<()> {
133 if !self.capabilities.contains(capability) {
134 return Err(Error::CapabilityDenied {
135 capability: capability.clone(),
136 });
137 }
138
139 if self.trust == TrustLevel::Untrusted && capability == &read_eval_capability() {
140 return Err(Error::TrustDenied {
141 capability: capability.clone(),
142 trust: self.trust,
143 });
144 }
145
146 Ok(())
147 }
148}
149
150pub fn read_construct_capability() -> CapabilityName {
152 CapabilityName::new("read-construct")
153}
154
155pub fn read_eval_capability() -> CapabilityName {
159 CapabilityName::new("read-eval")
160}
161
162pub fn native_dynamic_load_capability() -> CapabilityName {
164 CapabilityName::new("loader.native")
165}
166
167pub fn macro_expand_capability() -> CapabilityName {
169 CapabilityName::new("macro.expand")
170}
171
172pub fn macro_expand_compile_capability() -> CapabilityName {
174 CapabilityName::new("macro.expand.compile")
175}
176
177pub fn macro_expand_eval_capability() -> CapabilityName {
179 CapabilityName::new("macro.expand.eval")
180}
181
182pub fn macro_expand_read_capability() -> CapabilityName {
184 CapabilityName::new("macro.expand.read")
185}
186
187pub fn eval_fabric_capability() -> CapabilityName {
189 CapabilityName::new("eval.fabric")
190}
191
192pub fn eval_remote_capability() -> CapabilityName {
194 CapabilityName::new("eval.remote")
195}
196
197pub fn control_prompt_capability() -> CapabilityName {
199 CapabilityName::new("control.prompt")
200}
201
202pub fn control_capture_capability() -> CapabilityName {
204 CapabilityName::new("control.capture")
205}
206
207pub fn control_resume_capability() -> CapabilityName {
209 CapabilityName::new("control.resume")
210}
211
212pub fn control_multishot_capability() -> CapabilityName {
214 CapabilityName::new("control.multishot")
215}
216
217pub fn fact_private_capability() -> CapabilityName {
219 CapabilityName::new("kernel.fact.private")
220}
221
222pub fn browse_read_capability() -> CapabilityName {
224 CapabilityName::new("browse.read")
225}
226
227pub fn browse_run_tests_capability() -> CapabilityName {
229 CapabilityName::new("browse.run-tests")
230}
231
232pub fn browse_internal_capability() -> CapabilityName {
234 CapabilityName::new("browse.internal")
235}
236
237pub fn logic_db_write_capability() -> CapabilityName {
239 CapabilityName::new("logic.db.write")
240}
241
242pub fn logic_consult_file_capability() -> CapabilityName {
244 CapabilityName::new("logic.consult.file")
245}
246
247pub fn logic_tool_call_capability() -> CapabilityName {
249 CapabilityName::new("logic.tool-call")
250}
251
252pub fn config_list_impl_capability() -> CapabilityName {
254 CapabilityName::new("config.list.impl")
255}
256
257pub fn config_table_impl_capability() -> CapabilityName {
259 CapabilityName::new("config.table.impl")
260}
261
262pub fn list_force_unbounded_capability() -> CapabilityName {
264 CapabilityName::new("list.force.unbounded")
265}
266
267pub fn table_fs_capability() -> CapabilityName {
269 CapabilityName::new("table.fs")
270}
271
272pub fn table_fs_read_capability() -> CapabilityName {
274 CapabilityName::new("table.fs.read")
275}
276
277pub fn table_fs_write_capability() -> CapabilityName {
279 CapabilityName::new("table.fs.write")
280}
281
282pub fn table_fs_mkdir_capability() -> CapabilityName {
284 CapabilityName::new("table.fs.mkdir")
285}
286
287pub fn table_fs_rmdir_capability() -> CapabilityName {
289 CapabilityName::new("table.fs.rmdir")
290}
291
292pub fn table_db_capability() -> CapabilityName {
294 CapabilityName::new("table.db")
295}
296
297pub fn table_db_read_capability() -> CapabilityName {
299 CapabilityName::new("table.db.read")
300}
301
302pub fn table_db_write_capability() -> CapabilityName {
304 CapabilityName::new("table.db.write")
305}
306
307pub fn table_db_mkdir_capability() -> CapabilityName {
309 CapabilityName::new("table.db.mkdir")
310}
311
312pub fn table_db_rmdir_capability() -> CapabilityName {
314 CapabilityName::new("table.db.rmdir")
315}
316
317pub fn table_remote_capability() -> CapabilityName {
319 CapabilityName::new("table.remote")
320}
321
322pub fn registry_catalog_read_capability() -> CapabilityName {
324 CapabilityName::new("registry.catalog.read")
325}
326
327#[cfg(test)]
328mod tests {
329 use super::{
330 CapabilitySet, ReadPolicy, TrustLevel, read_construct_capability, read_eval_capability,
331 };
332 use crate::Error;
333
334 fn policy(trust: TrustLevel, capabilities: &[crate::CapabilityName]) -> ReadPolicy {
335 ReadPolicy {
336 trust,
337 capabilities: capabilities
338 .iter()
339 .cloned()
340 .fold(CapabilitySet::new(), |set, capability| {
341 set.grant(capability)
342 }),
343 }
344 }
345
346 #[test]
347 fn untrusted_policy_denies_read_eval_even_when_capability_is_present() {
348 let err = policy(TrustLevel::Untrusted, &[read_eval_capability()])
349 .require(&read_eval_capability())
350 .unwrap_err();
351 assert!(matches!(
352 err,
353 Error::TrustDenied { capability, trust }
354 if capability == read_eval_capability() && trust == TrustLevel::Untrusted
355 ));
356 }
357
358 #[test]
359 fn trusted_policy_allows_read_eval_when_capability_is_present() {
360 policy(TrustLevel::TrustedSource, &[read_eval_capability()])
361 .require(&read_eval_capability())
362 .unwrap();
363 }
364
365 #[test]
366 fn non_eval_capabilities_remain_capability_gated() {
367 policy(TrustLevel::Untrusted, &[read_construct_capability()])
368 .require(&read_construct_capability())
369 .unwrap();
370 }
371}