Skip to main content

entrenar/integrity/lineage/
event.rs

1//! Lineage event types and structures
2
3use super::timestamp::LamportTimestamp;
4use serde::{Deserialize, Serialize};
5use std::cmp::Ordering;
6
7/// Type of lineage event
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
9pub enum LineageEventType {
10    /// Training run started
11    RunStarted,
12    /// Metric was logged
13    MetricLogged,
14    /// Artifact was saved
15    ArtifactSaved,
16    /// Training run completed
17    RunCompleted,
18    /// Model was promoted to production
19    ModelPromoted,
20    /// Model was rolled back
21    ModelRolledBack,
22}
23
24impl LineageEventType {
25    /// Get a human-readable description
26    pub fn description(&self) -> &'static str {
27        match self {
28            Self::RunStarted => "Run started",
29            Self::MetricLogged => "Metric logged",
30            Self::ArtifactSaved => "Artifact saved",
31            Self::RunCompleted => "Run completed",
32            Self::ModelPromoted => "Model promoted",
33            Self::ModelRolledBack => "Model rolled back",
34        }
35    }
36}
37
38impl std::fmt::Display for LineageEventType {
39    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40        write!(f, "{}", self.description())
41    }
42}
43
44/// A single event in the causal lineage
45#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
46pub struct LineageEvent {
47    /// Lamport timestamp for causal ordering
48    pub timestamp: LamportTimestamp,
49    /// Type of event
50    pub event_type: LineageEventType,
51    /// Associated run ID
52    pub run_id: String,
53    /// Optional additional context
54    pub context: Option<String>,
55}
56
57impl LineageEvent {
58    /// Create a new lineage event
59    pub fn new(timestamp: LamportTimestamp, event_type: LineageEventType, run_id: &str) -> Self {
60        Self { timestamp, event_type, run_id: run_id.to_string(), context: None }
61    }
62
63    /// Add context to the event
64    pub fn with_context(mut self, context: impl Into<String>) -> Self {
65        self.context = Some(context.into());
66        self
67    }
68}
69
70impl PartialOrd for LineageEvent {
71    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
72        Some(self.cmp(other))
73    }
74}
75
76impl Ord for LineageEvent {
77    fn cmp(&self, other: &Self) -> Ordering {
78        self.timestamp.cmp(&other.timestamp)
79    }
80}
81
82#[cfg(test)]
83mod tests {
84    use super::*;
85
86    #[test]
87    fn test_lineage_event_type_description_run_started() {
88        assert_eq!(LineageEventType::RunStarted.description(), "Run started");
89    }
90
91    #[test]
92    fn test_lineage_event_type_description_metric_logged() {
93        assert_eq!(LineageEventType::MetricLogged.description(), "Metric logged");
94    }
95
96    #[test]
97    fn test_lineage_event_type_description_artifact_saved() {
98        assert_eq!(LineageEventType::ArtifactSaved.description(), "Artifact saved");
99    }
100
101    #[test]
102    fn test_lineage_event_type_description_run_completed() {
103        assert_eq!(LineageEventType::RunCompleted.description(), "Run completed");
104    }
105
106    #[test]
107    fn test_lineage_event_type_description_model_promoted() {
108        assert_eq!(LineageEventType::ModelPromoted.description(), "Model promoted");
109    }
110
111    #[test]
112    fn test_lineage_event_type_description_model_rolled_back() {
113        assert_eq!(LineageEventType::ModelRolledBack.description(), "Model rolled back");
114    }
115
116    #[test]
117    fn test_lineage_event_type_display() {
118        assert_eq!(LineageEventType::RunStarted.to_string(), "Run started");
119        assert_eq!(LineageEventType::MetricLogged.to_string(), "Metric logged");
120        assert_eq!(LineageEventType::ArtifactSaved.to_string(), "Artifact saved");
121        assert_eq!(LineageEventType::RunCompleted.to_string(), "Run completed");
122        assert_eq!(LineageEventType::ModelPromoted.to_string(), "Model promoted");
123        assert_eq!(LineageEventType::ModelRolledBack.to_string(), "Model rolled back");
124    }
125
126    #[test]
127    fn test_lineage_event_type_clone() {
128        let et = LineageEventType::RunStarted;
129        let cloned = et;
130        assert_eq!(et, cloned);
131    }
132
133    #[test]
134    fn test_lineage_event_type_hash() {
135        use std::collections::HashSet;
136        let mut set = HashSet::new();
137        set.insert(LineageEventType::RunStarted);
138        set.insert(LineageEventType::RunStarted);
139        assert_eq!(set.len(), 1);
140        set.insert(LineageEventType::RunCompleted);
141        assert_eq!(set.len(), 2);
142    }
143
144    #[test]
145    fn test_lineage_event_new() {
146        let ts = LamportTimestamp::new("node1");
147        let event = LineageEvent::new(ts, LineageEventType::RunStarted, "run-123");
148        assert_eq!(event.run_id, "run-123");
149        assert_eq!(event.event_type, LineageEventType::RunStarted);
150        assert!(event.context.is_none());
151    }
152
153    #[test]
154    fn test_lineage_event_with_context() {
155        let ts = LamportTimestamp::new("node1");
156        let event = LineageEvent::new(ts, LineageEventType::MetricLogged, "run-456")
157            .with_context("loss=0.5");
158        assert_eq!(event.context, Some("loss=0.5".to_string()));
159    }
160
161    #[test]
162    fn test_lineage_event_ordering() {
163        let ts1 = LamportTimestamp::with_counter("node1", 1);
164        let ts2 = LamportTimestamp::with_counter("node1", 2);
165
166        let event1 = LineageEvent::new(ts1, LineageEventType::RunStarted, "run-1");
167        let event2 = LineageEvent::new(ts2, LineageEventType::MetricLogged, "run-1");
168
169        assert!(event1 < event2);
170        assert!(event2 > event1);
171        assert_eq!(event1.cmp(&event2), Ordering::Less);
172    }
173
174    #[test]
175    fn test_lineage_event_partial_ord() {
176        let ts1 = LamportTimestamp::with_counter("node1", 1);
177        let ts2 = LamportTimestamp::with_counter("node1", 2);
178
179        let event1 = LineageEvent::new(ts1, LineageEventType::RunStarted, "run-1");
180        let event2 = LineageEvent::new(ts2, LineageEventType::MetricLogged, "run-1");
181
182        assert_eq!(event1.partial_cmp(&event2), Some(Ordering::Less));
183    }
184
185    #[test]
186    fn test_lineage_event_clone() {
187        let ts = LamportTimestamp::new("node1");
188        let event =
189            LineageEvent::new(ts, LineageEventType::RunStarted, "run-123").with_context("test");
190        let cloned = event.clone();
191        assert_eq!(event.run_id, cloned.run_id);
192        assert_eq!(event.event_type, cloned.event_type);
193        assert_eq!(event.context, cloned.context);
194    }
195
196    #[test]
197    fn test_lineage_event_eq() {
198        let ts = LamportTimestamp::new("node1");
199        let event1 = LineageEvent::new(ts.clone(), LineageEventType::RunStarted, "run-123");
200        let event2 = LineageEvent::new(ts, LineageEventType::RunStarted, "run-123");
201        assert_eq!(event1, event2);
202    }
203
204    #[test]
205    fn test_lineage_event_serde() {
206        let ts = LamportTimestamp::new("node1");
207        let event = LineageEvent::new(ts, LineageEventType::ArtifactSaved, "run-789")
208            .with_context("model.gguf");
209
210        let json = serde_json::to_string(&event).expect("JSON serialization should succeed");
211        let deserialized: LineageEvent =
212            serde_json::from_str(&json).expect("JSON deserialization should succeed");
213        assert_eq!(event.run_id, deserialized.run_id);
214        assert_eq!(event.event_type, deserialized.event_type);
215        assert_eq!(event.context, deserialized.context);
216    }
217
218    #[test]
219    fn test_lineage_event_type_serde() {
220        let et = LineageEventType::ModelPromoted;
221        let json = serde_json::to_string(&et).expect("JSON serialization should succeed");
222        let deserialized: LineageEventType =
223            serde_json::from_str(&json).expect("JSON deserialization should succeed");
224        assert_eq!(et, deserialized);
225    }
226
227    #[test]
228    fn test_lineage_event_type_debug() {
229        assert_eq!(format!("{:?}", LineageEventType::RunStarted), "RunStarted");
230        assert_eq!(format!("{:?}", LineageEventType::MetricLogged), "MetricLogged");
231        assert_eq!(format!("{:?}", LineageEventType::ArtifactSaved), "ArtifactSaved");
232        assert_eq!(format!("{:?}", LineageEventType::RunCompleted), "RunCompleted");
233        assert_eq!(format!("{:?}", LineageEventType::ModelPromoted), "ModelPromoted");
234        assert_eq!(format!("{:?}", LineageEventType::ModelRolledBack), "ModelRolledBack");
235    }
236}