ldp_protocol/types/
provenance.rs1use crate::types::payload::PayloadMode;
7use crate::types::verification::{EvidenceRef, ProvenanceEntry, VerificationStatus};
8use serde::{Deserialize, Serialize};
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct Provenance {
16 pub produced_by: String,
18
19 pub model_version: String,
21
22 pub payload_mode_used: PayloadMode,
24
25 pub confidence: Option<f64>,
27
28 #[deprecated(note = "Use verification_status instead")]
30 #[serde(default)]
31 pub verified: bool,
32
33 pub session_id: Option<String>,
35
36 pub timestamp: Option<String>,
38
39 #[serde(skip_serializing_if = "Option::is_none", default)]
41 pub tokens_used: Option<u64>,
42
43 #[serde(skip_serializing_if = "Option::is_none", default)]
45 pub cost_usd: Option<f64>,
46
47 #[serde(skip_serializing_if = "Option::is_none", default)]
49 pub contract_id: Option<String>,
50
51 #[serde(skip_serializing_if = "Option::is_none", default)]
53 pub contract_satisfied: Option<bool>,
54
55 #[serde(default)]
57 pub contract_violations: Vec<String>,
58
59 #[serde(default)]
61 pub verification_status: VerificationStatus,
62
63 #[serde(default)]
65 pub evidence: Vec<EvidenceRef>,
66
67 #[serde(default)]
69 pub lineage: Vec<ProvenanceEntry>,
70}
71
72impl Provenance {
73 #[allow(deprecated)]
75 pub fn new(delegate_id: impl Into<String>, model_version: impl Into<String>) -> Self {
76 Self {
77 produced_by: delegate_id.into(),
78 model_version: model_version.into(),
79 payload_mode_used: PayloadMode::SemanticFrame,
80 confidence: None,
81 verified: false,
82 session_id: None,
83 timestamp: Some(chrono::Utc::now().to_rfc3339()),
84 tokens_used: None,
85 cost_usd: None,
86 contract_id: None,
87 contract_satisfied: None,
88 contract_violations: Vec::new(),
89 verification_status: VerificationStatus::Unverified,
90 evidence: Vec::new(),
91 lineage: Vec::new(),
92 }
93 }
94
95 pub fn normalize(&mut self) {
98 #[allow(deprecated)]
99 {
100 if self.verification_status == VerificationStatus::Unverified && self.verified {
101 self.verification_status = VerificationStatus::SelfVerified;
102 }
103 self.verified = self.verification_status != VerificationStatus::Unverified;
104 }
105 }
106
107 pub fn to_value(&self) -> serde_json::Value {
109 serde_json::to_value(self).unwrap_or_default()
110 }
111}
112
113#[cfg(test)]
114mod tests {
115 use super::*;
116
117 #[test]
118 fn new_has_no_contract_fields() {
119 let p = Provenance::new("d1", "v1");
120 assert!(p.contract_id.is_none());
121 assert!(p.contract_satisfied.is_none());
122 assert!(p.contract_violations.is_empty());
123 assert!(p.tokens_used.is_none());
124 assert!(p.cost_usd.is_none());
125 }
126
127 #[test]
128 fn with_usage() {
129 let mut p = Provenance::new("d1", "v1");
130 p.tokens_used = Some(5000);
131 p.cost_usd = Some(0.03);
132 let json = serde_json::to_value(&p).unwrap();
133 let restored: Provenance = serde_json::from_value(json).unwrap();
134 assert_eq!(restored.tokens_used, Some(5000));
135 assert_eq!(restored.cost_usd, Some(0.03));
136 }
137
138 #[test]
139 fn backward_compat_deserialization() {
140 let old_json = serde_json::json!({
141 "produced_by": "d1",
142 "model_version": "v1",
143 "payload_mode_used": "text",
144 "verified": false
145 });
146 let p: Provenance = serde_json::from_value(old_json).unwrap();
147 assert_eq!(p.produced_by, "d1");
148 assert!(p.contract_id.is_none());
149 assert!(p.contract_violations.is_empty());
150 }
151
152 #[test]
153 fn provenance_new_has_unverified_status() {
154 let p = Provenance::new("d1", "v1");
155 assert_eq!(p.verification_status, VerificationStatus::Unverified);
156 assert!(p.evidence.is_empty());
157 assert!(p.lineage.is_empty());
158 }
159
160 #[test]
161 fn provenance_normalize_syncs_verified_to_status() {
162 let mut p = Provenance::new("d1", "v1");
163 p.verification_status = VerificationStatus::PeerVerified;
164 p.normalize();
165 #[allow(deprecated)]
166 {
167 assert!(p.verified);
168 }
169 }
170
171 #[test]
172 fn provenance_normalize_syncs_old_verified_true() {
173 let mut p = Provenance::new("d1", "v1");
174 #[allow(deprecated)]
175 {
176 p.verified = true;
177 }
178 p.normalize();
179 assert_eq!(p.verification_status, VerificationStatus::SelfVerified);
180 }
181
182 #[test]
183 fn provenance_backward_compat_no_verification_fields() {
184 let old_json = serde_json::json!({
185 "produced_by": "d1",
186 "model_version": "v1",
187 "payload_mode_used": "text",
188 "verified": true
189 });
190 let mut p: Provenance = serde_json::from_value(old_json).unwrap();
191 p.normalize();
192 assert_eq!(p.verification_status, VerificationStatus::SelfVerified);
193 assert!(p.evidence.is_empty());
194 assert!(p.lineage.is_empty());
195 }
196}