Skip to main content

sim_kernel/
claim.rs

1//! Claims: the contract for asserted facts about runtime subjects.
2//!
3//! The kernel defines the [`Claim`] record, its kinds, visibility, and the
4//! [`ClaimPattern`] match contract; libraries supply the claims and the stores
5//! that hold them.
6
7use crate::{
8    capability::CapabilityName,
9    datum::Datum,
10    datum_store::DatumStore,
11    error::Result,
12    id::Symbol,
13    ref_id::{ContentId, Coordinate, HandleId, Ref},
14};
15
16/// An asserted fact about a runtime subject: a `subject`/`predicate`/`object`
17/// triple plus kind, evidence, required capabilities, and visibility.
18///
19/// The kernel defines the record and its canonical datum form; libraries supply
20/// the claims and the [`FactStore`](crate::fact_store::FactStore)s that hold
21/// them.
22///
23/// # Examples
24///
25/// ```
26/// # use sim_kernel::{Claim, ClaimKind, Ref, Symbol};
27/// let claim = Claim::new(
28///     Ref::Symbol(Symbol::qualified("core", "List")),
29///     Symbol::new("kind"),
30///     Ref::Symbol(Symbol::qualified("core", "class")),
31/// );
32/// assert_eq!(claim.kind, ClaimKind::Asserted);
33/// // The claim has a deterministic content datum.
34/// let _ = claim.canonical_datum();
35/// ```
36#[derive(Clone, Debug, PartialEq, Eq)]
37pub struct Claim {
38    /// Content id of the claim once interned; `None` before insertion.
39    pub id: Option<ContentId>,
40    /// The subject the claim is about.
41    pub subject: Ref,
42    /// The predicate relating subject to object.
43    pub predicate: Symbol,
44    /// The object of the claim.
45    pub object: Ref,
46    /// How the claim was established.
47    pub kind: ClaimKind,
48    /// Supporting references for the claim.
49    pub evidence: Vec<Ref>,
50    /// Capabilities required to see the claim under gated or private visibility.
51    pub requires: Vec<CapabilityName>,
52    /// Read visibility policy for the claim.
53    pub visibility: Visibility,
54}
55
56/// How a [`Claim`] was established.
57#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
58pub enum ClaimKind {
59    /// Stated directly by a caller.
60    Asserted,
61    /// Inferred from other claims.
62    Derived,
63    /// Recorded from observation.
64    Observed,
65    /// Withdrawn; hidden from queries unless revoked claims are requested.
66    Revoked,
67}
68
69/// Read visibility policy for a [`Claim`].
70#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
71pub enum Visibility {
72    /// Readable by anyone.
73    Public,
74    /// Readable only when the caller holds the claim's required capabilities.
75    CapabilityGated,
76    /// Readable only with the fact-private capability and required capabilities.
77    Private,
78}
79
80/// Query pattern matched against stored [`Claim`]s; `None` fields are wildcards.
81#[derive(Clone, Debug, Default, PartialEq, Eq)]
82pub struct ClaimPattern {
83    /// Subject to match, or any subject when `None`.
84    pub subject: Option<Ref>,
85    /// Predicate to match, or any predicate when `None`.
86    pub predicate: Option<Symbol>,
87    /// Object to match, or any object when `None`.
88    pub object: Option<Ref>,
89    /// Whether revoked claims are included in results.
90    pub include_revoked: bool,
91}
92
93impl Claim {
94    /// Creates an asserted, public claim with no evidence or requirements.
95    pub fn new(subject: Ref, predicate: Symbol, object: Ref) -> Self {
96        Self {
97            id: None,
98            subject,
99            predicate,
100            object,
101            kind: ClaimKind::Asserted,
102            evidence: Vec::new(),
103            requires: Vec::new(),
104            visibility: Visibility::Public,
105        }
106    }
107
108    /// Creates a public claim; alias for [`Claim::new`].
109    pub fn public(subject: Ref, predicate: Symbol, object: Ref) -> Self {
110        Self::new(subject, predicate, object)
111    }
112
113    /// Builds a claim whose object is a [`Datum`] interned into `store` and
114    /// referenced by content.
115    pub fn content_object(
116        store: &mut dyn DatumStore,
117        subject: Ref,
118        predicate: Symbol,
119        object: Datum,
120    ) -> Result<Self> {
121        Ok(Self::new(
122            subject,
123            predicate,
124            Self::intern_object(store, object)?,
125        ))
126    }
127
128    /// Interns `object` into `store` and returns a content [`Ref`] to it.
129    pub fn intern_object(store: &mut dyn DatumStore, object: Datum) -> Result<Ref> {
130        store.intern(object).map(Ref::Content)
131    }
132
133    /// Returns the claim with its [`ClaimKind`] set.
134    pub fn with_kind(mut self, kind: ClaimKind) -> Self {
135        self.kind = kind;
136        self
137    }
138
139    /// Returns the claim with its [`Visibility`] set.
140    pub fn with_visibility(mut self, visibility: Visibility) -> Self {
141        self.visibility = visibility;
142        self
143    }
144
145    /// Returns the claim with its evidence references replaced.
146    pub fn with_evidence(mut self, evidence: Vec<Ref>) -> Self {
147        self.evidence = evidence;
148        self
149    }
150
151    /// Returns the claim with one required capability appended.
152    pub fn requiring(mut self, capability: CapabilityName) -> Self {
153        self.requires.push(capability);
154        self
155    }
156
157    /// Returns the claim with its required capabilities replaced.
158    pub fn with_requirements(mut self, requires: Vec<CapabilityName>) -> Self {
159        self.requires = requires;
160        self
161    }
162
163    /// Returns the deterministic [`Datum`] form used to derive the content id.
164    pub fn canonical_datum(&self) -> Datum {
165        Datum::Node {
166            tag: core_symbol("claim"),
167            fields: vec![
168                (Symbol::new("subject"), ref_datum(&self.subject)),
169                (
170                    Symbol::new("predicate"),
171                    Datum::Symbol(self.predicate.clone()),
172                ),
173                (Symbol::new("object"), ref_datum(&self.object)),
174                (Symbol::new("kind"), Datum::Symbol(self.kind.as_symbol())),
175                (
176                    Symbol::new("evidence"),
177                    Datum::Vector(self.evidence.iter().map(ref_datum).collect()),
178                ),
179                (
180                    Symbol::new("requires"),
181                    Datum::Set(requirement_data(&self.requires)),
182                ),
183                (
184                    Symbol::new("visibility"),
185                    Datum::Symbol(self.visibility.as_symbol()),
186                ),
187            ],
188        }
189    }
190
191    /// Interns the claim's canonical datum and returns its content id.
192    pub fn content_id(&self, store: &mut dyn DatumStore) -> Result<ContentId> {
193        store.intern(self.canonical_datum())
194    }
195}
196
197impl ClaimKind {
198    /// Returns the `core/*` symbol naming this kind.
199    pub fn as_symbol(self) -> Symbol {
200        match self {
201            Self::Asserted => core_symbol("asserted"),
202            Self::Derived => core_symbol("derived"),
203            Self::Observed => core_symbol("observed"),
204            Self::Revoked => core_symbol("revoked"),
205        }
206    }
207}
208
209impl Visibility {
210    /// Returns the `core/*` symbol naming this visibility.
211    pub fn as_symbol(self) -> Symbol {
212        match self {
213            Self::Public => core_symbol("public"),
214            Self::CapabilityGated => core_symbol("capability-gated"),
215            Self::Private => core_symbol("private"),
216        }
217    }
218}
219
220impl ClaimPattern {
221    /// Returns a pattern that matches every claim (all fields wildcard).
222    pub fn any() -> Self {
223        Self::default()
224    }
225
226    /// Returns a pattern fixing subject, predicate, and object exactly.
227    pub fn exact(subject: Ref, predicate: Symbol, object: Ref) -> Self {
228        Self {
229            subject: Some(subject),
230            predicate: Some(predicate),
231            object: Some(object),
232            include_revoked: false,
233        }
234    }
235
236    /// Returns the pattern with revoked claims included in results.
237    pub fn include_revoked(mut self) -> Self {
238        self.include_revoked = true;
239        self
240    }
241}
242
243fn requirement_data(requires: &[CapabilityName]) -> Vec<Datum> {
244    let mut requirements = requires
245        .iter()
246        .map(|capability| Datum::String(capability.as_str().to_owned()))
247        .collect::<Vec<_>>();
248    requirements.sort_by_key(|datum| datum.canonical_bytes().unwrap_or_default());
249    requirements.dedup();
250    requirements
251}
252
253fn ref_datum(reference: &Ref) -> Datum {
254    match reference {
255        Ref::Symbol(symbol) => Datum::Node {
256            tag: core_symbol("ref"),
257            fields: vec![
258                (Symbol::new("kind"), Datum::Symbol(core_symbol("symbol"))),
259                (Symbol::new("symbol"), Datum::Symbol(symbol.clone())),
260            ],
261        },
262        Ref::Content(content) => Datum::Node {
263            tag: core_symbol("ref"),
264            fields: vec![
265                (Symbol::new("kind"), Datum::Symbol(core_symbol("content"))),
266                (Symbol::new("content"), content_id_datum(content)),
267            ],
268        },
269        Ref::Handle(handle) => Datum::Node {
270            tag: core_symbol("ref"),
271            fields: vec![
272                (Symbol::new("kind"), Datum::Symbol(core_symbol("handle"))),
273                (Symbol::new("id"), handle_id_datum(*handle)),
274            ],
275        },
276        Ref::Coord(coordinate) => coordinate_datum(coordinate),
277    }
278}
279
280fn coordinate_datum(coordinate: &Coordinate) -> Datum {
281    Datum::Node {
282        tag: core_symbol("ref"),
283        fields: vec![
284            (Symbol::new("kind"), Datum::Symbol(core_symbol("coord"))),
285            (
286                Symbol::new("space"),
287                Datum::Symbol(coordinate.space.clone()),
288            ),
289            (
290                Symbol::new("ordinal"),
291                content_id_datum(&coordinate.ordinal),
292            ),
293        ],
294    }
295}
296
297fn content_id_datum(content: &ContentId) -> Datum {
298    Datum::Node {
299        tag: core_symbol("content-id"),
300        fields: vec![
301            (
302                Symbol::new("algorithm"),
303                Datum::Symbol(content.algorithm.clone()),
304            ),
305            (Symbol::new("bytes"), Datum::Bytes(content.bytes.to_vec())),
306        ],
307    }
308}
309
310fn handle_id_datum(handle: HandleId) -> Datum {
311    Datum::Bytes(handle.0.to_be_bytes().to_vec())
312}
313
314fn core_symbol(name: &str) -> Symbol {
315    Symbol::qualified("core", name)
316}