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(Debug)]
69pub struct GrantSeat {
70 cx_id: u64,
75}
76
77impl GrantSeat {
78 pub(crate) fn for_cx(cx_id: u64) -> Self {
79 Self { cx_id }
80 }
81
82 #[cfg(feature = "test-support")]
86 pub fn for_test() -> Self {
87 Self { cx_id: 0 }
88 }
89
90 pub fn grant(&self, cx: &mut crate::Cx, capability: CapabilityName) {
100 assert!(
101 self.cx_id == 0 || self.cx_id == cx.grant_id(),
102 "a GrantSeat may only grant into the Cx it was minted with"
103 );
104 cx.grant_from_host(capability);
105 }
106
107 pub fn grant_named(&self, cx: &mut crate::Cx, capability: &'static str) {
109 self.grant(cx, CapabilityName::new(capability));
110 }
111}
112
113#[derive(Clone, Debug, Default, PartialEq, Eq)]
126pub struct CapabilitySet {
127 granted: BTreeSet<CapabilityName>,
128}
129
130impl CapabilitySet {
131 pub fn new() -> Self {
133 Self::default()
134 }
135
136 pub fn grant(mut self, capability: CapabilityName) -> Self {
138 self.granted.insert(capability);
139 self
140 }
141
142 pub fn insert(&mut self, capability: CapabilityName) {
144 self.granted.insert(capability);
145 }
146
147 pub fn contains(&self, capability: &CapabilityName) -> bool {
149 self.granted.contains(capability)
150 }
151
152 pub fn iter(&self) -> impl Iterator<Item = &CapabilityName> {
154 self.granted.iter()
155 }
156}
157
158#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
163pub enum TrustLevel {
164 #[default]
166 Untrusted,
167 TrustedSource,
169 HostInternal,
171}
172
173#[derive(Clone, Debug, Default, PartialEq, Eq)]
178pub struct ReadPolicy {
179 pub trust: TrustLevel,
181 pub capabilities: CapabilitySet,
183}
184
185impl ReadPolicy {
186 pub fn require(&self, capability: &CapabilityName) -> Result<()> {
192 if !self.capabilities.contains(capability) {
193 return Err(Error::CapabilityDenied {
194 capability: capability.clone(),
195 });
196 }
197
198 if self.trust == TrustLevel::Untrusted && capability == &read_eval_capability() {
199 return Err(Error::TrustDenied {
200 capability: capability.clone(),
201 trust: self.trust,
202 });
203 }
204
205 Ok(())
206 }
207}
208
209pub fn read_construct_capability() -> CapabilityName {
211 CapabilityName::new("read-construct")
212}
213
214pub fn read_eval_capability() -> CapabilityName {
218 CapabilityName::new("read-eval")
219}
220
221pub fn native_dynamic_load_capability() -> CapabilityName {
223 CapabilityName::new("loader.native")
224}
225
226pub fn macro_expand_capability() -> CapabilityName {
228 CapabilityName::new("macro.expand")
229}
230
231pub fn macro_expand_compile_capability() -> CapabilityName {
233 CapabilityName::new("macro.expand.compile")
234}
235
236pub fn macro_expand_eval_capability() -> CapabilityName {
238 CapabilityName::new("macro.expand.eval")
239}
240
241pub fn macro_expand_read_capability() -> CapabilityName {
243 CapabilityName::new("macro.expand.read")
244}
245
246pub fn eval_fabric_capability() -> CapabilityName {
248 CapabilityName::new("eval.fabric")
249}
250
251pub fn eval_remote_capability() -> CapabilityName {
253 CapabilityName::new("eval.remote")
254}
255
256pub fn control_prompt_capability() -> CapabilityName {
258 CapabilityName::new("control.prompt")
259}
260
261pub fn control_capture_capability() -> CapabilityName {
263 CapabilityName::new("control.capture")
264}
265
266pub fn control_resume_capability() -> CapabilityName {
268 CapabilityName::new("control.resume")
269}
270
271pub fn control_multishot_capability() -> CapabilityName {
273 CapabilityName::new("control.multishot")
274}
275
276pub fn fact_private_capability() -> CapabilityName {
278 CapabilityName::new("kernel.fact.private")
279}
280
281pub fn browse_read_capability() -> CapabilityName {
283 CapabilityName::new("browse.read")
284}
285
286pub fn browse_run_tests_capability() -> CapabilityName {
288 CapabilityName::new("browse.run-tests")
289}
290
291pub fn browse_internal_capability() -> CapabilityName {
293 CapabilityName::new("browse.internal")
294}
295
296pub fn logic_db_write_capability() -> CapabilityName {
298 CapabilityName::new("logic.db.write")
299}
300
301pub fn logic_consult_file_capability() -> CapabilityName {
303 CapabilityName::new("logic.consult.file")
304}
305
306pub fn logic_tool_call_capability() -> CapabilityName {
308 CapabilityName::new("logic.tool-call")
309}
310
311pub fn config_list_impl_capability() -> CapabilityName {
313 CapabilityName::new("config.list.impl")
314}
315
316pub fn config_table_impl_capability() -> CapabilityName {
318 CapabilityName::new("config.table.impl")
319}
320
321pub fn list_force_unbounded_capability() -> CapabilityName {
323 CapabilityName::new("list.force.unbounded")
324}
325
326pub fn table_fs_capability() -> CapabilityName {
328 CapabilityName::new("table.fs")
329}
330
331pub fn table_fs_read_capability() -> CapabilityName {
333 CapabilityName::new("table.fs.read")
334}
335
336pub fn table_fs_write_capability() -> CapabilityName {
338 CapabilityName::new("table.fs.write")
339}
340
341pub fn table_fs_mkdir_capability() -> CapabilityName {
343 CapabilityName::new("table.fs.mkdir")
344}
345
346pub fn table_fs_rmdir_capability() -> CapabilityName {
348 CapabilityName::new("table.fs.rmdir")
349}
350
351pub fn table_db_capability() -> CapabilityName {
353 CapabilityName::new("table.db")
354}
355
356pub fn table_db_read_capability() -> CapabilityName {
358 CapabilityName::new("table.db.read")
359}
360
361pub fn table_db_write_capability() -> CapabilityName {
363 CapabilityName::new("table.db.write")
364}
365
366pub fn table_db_mkdir_capability() -> CapabilityName {
368 CapabilityName::new("table.db.mkdir")
369}
370
371pub fn table_db_rmdir_capability() -> CapabilityName {
373 CapabilityName::new("table.db.rmdir")
374}
375
376pub fn table_remote_capability() -> CapabilityName {
378 CapabilityName::new("table.remote")
379}
380
381pub fn registry_catalog_read_capability() -> CapabilityName {
383 CapabilityName::new("registry.catalog.read")
384}
385
386#[cfg(test)]
387mod tests {
388 use super::{
389 CapabilitySet, ReadPolicy, TrustLevel, read_construct_capability, read_eval_capability,
390 };
391 use crate::Error;
392
393 fn policy(trust: TrustLevel, capabilities: &[crate::CapabilityName]) -> ReadPolicy {
394 ReadPolicy {
395 trust,
396 capabilities: capabilities
397 .iter()
398 .cloned()
399 .fold(CapabilitySet::new(), |set, capability| {
400 set.grant(capability)
401 }),
402 }
403 }
404
405 #[test]
406 fn untrusted_policy_denies_read_eval_even_when_capability_is_present() {
407 let err = policy(TrustLevel::Untrusted, &[read_eval_capability()])
408 .require(&read_eval_capability())
409 .unwrap_err();
410 assert!(matches!(
411 err,
412 Error::TrustDenied { capability, trust }
413 if capability == read_eval_capability() && trust == TrustLevel::Untrusted
414 ));
415 }
416
417 #[test]
418 fn trusted_policy_allows_read_eval_when_capability_is_present() {
419 policy(TrustLevel::TrustedSource, &[read_eval_capability()])
420 .require(&read_eval_capability())
421 .unwrap();
422 }
423
424 #[test]
425 fn non_eval_capabilities_remain_capability_gated() {
426 policy(TrustLevel::Untrusted, &[read_construct_capability()])
427 .require(&read_construct_capability())
428 .unwrap();
429 }
430
431 #[test]
432 fn grant_seat_grants_a_capability_into_a_cx() {
433 use std::sync::Arc;
434
435 use crate::{Cx, DefaultFactory, NoopEvalPolicy};
436
437 let cap = read_eval_capability();
438 let (mut cx, seat) = Cx::new_seated(Arc::new(NoopEvalPolicy), Arc::new(DefaultFactory));
439 assert!(cx.require(&cap).is_err());
441 seat.grant(&mut cx, cap.clone());
442 assert!(cx.require(&cap).is_ok());
443 }
444
445 #[test]
446 #[should_panic(expected = "minted with")]
447 fn a_seat_cannot_grant_into_a_foreign_cx() {
448 use std::sync::Arc;
449
450 use crate::{Cx, DefaultFactory, NoopEvalPolicy};
451
452 let (mut victim, _victim_seat) =
456 Cx::new_seated(Arc::new(NoopEvalPolicy), Arc::new(DefaultFactory));
457 let (_throwaway, forged) =
458 Cx::new_seated(Arc::new(NoopEvalPolicy), Arc::new(DefaultFactory));
459 forged.grant(&mut victim, read_eval_capability()); }
461
462 #[cfg(feature = "test-support")]
463 #[test]
464 fn grant_seat_for_test_is_available_under_the_feature() {
465 let _seat = super::GrantSeat::for_test();
467 }
468}