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(
271            Arc::new(NoopEvalPolicy),
272            Arc::new(DefaultFactory),
273            crate::HandleSeed::new(7),
274        );
275        let profile = Symbol::qualified("standard", "rust-clean");
276        let organ = Symbol::qualified("standard", "reader");
277        let test = Symbol::qualified("test", "reader-roundtrip");
278
279        publish_profile_claims(&mut cx, profile.clone(), [organ.clone()], [test.clone()]).unwrap();
280        publish_organ_claims(&mut cx, organ.clone(), [standard_control_op_key("prompt")]).unwrap();
281        publish_fidelity_badge(
282            &mut cx,
283            Ref::Symbol(profile.clone()),
284            Symbol::qualified("standard", "full"),
285            Ref::Symbol(test.clone()),
286        )
287        .unwrap();
288
289        assert_has_claim(
290            &cx,
291            Ref::Symbol(profile.clone()),
292            standard_organ_predicate(),
293            Ref::Symbol(organ),
294        );
295        assert_has_claim(
296            &cx,
297            Ref::Symbol(profile),
298            card_tests_predicate(),
299            Ref::Symbol(test),
300        );
301    }
302
303    #[test]
304    fn profile_and_organ_claims_project_to_cards() {
305        let mut cx = Cx::new(
306            Arc::new(NoopEvalPolicy),
307            Arc::new(DefaultFactory),
308            crate::HandleSeed::new(7),
309        );
310        let profile = Symbol::qualified("standard", "rust-clean");
311        let organ = Symbol::qualified("standard", "reader");
312        let test = Symbol::qualified("test", "reader-roundtrip");
313
314        publish_profile_claims(&mut cx, profile.clone(), [organ.clone()], [test.clone()]).unwrap();
315        publish_organ_claims(&mut cx, organ.clone(), [standard_control_op_key("prompt")]).unwrap();
316        publish_fidelity_badge(
317            &mut cx,
318            Ref::Symbol(profile.clone()),
319            Symbol::qualified("standard", "full"),
320            Ref::Symbol(test.clone()),
321        )
322        .unwrap();
323
324        let profile_card = card_expr(&mut cx, Ref::Symbol(profile));
325        assert_eq!(
326            table_value(&profile_card, "kind"),
327            Some(&Expr::Symbol(standard_profile_kind()))
328        );
329        assert_list_contains_symbol(table_value(&profile_card, "tests").unwrap(), test);
330
331        let organ_card = card_expr(&mut cx, Ref::Symbol(organ));
332        assert_eq!(
333            table_value(&organ_card, "kind"),
334            Some(&Expr::Symbol(standard_organ_kind()))
335        );
336        assert_list_contains_symbol(
337            table_value(&organ_card, "ops").unwrap(),
338            Symbol::qualified("control", "prompt.v1"),
339        );
340    }
341
342    fn assert_has_claim(cx: &Cx, subject: Ref, predicate: Symbol, object: Ref) {
343        let claims = cx
344            .query_facts(ClaimPattern::exact(subject, predicate, object))
345            .unwrap();
346        assert_eq!(claims.len(), 1);
347    }
348
349    fn card_expr(cx: &mut Cx, subject: Ref) -> Expr {
350        card_for_ref(cx, subject)
351            .unwrap()
352            .object()
353            .as_expr(cx)
354            .unwrap()
355    }
356
357    fn table_value<'a>(expr: &'a Expr, key: &str) -> Option<&'a Expr> {
358        let Expr::Map(entries) = expr else {
359            return None;
360        };
361        entries.iter().find_map(|(entry_key, entry_value)| {
362            let Expr::Symbol(entry_key) = entry_key else {
363                return None;
364            };
365            (entry_key == &Symbol::new(key)).then_some(entry_value)
366        })
367    }
368
369    fn assert_list_contains_symbol(expr: &Expr, expected: Symbol) {
370        assert!(matches!(expr, Expr::List(_)), "expected list");
371        let Expr::List(items) = expr else {
372            return;
373        };
374        assert!(
375            items
376                .iter()
377                .any(|item| item == &Expr::Symbol(expected.clone())),
378            "expected list to contain {expected}"
379        );
380    }
381}