converge_optimization/gate/
provenance.rs1use serde::{Deserialize, Serialize};
4use std::time::SystemTime;
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct ProvenanceEnvelope {
9 pub input_hash: String,
11 #[serde(with = "system_time_serde")]
13 pub submitted_at: SystemTime,
14 pub source_system: String,
16 pub submitted_by: String,
18 pub correlation_id: String,
20 pub metadata: serde_json::Value,
22}
23
24impl Default for ProvenanceEnvelope {
25 fn default() -> Self {
26 Self {
27 input_hash: String::new(),
28 submitted_at: SystemTime::now(),
29 source_system: String::new(),
30 submitted_by: String::new(),
31 correlation_id: String::new(),
32 metadata: serde_json::Value::Null,
33 }
34 }
35}
36
37impl ProvenanceEnvelope {
38 pub fn new(source_system: impl Into<String>, submitted_by: impl Into<String>) -> Self {
40 Self {
41 source_system: source_system.into(),
42 submitted_by: submitted_by.into(),
43 ..Default::default()
44 }
45 }
46
47 pub fn with_correlation_id(mut self, id: impl Into<String>) -> Self {
49 self.correlation_id = id.into();
50 self
51 }
52
53 pub fn with_input_hash(mut self, hash: impl Into<String>) -> Self {
55 self.input_hash = hash.into();
56 self
57 }
58
59 pub fn with_metadata(mut self, metadata: serde_json::Value) -> Self {
61 self.metadata = metadata;
62 self
63 }
64}
65
66#[derive(Debug, Clone, Serialize, Deserialize)]
68pub struct KernelTraceLink {
69 pub trace_id: String,
71 pub mode: TraceMode,
73 pub location: String,
75}
76
77impl Default for KernelTraceLink {
78 fn default() -> Self {
79 Self {
80 trace_id: String::new(),
81 mode: TraceMode::AuditOnly,
82 location: String::new(),
83 }
84 }
85}
86
87impl KernelTraceLink {
88 pub fn audit_only(trace_id: impl Into<String>) -> Self {
90 Self {
91 trace_id: trace_id.into(),
92 mode: TraceMode::AuditOnly,
93 location: String::new(),
94 }
95 }
96
97 pub fn replayable(trace_id: impl Into<String>, location: impl Into<String>) -> Self {
99 Self {
100 trace_id: trace_id.into(),
101 mode: TraceMode::Replayable,
102 location: location.into(),
103 }
104 }
105
106 pub fn is_replayable(&self) -> bool {
108 self.mode == TraceMode::Replayable
109 }
110}
111
112#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
114#[serde(rename_all = "snake_case")]
115pub enum TraceMode {
116 Replayable,
118 AuditOnly,
120}
121
122#[derive(Debug, Clone, Serialize, Deserialize)]
124pub struct ReplayEnvelope {
125 pub input_hash: String,
127 pub output_hash: String,
129 pub seed: u64,
131 pub solver_version: String,
133 pub pack_version: String,
135 pub library_version: String,
137}
138
139impl ReplayEnvelope {
140 pub fn new(
142 input_hash: impl Into<String>,
143 output_hash: impl Into<String>,
144 seed: u64,
145 solver_version: impl Into<String>,
146 pack_version: impl Into<String>,
147 ) -> Self {
148 Self {
149 input_hash: input_hash.into(),
150 output_hash: output_hash.into(),
151 seed,
152 solver_version: solver_version.into(),
153 pack_version: pack_version.into(),
154 library_version: env!("CARGO_PKG_VERSION").to_string(),
155 }
156 }
157
158 pub fn minimal(seed: u64) -> Self {
160 Self {
161 input_hash: String::new(),
162 output_hash: String::new(),
163 seed,
164 solver_version: "test".to_string(),
165 pack_version: "test".to_string(),
166 library_version: env!("CARGO_PKG_VERSION").to_string(),
167 }
168 }
169}
170
171mod system_time_serde {
173 use serde::{Deserialize, Deserializer, Serialize, Serializer};
174 use std::time::{Duration, SystemTime, UNIX_EPOCH};
175
176 pub fn serialize<S>(time: &SystemTime, serializer: S) -> Result<S::Ok, S::Error>
177 where
178 S: Serializer,
179 {
180 let duration = time.duration_since(UNIX_EPOCH).unwrap_or(Duration::ZERO);
181 duration.as_secs_f64().serialize(serializer)
182 }
183
184 pub fn deserialize<'de, D>(deserializer: D) -> Result<SystemTime, D::Error>
185 where
186 D: Deserializer<'de>,
187 {
188 let secs = f64::deserialize(deserializer)?;
189 Ok(UNIX_EPOCH + Duration::from_secs_f64(secs))
190 }
191}
192
193#[cfg(test)]
194mod tests {
195 use super::*;
196
197 #[test]
198 fn test_provenance_builder() {
199 let prov = ProvenanceEnvelope::new("test-system", "test-user")
200 .with_correlation_id("corr-123")
201 .with_input_hash("sha256:abc");
202
203 assert_eq!(prov.source_system, "test-system");
204 assert_eq!(prov.submitted_by, "test-user");
205 assert_eq!(prov.correlation_id, "corr-123");
206 assert_eq!(prov.input_hash, "sha256:abc");
207 }
208
209 #[test]
210 fn test_trace_link() {
211 let audit = KernelTraceLink::audit_only("trace-001");
212 assert!(!audit.is_replayable());
213
214 let replay = KernelTraceLink::replayable("trace-002", "/traces/002.json");
215 assert!(replay.is_replayable());
216 assert_eq!(replay.location, "/traces/002.json");
217 }
218
219 #[test]
220 fn test_replay_envelope() {
221 let envelope = ReplayEnvelope::new("input-hash", "output-hash", 42, "greedy-v1", "1.0.0");
222 assert_eq!(envelope.seed, 42);
223 assert_eq!(envelope.solver_version, "greedy-v1");
224 }
225
226 #[test]
227 fn test_serde_roundtrip() {
228 let prov = ProvenanceEnvelope::new("system", "user");
229 let json = serde_json::to_string(&prov).unwrap();
230 let restored: ProvenanceEnvelope = serde_json::from_str(&json).unwrap();
231 assert_eq!(restored.source_system, prov.source_system);
232 }
233}