Skip to main content

agi4_core/
lib.rs

1//! Pure verdict logic for AGI/4 attestation.
2//!
3//! This crate contains the core types and verdict function with zero external dependencies
4//! beyond serialization. The verdict function is pure and total: given valid input, it always
5//! returns a verdict with no panics or side effects.
6
7pub mod conjunct;
8pub mod consistency;
9pub mod evaluators;
10pub mod evidence;
11pub mod sources;
12pub mod threshold;
13pub mod verdict;
14
15pub use conjunct::{Conjunct, ConjunctStatus};
16pub use evaluators::{
17    evaluate_autonomous_agency, evaluate_economic_substitutability,
18    evaluate_environmental_transfer, evaluate_generality,
19};
20pub use evidence::Evidence;
21pub use verdict::{Verdict, verdict};
22
23#[cfg(test)]
24mod tests {
25    use super::*;
26
27    #[test]
28    fn conjunct_debug_works() {
29        let c = Conjunct::Generality;
30        let debug_str = format!("{:?}", c);
31        assert!(!debug_str.is_empty());
32        assert!(debug_str.contains("Generality"));
33    }
34
35    #[test]
36    fn conjunct_clone_works() {
37        let c1 = Conjunct::EconomicSubstitutability;
38        let c2 = c1;
39        assert_eq!(c1, c2);
40    }
41
42    #[test]
43    fn conjunct_serialize_works() {
44        let c = Conjunct::EnvironmentalTransfer;
45        let json = serde_json::to_string(&c).expect("should serialize");
46        assert!(!json.is_empty());
47        let deserialized: Conjunct = serde_json::from_str(&json).expect("should deserialize");
48        assert_eq!(c, deserialized);
49    }
50
51    #[test]
52    fn conjunct_all_variants_serialize() {
53        let variants = vec![
54            Conjunct::Generality,
55            Conjunct::EconomicSubstitutability,
56            Conjunct::EnvironmentalTransfer,
57            Conjunct::AutonomousAgency,
58        ];
59
60        for variant in variants {
61            let json = serde_json::to_string(&variant).expect("should serialize");
62            let deserialized: Conjunct = serde_json::from_str(&json).expect("should deserialize");
63            assert_eq!(variant, deserialized);
64        }
65    }
66
67    #[test]
68    fn conjunct_status_debug_works() {
69        let statuses = vec![
70            ConjunctStatus::Pass,
71            ConjunctStatus::Partial,
72            ConjunctStatus::Fail,
73            ConjunctStatus::InsufficientData,
74        ];
75
76        for status in statuses {
77            let debug_str = format!("{:?}", status);
78            assert!(!debug_str.is_empty());
79        }
80    }
81
82    #[test]
83    fn conjunct_status_clone_works() {
84        let s1 = ConjunctStatus::Pass;
85        let s2 = s1;
86        assert_eq!(s1, s2);
87    }
88
89    #[test]
90    fn conjunct_status_serialize_works() {
91        let status = ConjunctStatus::Partial;
92        let json = serde_json::to_string(&status).expect("should serialize");
93        assert!(!json.is_empty());
94        let deserialized: ConjunctStatus = serde_json::from_str(&json).expect("should deserialize");
95        assert_eq!(status, deserialized);
96    }
97
98    #[test]
99    fn evidence_debug_works() {
100        use chrono::Utc;
101        use evidence::{
102            BoundedFraction, Evidence, MeasurementId, Provenance, SourceId, SourceValue,
103        };
104        use url::Url;
105
106        let evidence = Evidence {
107            source: SourceId::new("test-source"),
108            measurement: MeasurementId::new("test-measurement"),
109            value: SourceValue::Fraction(BoundedFraction::new(0.75).unwrap()),
110            reliability_percentile: 95,
111            provenance: Provenance {
112                source_url: Url::parse("https://example.com").unwrap(),
113                fetch_timestamp: Utc::now(),
114                source_version: Some("1.0".to_string()),
115                raw_value: "75.0%".to_string(),
116            },
117        };
118
119        let debug_str = format!("{:?}", evidence);
120        assert!(!debug_str.is_empty());
121    }
122
123    #[test]
124    fn evidence_clone_works() {
125        use chrono::Utc;
126        use evidence::{
127            BoundedFraction, Evidence, MeasurementId, Provenance, SourceId, SourceValue,
128        };
129        use url::Url;
130
131        let evidence = Evidence {
132            source: SourceId::new("test-source"),
133            measurement: MeasurementId::new("test-measurement"),
134            value: SourceValue::Fraction(BoundedFraction::new(0.75).unwrap()),
135            reliability_percentile: 95,
136            provenance: Provenance {
137                source_url: Url::parse("https://example.com").unwrap(),
138                fetch_timestamp: Utc::now(),
139                source_version: Some("1.0".to_string()),
140                raw_value: "75.0%".to_string(),
141            },
142        };
143
144        let cloned = evidence.clone();
145        assert_eq!(evidence.source, cloned.source);
146    }
147
148    #[test]
149    fn evidence_serialize_works() {
150        use chrono::Utc;
151        use evidence::{
152            BoundedFraction, Evidence, MeasurementId, Provenance, SourceId, SourceValue,
153        };
154        use url::Url;
155
156        let evidence = Evidence {
157            source: SourceId::new("test-source"),
158            measurement: MeasurementId::new("test-measurement"),
159            value: SourceValue::Fraction(BoundedFraction::new(0.75).unwrap()),
160            reliability_percentile: 95,
161            provenance: Provenance {
162                source_url: Url::parse("https://example.com").unwrap(),
163                fetch_timestamp: Utc::now(),
164                source_version: Some("1.0".to_string()),
165                raw_value: "75.0%".to_string(),
166            },
167        };
168
169        let json = serde_json::to_string(&evidence).expect("should serialize");
170        assert!(!json.is_empty());
171        let _deserialized: Evidence = serde_json::from_str(&json).expect("should deserialize");
172    }
173
174    #[test]
175    fn bounded_fraction_debug_works() {
176        use evidence::BoundedFraction;
177
178        let bf = BoundedFraction::new(0.5).unwrap();
179        let debug_str = format!("{:?}", bf);
180        assert!(!debug_str.is_empty());
181    }
182
183    #[test]
184    fn bounded_fraction_clone_works() {
185        use evidence::BoundedFraction;
186
187        let bf1 = BoundedFraction::new(0.5).unwrap();
188        let bf2 = bf1;
189        assert_eq!(bf1, bf2);
190    }
191
192    #[test]
193    fn bounded_fraction_serialize_works() {
194        use evidence::BoundedFraction;
195
196        let bf = BoundedFraction::new(0.5).unwrap();
197        let json = serde_json::to_string(&bf).expect("should serialize");
198        assert!(!json.is_empty());
199        let deserialized: BoundedFraction =
200            serde_json::from_str(&json).expect("should deserialize");
201        assert_eq!(bf, deserialized);
202    }
203
204    #[test]
205    fn non_negative_hours_debug_works() {
206        use evidence::NonNegativeHours;
207
208        let nnh = NonNegativeHours::new(24.0).unwrap();
209        let debug_str = format!("{:?}", nnh);
210        assert!(!debug_str.is_empty());
211    }
212
213    #[test]
214    fn non_negative_hours_clone_works() {
215        use evidence::NonNegativeHours;
216
217        let nnh1 = NonNegativeHours::new(24.0).unwrap();
218        let nnh2 = nnh1;
219        assert_eq!(nnh1, nnh2);
220    }
221
222    #[test]
223    fn non_negative_hours_serialize_works() {
224        use evidence::NonNegativeHours;
225
226        let nnh = NonNegativeHours::new(24.0).unwrap();
227        let json = serde_json::to_string(&nnh).expect("should serialize");
228        assert!(!json.is_empty());
229        let deserialized: NonNegativeHours =
230            serde_json::from_str(&json).expect("should deserialize");
231        assert_eq!(nnh, deserialized);
232    }
233
234    #[test]
235    fn verdict_debug_works() {
236        let v = Verdict::attested();
237        let debug_str = format!("{:?}", v);
238        assert!(!debug_str.is_empty());
239    }
240
241    #[test]
242    fn verdict_clone_works() {
243        let v1 = Verdict::not_attested(vec!["reason"]);
244        let v2 = v1.clone();
245        // Just verify it clones without panic
246        let _ = format!("{:?}", v2);
247    }
248
249    #[test]
250    fn verdict_serialize_works() {
251        let v = Verdict::attested();
252        let json = serde_json::to_string(&v).expect("should serialize");
253        assert!(!json.is_empty());
254        let deserialized: Verdict = serde_json::from_str(&json).expect("should deserialize");
255        assert_eq!(format!("{:?}", v), format!("{:?}", deserialized));
256    }
257
258    #[test]
259    fn consistency_result_debug_works() {
260        use consistency::ConsistencyResult;
261
262        let cr = ConsistencyResult::pass();
263        let debug_str = format!("{:?}", cr);
264        assert!(!debug_str.is_empty());
265    }
266
267    #[test]
268    fn consistency_result_clone_works() {
269        use consistency::ConsistencyResult;
270
271        let cr1 = ConsistencyResult::pass();
272        let cr2 = cr1.clone();
273        assert_eq!(cr1.passed, cr2.passed);
274    }
275
276    #[test]
277    fn consistency_result_serialize_works() {
278        use consistency::ConsistencyResult;
279
280        let cr = ConsistencyResult::pass();
281        let json = serde_json::to_string(&cr).expect("should serialize");
282        assert!(!json.is_empty());
283        let deserialized: ConsistencyResult =
284            serde_json::from_str(&json).expect("should deserialize");
285        assert_eq!(cr.passed, deserialized.passed);
286    }
287}