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