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(
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}