Skip to main content

sim_kernel/
capability.rs

1//! Capability names and sets: the contract for gating privileged operations.
2//!
3//! The kernel defines capability identity, trust levels, and read policy plus
4//! the well-known core capability names; libraries decide what each capability
5//! authorizes.
6
7use std::{collections::BTreeSet, fmt, sync::Arc};
8
9use crate::{
10    error::{Error, Result},
11    id::Symbol,
12};
13
14/// The identity of a capability: the token an operation requires to run.
15///
16/// Capability names are the kernel's contract for gating privileged behavior;
17/// libraries decide what each name authorizes. See the README section
18/// "Capabilities and trust".
19///
20/// # Examples
21///
22/// ```
23/// # use sim_kernel::CapabilityName;
24/// let cap = CapabilityName::new("table.fs.read");
25/// assert_eq!(cap.as_str(), "table.fs.read");
26/// assert_eq!(cap.as_symbol().to_string(), "capability/table.fs.read");
27/// ```
28#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
29pub struct CapabilityName(Arc<str>);
30
31impl CapabilityName {
32    /// Interns a capability name from a string.
33    pub fn new(name: impl Into<Arc<str>>) -> Self {
34        Self(name.into())
35    }
36
37    /// Returns the capability name as a string slice.
38    pub fn as_str(&self) -> &str {
39        &self.0
40    }
41
42    /// Returns the name as a `capability`-qualified [`Symbol`].
43    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/// A set of granted capabilities, used as the capability state of a [`Cx`].
55///
56/// [`Cx`]: crate::Cx
57///
58/// # Examples
59///
60/// ```
61/// # use sim_kernel::{CapabilityName, capability::CapabilitySet};
62/// let cap = CapabilityName::new("read-construct");
63/// let granted = CapabilitySet::new().grant(cap.clone());
64/// assert!(granted.contains(&cap));
65/// ```
66#[derive(Clone, Debug, Default, PartialEq, Eq)]
67pub struct CapabilitySet {
68    granted: BTreeSet<CapabilityName>,
69}
70
71impl CapabilitySet {
72    /// Builds an empty capability set.
73    pub fn new() -> Self {
74        Self::default()
75    }
76
77    /// Returns the set with one more capability granted (builder style).
78    pub fn grant(mut self, capability: CapabilityName) -> Self {
79        self.granted.insert(capability);
80        self
81    }
82
83    /// Grants a capability in place.
84    pub fn insert(&mut self, capability: CapabilityName) {
85        self.granted.insert(capability);
86    }
87
88    /// Reports whether the capability is granted.
89    pub fn contains(&self, capability: &CapabilityName) -> bool {
90        self.granted.contains(capability)
91    }
92
93    /// Iterates the granted capabilities in sorted order.
94    pub fn iter(&self) -> impl Iterator<Item = &CapabilityName> {
95        self.granted.iter()
96    }
97}
98
99/// The trust level of a source, gating capabilities beyond mere possession.
100///
101/// Even a granted capability may be denied when trust is insufficient; see
102/// [`ReadPolicy::require`].
103#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
104pub enum TrustLevel {
105    /// Untrusted input; eval-class capabilities are denied even if granted.
106    #[default]
107    Untrusted,
108    /// A source trusted to request evaluation.
109    TrustedSource,
110    /// The host itself, the most trusted level.
111    HostInternal,
112}
113
114/// The trust level and capability set governing a read.
115///
116/// Pairs a [`TrustLevel`] with a [`CapabilitySet`] so the kernel can both
117/// check possession and apply trust-sensitive policy.
118#[derive(Clone, Debug, Default, PartialEq, Eq)]
119pub struct ReadPolicy {
120    /// The trust level of the source being read.
121    pub trust: TrustLevel,
122    /// The capabilities granted to the read.
123    pub capabilities: CapabilitySet,
124}
125
126impl ReadPolicy {
127    /// Demands a capability, applying trust policy on top of possession.
128    ///
129    /// Returns [`Error::CapabilityDenied`] when the capability is not granted,
130    /// and [`Error::TrustDenied`] when an [`Untrusted`](TrustLevel::Untrusted)
131    /// source requests the read-eval capability.
132    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
150/// The capability gating read-time construction (`read-construct`).
151pub fn read_construct_capability() -> CapabilityName {
152    CapabilityName::new("read-construct")
153}
154
155/// The capability gating read-time evaluation (`read-eval`).
156///
157/// Trust-sensitive: denied for [`Untrusted`](TrustLevel::Untrusted) sources.
158pub fn read_eval_capability() -> CapabilityName {
159    CapabilityName::new("read-eval")
160}
161
162/// The capability gating native dynamic library loading (`loader.native`).
163pub fn native_dynamic_load_capability() -> CapabilityName {
164    CapabilityName::new("loader.native")
165}
166
167/// The capability gating macro expansion (`macro.expand`).
168pub fn macro_expand_capability() -> CapabilityName {
169    CapabilityName::new("macro.expand")
170}
171
172/// The capability gating compile-phase macro expansion (`macro.expand.compile`).
173pub fn macro_expand_compile_capability() -> CapabilityName {
174    CapabilityName::new("macro.expand.compile")
175}
176
177/// The capability gating eval-phase macro expansion (`macro.expand.eval`).
178pub fn macro_expand_eval_capability() -> CapabilityName {
179    CapabilityName::new("macro.expand.eval")
180}
181
182/// The capability gating read-phase macro expansion (`macro.expand.read`).
183pub fn macro_expand_read_capability() -> CapabilityName {
184    CapabilityName::new("macro.expand.read")
185}
186
187/// The capability gating use of the eval fabric (`eval.fabric`).
188pub fn eval_fabric_capability() -> CapabilityName {
189    CapabilityName::new("eval.fabric")
190}
191
192/// The capability gating remote evaluation (`eval.remote`).
193pub fn eval_remote_capability() -> CapabilityName {
194    CapabilityName::new("eval.remote")
195}
196
197/// The capability gating control prompts (`control.prompt`).
198pub fn control_prompt_capability() -> CapabilityName {
199    CapabilityName::new("control.prompt")
200}
201
202/// The capability gating control-stack capture (`control.capture`).
203pub fn control_capture_capability() -> CapabilityName {
204    CapabilityName::new("control.capture")
205}
206
207/// The capability gating continuation resumption (`control.resume`).
208pub fn control_resume_capability() -> CapabilityName {
209    CapabilityName::new("control.resume")
210}
211
212/// The capability gating multi-shot continuations (`control.multishot`).
213pub fn control_multishot_capability() -> CapabilityName {
214    CapabilityName::new("control.multishot")
215}
216
217/// The capability gating access to private facts (`kernel.fact.private`).
218pub fn fact_private_capability() -> CapabilityName {
219    CapabilityName::new("kernel.fact.private")
220}
221
222/// The capability gating browse reads (`browse.read`).
223pub fn browse_read_capability() -> CapabilityName {
224    CapabilityName::new("browse.read")
225}
226
227/// The capability gating browse-driven test runs (`browse.run-tests`).
228pub fn browse_run_tests_capability() -> CapabilityName {
229    CapabilityName::new("browse.run-tests")
230}
231
232/// The capability gating internal browse surfaces (`browse.internal`).
233pub fn browse_internal_capability() -> CapabilityName {
234    CapabilityName::new("browse.internal")
235}
236
237/// The capability gating logic-database writes (`logic.db.write`).
238pub fn logic_db_write_capability() -> CapabilityName {
239    CapabilityName::new("logic.db.write")
240}
241
242/// The capability gating logic file consulting (`logic.consult.file`).
243pub fn logic_consult_file_capability() -> CapabilityName {
244    CapabilityName::new("logic.consult.file")
245}
246
247/// The capability gating logic tool calls (`logic.tool-call`).
248pub fn logic_tool_call_capability() -> CapabilityName {
249    CapabilityName::new("logic.tool-call")
250}
251
252/// The capability gating the configured list implementation (`config.list.impl`).
253pub fn config_list_impl_capability() -> CapabilityName {
254    CapabilityName::new("config.list.impl")
255}
256
257/// The capability gating the configured table implementation (`config.table.impl`).
258pub fn config_table_impl_capability() -> CapabilityName {
259    CapabilityName::new("config.table.impl")
260}
261
262/// The capability gating unbounded list forcing (`list.force.unbounded`).
263pub fn list_force_unbounded_capability() -> CapabilityName {
264    CapabilityName::new("list.force.unbounded")
265}
266
267/// The capability gating filesystem-backed tables (`table.fs`).
268pub fn table_fs_capability() -> CapabilityName {
269    CapabilityName::new("table.fs")
270}
271
272/// The capability gating filesystem table reads (`table.fs.read`).
273pub fn table_fs_read_capability() -> CapabilityName {
274    CapabilityName::new("table.fs.read")
275}
276
277/// The capability gating filesystem table writes (`table.fs.write`).
278pub fn table_fs_write_capability() -> CapabilityName {
279    CapabilityName::new("table.fs.write")
280}
281
282/// The capability gating filesystem table directory creation (`table.fs.mkdir`).
283pub fn table_fs_mkdir_capability() -> CapabilityName {
284    CapabilityName::new("table.fs.mkdir")
285}
286
287/// The capability gating filesystem table directory removal (`table.fs.rmdir`).
288pub fn table_fs_rmdir_capability() -> CapabilityName {
289    CapabilityName::new("table.fs.rmdir")
290}
291
292/// The capability gating database-backed tables (`table.db`).
293pub fn table_db_capability() -> CapabilityName {
294    CapabilityName::new("table.db")
295}
296
297/// The capability gating database table reads (`table.db.read`).
298pub fn table_db_read_capability() -> CapabilityName {
299    CapabilityName::new("table.db.read")
300}
301
302/// The capability gating database table writes (`table.db.write`).
303pub fn table_db_write_capability() -> CapabilityName {
304    CapabilityName::new("table.db.write")
305}
306
307/// The capability gating database table directory creation (`table.db.mkdir`).
308pub fn table_db_mkdir_capability() -> CapabilityName {
309    CapabilityName::new("table.db.mkdir")
310}
311
312/// The capability gating database table directory removal (`table.db.rmdir`).
313pub fn table_db_rmdir_capability() -> CapabilityName {
314    CapabilityName::new("table.db.rmdir")
315}
316
317/// The capability gating remote tables (`table.remote`).
318pub fn table_remote_capability() -> CapabilityName {
319    CapabilityName::new("table.remote")
320}
321
322/// The capability gating registry catalog reads (`registry.catalog.read`).
323pub 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}