Skip to main content

sim_kernel/
standard.rs

1//! Standard-distribution metadata: the profile and organ claim vocabulary.
2//!
3//! The kernel defines the predicate keys and operation keys by which the
4//! standard distribution describes itself; the standard distribution itself is
5//! a lib loaded by default, not kernel behavior.
6
7use crate::{
8    card::{card_kind_predicate, card_ops_predicate, card_tests_predicate},
9    claim::{Claim, ClaimKind, ClaimPattern},
10    env::Cx,
11    error::Result,
12    id::{LibId, Symbol},
13    ref_id::Ref,
14    term::OpKey,
15};
16
17/// The card-kind symbol for a standard-distribution profile.
18pub fn standard_profile_kind() -> Symbol {
19    standard_symbol("profile")
20}
21
22/// The card-kind symbol for a standard-distribution organ.
23pub fn standard_organ_kind() -> Symbol {
24    standard_symbol("organ")
25}
26
27/// The claim predicate naming a profile.
28pub fn standard_profile_predicate() -> Symbol {
29    standard_symbol("profile")
30}
31
32/// The claim predicate relating a profile to one of its organs.
33pub fn standard_organ_predicate() -> Symbol {
34    standard_symbol("organ")
35}
36
37/// The claim predicate carrying a fidelity badge.
38pub fn standard_fidelity_predicate() -> Symbol {
39    standard_symbol("fidelity")
40}
41
42/// The claim predicate carrying fidelity evidence.
43pub fn standard_evidence_predicate() -> Symbol {
44    standard_symbol("evidence")
45}
46
47/// Builds a `control`-namespace operation key for the given name at version 1.
48pub fn standard_control_op_key(name: &str) -> OpKey {
49    OpKey::new(Symbol::new("control"), Symbol::new(name), 1)
50}
51
52/// Publishes the claims describing a profile: its kind, organs, and tests.
53///
54/// Each claim is inserted once; re-running is idempotent.
55pub fn publish_profile_claims(
56    cx: &mut Cx,
57    profile: Symbol,
58    organs: impl IntoIterator<Item = Symbol>,
59    conformance_tests: impl IntoIterator<Item = Symbol>,
60) -> Result<()> {
61    publish_profile_claims_with_owner(cx, None, profile, organs, conformance_tests)
62}
63
64/// Publishes profile claims and records newly inserted facts under `lib_id`.
65pub fn publish_profile_claims_for_lib(
66    cx: &mut Cx,
67    lib_id: LibId,
68    profile: Symbol,
69    organs: impl IntoIterator<Item = Symbol>,
70    conformance_tests: impl IntoIterator<Item = Symbol>,
71) -> Result<()> {
72    publish_profile_claims_with_owner(cx, Some(lib_id), profile, organs, conformance_tests)
73}
74
75fn publish_profile_claims_with_owner(
76    cx: &mut Cx,
77    owner: Option<LibId>,
78    profile: Symbol,
79    organs: impl IntoIterator<Item = Symbol>,
80    conformance_tests: impl IntoIterator<Item = Symbol>,
81) -> Result<()> {
82    let subject = Ref::Symbol(profile.clone());
83    insert_once(
84        cx,
85        owner,
86        subject.clone(),
87        card_kind_predicate(),
88        Ref::Symbol(standard_profile_kind()),
89    )?;
90    for organ in organs {
91        insert_once(
92            cx,
93            owner,
94            subject.clone(),
95            standard_organ_predicate(),
96            Ref::Symbol(organ),
97        )?;
98    }
99    for test in conformance_tests {
100        insert_once(
101            cx,
102            owner,
103            subject.clone(),
104            card_tests_predicate(),
105            Ref::Symbol(test),
106        )?;
107    }
108    insert_once(
109        cx,
110        owner,
111        subject,
112        standard_profile_predicate(),
113        Ref::Symbol(profile),
114    )
115}
116
117/// Publishes the claims describing an organ: its kind and the ops it provides.
118///
119/// Each claim is inserted once; re-running is idempotent.
120pub fn publish_organ_claims(
121    cx: &mut Cx,
122    organ: Symbol,
123    ops: impl IntoIterator<Item = OpKey>,
124) -> Result<()> {
125    publish_organ_claims_with_owner(cx, None, organ, ops)
126}
127
128/// Publishes organ claims and records newly inserted facts under `lib_id`.
129pub fn publish_organ_claims_for_lib(
130    cx: &mut Cx,
131    lib_id: LibId,
132    organ: Symbol,
133    ops: impl IntoIterator<Item = OpKey>,
134) -> Result<()> {
135    publish_organ_claims_with_owner(cx, Some(lib_id), organ, ops)
136}
137
138fn publish_organ_claims_with_owner(
139    cx: &mut Cx,
140    owner: Option<LibId>,
141    organ: Symbol,
142    ops: impl IntoIterator<Item = OpKey>,
143) -> Result<()> {
144    let subject = Ref::Symbol(organ);
145    insert_once(
146        cx,
147        owner,
148        subject.clone(),
149        card_kind_predicate(),
150        Ref::Symbol(standard_organ_kind()),
151    )?;
152    for op in ops {
153        insert_once(
154            cx,
155            owner,
156            subject.clone(),
157            card_ops_predicate(),
158            Ref::Symbol(op_symbol(&op)),
159        )?;
160    }
161    Ok(())
162}
163
164/// Publishes a fidelity badge for a subject together with its evidence.
165///
166/// The badge and the observed evidence claim are each inserted once.
167pub fn publish_fidelity_badge(
168    cx: &mut Cx,
169    subject: Ref,
170    badge: Symbol,
171    evidence: Ref,
172) -> Result<()> {
173    publish_fidelity_badge_with_owner(cx, None, subject, badge, evidence)
174}
175
176/// Publishes a fidelity badge and records newly inserted facts under `lib_id`.
177pub fn publish_fidelity_badge_for_lib(
178    cx: &mut Cx,
179    lib_id: LibId,
180    subject: Ref,
181    badge: Symbol,
182    evidence: Ref,
183) -> Result<()> {
184    publish_fidelity_badge_with_owner(cx, Some(lib_id), subject, badge, evidence)
185}
186
187fn publish_fidelity_badge_with_owner(
188    cx: &mut Cx,
189    owner: Option<LibId>,
190    subject: Ref,
191    badge: Symbol,
192    evidence: Ref,
193) -> Result<()> {
194    insert_once(
195        cx,
196        owner,
197        subject.clone(),
198        standard_fidelity_predicate(),
199        Ref::Symbol(badge),
200    )?;
201    let exists = !cx
202        .query_facts(ClaimPattern::exact(
203            subject.clone(),
204            standard_evidence_predicate(),
205            evidence.clone(),
206        ))?
207        .is_empty();
208    if !exists {
209        insert_claim(
210            cx,
211            owner,
212            Claim::public(subject, standard_evidence_predicate(), evidence)
213                .with_kind(ClaimKind::Observed),
214        )?;
215    }
216    Ok(())
217}
218
219fn insert_once(
220    cx: &mut Cx,
221    owner: Option<LibId>,
222    subject: Ref,
223    predicate: Symbol,
224    object: Ref,
225) -> Result<()> {
226    let exists = !cx
227        .query_facts(ClaimPattern::exact(
228            subject.clone(),
229            predicate.clone(),
230            object.clone(),
231        ))?
232        .is_empty();
233    if !exists {
234        insert_claim(cx, owner, Claim::public(subject, predicate, object))?;
235    }
236    Ok(())
237}
238
239fn insert_claim(cx: &mut Cx, owner: Option<LibId>, claim: Claim) -> Result<()> {
240    match owner {
241        Some(lib_id) => {
242            cx.insert_fact_for_lib(lib_id, claim)?;
243        }
244        None => {
245            cx.insert_fact(claim)?;
246        }
247    }
248    Ok(())
249}
250
251fn op_symbol(op: &OpKey) -> Symbol {
252    Symbol::qualified(
253        op.namespace.to_string(),
254        format!("{}.v{}", op.name, op.version),
255    )
256}
257
258fn standard_symbol(name: &str) -> Symbol {
259    Symbol::qualified("standard", name)
260}
261
262#[cfg(test)]
263mod tests {
264    use super::*;
265    use crate::{DefaultFactory, Expr, NoopEvalPolicy, card::card_for_ref};
266    use std::sync::Arc;
267
268    #[test]
269    fn profile_organs_tests_and_fidelity_are_claims() {
270        let mut cx = Cx::new(Arc::new(NoopEvalPolicy), Arc::new(DefaultFactory));
271        let profile = Symbol::qualified("standard", "rust-clean");
272        let organ = Symbol::qualified("standard", "reader");
273        let test = Symbol::qualified("test", "reader-roundtrip");
274
275        publish_profile_claims(&mut cx, profile.clone(), [organ.clone()], [test.clone()]).unwrap();
276        publish_organ_claims(&mut cx, organ.clone(), [standard_control_op_key("prompt")]).unwrap();
277        publish_fidelity_badge(
278            &mut cx,
279            Ref::Symbol(profile.clone()),
280            Symbol::qualified("standard", "full"),
281            Ref::Symbol(test.clone()),
282        )
283        .unwrap();
284
285        assert_has_claim(
286            &cx,
287            Ref::Symbol(profile.clone()),
288            standard_organ_predicate(),
289            Ref::Symbol(organ),
290        );
291        assert_has_claim(
292            &cx,
293            Ref::Symbol(profile),
294            card_tests_predicate(),
295            Ref::Symbol(test),
296        );
297    }
298
299    #[test]
300    fn profile_and_organ_claims_project_to_cards() {
301        let mut cx = Cx::new(Arc::new(NoopEvalPolicy), Arc::new(DefaultFactory));
302        let profile = Symbol::qualified("standard", "rust-clean");
303        let organ = Symbol::qualified("standard", "reader");
304        let test = Symbol::qualified("test", "reader-roundtrip");
305
306        publish_profile_claims(&mut cx, profile.clone(), [organ.clone()], [test.clone()]).unwrap();
307        publish_organ_claims(&mut cx, organ.clone(), [standard_control_op_key("prompt")]).unwrap();
308        publish_fidelity_badge(
309            &mut cx,
310            Ref::Symbol(profile.clone()),
311            Symbol::qualified("standard", "full"),
312            Ref::Symbol(test.clone()),
313        )
314        .unwrap();
315
316        let profile_card = card_expr(&mut cx, Ref::Symbol(profile));
317        assert_eq!(
318            table_value(&profile_card, "kind"),
319            Some(&Expr::Symbol(standard_profile_kind()))
320        );
321        assert_list_contains_symbol(table_value(&profile_card, "tests").unwrap(), test);
322
323        let organ_card = card_expr(&mut cx, Ref::Symbol(organ));
324        assert_eq!(
325            table_value(&organ_card, "kind"),
326            Some(&Expr::Symbol(standard_organ_kind()))
327        );
328        assert_list_contains_symbol(
329            table_value(&organ_card, "ops").unwrap(),
330            Symbol::qualified("control", "prompt.v1"),
331        );
332    }
333
334    fn assert_has_claim(cx: &Cx, subject: Ref, predicate: Symbol, object: Ref) {
335        let claims = cx
336            .query_facts(ClaimPattern::exact(subject, predicate, object))
337            .unwrap();
338        assert_eq!(claims.len(), 1);
339    }
340
341    fn card_expr(cx: &mut Cx, subject: Ref) -> Expr {
342        card_for_ref(cx, subject)
343            .unwrap()
344            .object()
345            .as_expr(cx)
346            .unwrap()
347    }
348
349    fn table_value<'a>(expr: &'a Expr, key: &str) -> Option<&'a Expr> {
350        let Expr::Map(entries) = expr else {
351            return None;
352        };
353        entries.iter().find_map(|(entry_key, entry_value)| {
354            let Expr::Symbol(entry_key) = entry_key else {
355                return None;
356            };
357            (entry_key == &Symbol::new(key)).then_some(entry_value)
358        })
359    }
360
361    fn assert_list_contains_symbol(expr: &Expr, expected: Symbol) {
362        assert!(matches!(expr, Expr::List(_)), "expected list");
363        let Expr::List(items) = expr else {
364            return;
365        };
366        assert!(
367            items
368                .iter()
369                .any(|item| item == &Expr::Symbol(expected.clone())),
370            "expected list to contain {expected}"
371        );
372    }
373}