1use 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
17pub fn standard_profile_kind() -> Symbol {
19 standard_symbol("profile")
20}
21
22pub fn standard_organ_kind() -> Symbol {
24 standard_symbol("organ")
25}
26
27pub fn standard_profile_predicate() -> Symbol {
29 standard_symbol("profile")
30}
31
32pub fn standard_organ_predicate() -> Symbol {
34 standard_symbol("organ")
35}
36
37pub fn standard_fidelity_predicate() -> Symbol {
39 standard_symbol("fidelity")
40}
41
42pub fn standard_evidence_predicate() -> Symbol {
44 standard_symbol("evidence")
45}
46
47pub fn standard_control_op_key(name: &str) -> OpKey {
49 OpKey::new(Symbol::new("control"), Symbol::new(name), 1)
50}
51
52pub 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
64pub 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
117pub 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
128pub 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
164pub 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
176pub 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}