1use crate::edge::RelationType;
14use crate::entity::{DiscoverySource, EntityId, EntityKind};
15use chrono::{DateTime, Utc};
16use serde::{Deserialize, Serialize};
17use uuid::Uuid;
18
19#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct UcmEvent {
25 pub event_id: Uuid,
27 pub timestamp: DateTime<Utc>,
29 pub causation_id: Option<Uuid>,
31 pub schema_version: u32,
33 pub payload: EventPayload,
35}
36
37#[derive(Debug, Clone, Serialize, Deserialize)]
39pub enum EventPayload {
40 EntityDiscovered {
42 entity_id: EntityId,
43 kind: EntityKind,
44 name: String,
45 file_path: String,
46 language: String,
47 source: DiscoverySource,
48 line_range: Option<(usize, usize)>,
49 },
50
51 EntityRemoved { entity_id: EntityId, reason: String },
53
54 DependencyLinked {
56 source_entity: EntityId,
57 target_entity: EntityId,
58 relation_type: RelationType,
59 confidence: f64,
60 source: DiscoverySource,
61 description: String,
62 },
63
64 ConfidenceUpdated {
66 source_entity: EntityId,
67 target_entity: EntityId,
68 new_evidence_confidence: f64,
69 source: DiscoverySource,
70 description: String,
71 },
72
73 ChangeDetected {
75 file_path: String,
76 change_type: ChangeType,
77 affected_entities: Vec<EntityId>,
78 before_snapshot: Option<String>,
79 after_snapshot: Option<String>,
80 },
81
82 ConflictFlagged {
84 entity_id: EntityId,
85 conflict_type: ConflictType,
86 sources: Vec<ConflictSource>,
87 description: String,
88 },
89
90 EdgeVerified {
92 source_entity: EntityId,
93 target_entity: EntityId,
94 },
95
96 IngestionCompleted {
98 source: DiscoverySource,
99 entities_count: usize,
100 edges_count: usize,
101 duration_ms: u64,
102 },
103}
104
105#[derive(Debug, Clone, Serialize, Deserialize)]
106pub enum ChangeType {
107 SignatureChange,
109 BodyChange,
111 EntityAdded,
113 EntityDeleted,
115 FileRenamed { old_path: String },
117 ImportChange,
119}
120
121#[derive(Debug, Clone, Serialize, Deserialize)]
122pub enum ConflictType {
123 RequirementDrift,
125 SourceConflict,
127 MissingData,
129 CoverageGap,
131}
132
133#[derive(Debug, Clone, Serialize, Deserialize)]
134pub struct ConflictSource {
135 pub source_type: String,
136 pub claimed_value: String,
137 pub confidence: f64,
138}
139
140impl UcmEvent {
141 pub fn new(payload: EventPayload) -> Self {
143 Self {
144 event_id: Uuid::now_v7(),
145 timestamp: Utc::now(),
146 causation_id: None,
147 schema_version: 1,
148 payload,
149 }
150 }
151
152 pub fn caused_by(payload: EventPayload, parent: Uuid) -> Self {
154 Self {
155 event_id: Uuid::now_v7(),
156 timestamp: Utc::now(),
157 causation_id: Some(parent),
158 schema_version: 1,
159 payload,
160 }
161 }
162}
163
164#[cfg(test)]
165mod tests {
166 use super::*;
167
168 #[test]
169 fn test_event_creation() {
170 let event = UcmEvent::new(EventPayload::EntityDiscovered {
171 entity_id: EntityId::local("src/auth/service.ts", "validateToken"),
172 kind: EntityKind::Function {
173 is_async: true,
174 parameter_count: 1,
175 return_type: Some("boolean".into()),
176 },
177 name: "validateToken".into(),
178 file_path: "src/auth/service.ts".into(),
179 language: "typescript".into(),
180 source: DiscoverySource::StaticAnalysis,
181 line_range: Some((10, 25)),
182 });
183 assert_eq!(event.schema_version, 1);
184 assert!(event.causation_id.is_none());
185 }
186
187 #[test]
188 fn test_causation_chain() {
189 let parent = UcmEvent::new(EventPayload::ChangeDetected {
190 file_path: "src/auth/service.ts".into(),
191 change_type: ChangeType::SignatureChange,
192 affected_entities: vec![EntityId::local("src/auth/service.ts", "validateToken")],
193 before_snapshot: None,
194 after_snapshot: None,
195 });
196
197 let child = UcmEvent::caused_by(
198 EventPayload::ConfidenceUpdated {
199 source_entity: EntityId::local("src/auth/service.ts", "validateToken"),
200 target_entity: EntityId::local("src/api/middleware.ts", "authMiddleware"),
201 new_evidence_confidence: 0.95,
202 source: DiscoverySource::StaticAnalysis,
203 description: "re-analyzed after change".into(),
204 },
205 parent.event_id,
206 );
207
208 assert_eq!(child.causation_id, Some(parent.event_id));
209 }
210
211 #[test]
212 fn test_event_serialization() {
213 let event = UcmEvent::new(EventPayload::DependencyLinked {
214 source_entity: EntityId::local("src/auth/service.ts", "AuthService"),
215 target_entity: EntityId::local("src/db/client.ts", "DatabaseClient"),
216 relation_type: RelationType::DependsOn,
217 confidence: 0.92,
218 source: DiscoverySource::StaticAnalysis,
219 description: "import statement found".into(),
220 });
221
222 let json = serde_json::to_string_pretty(&event).unwrap();
223 assert!(json.contains("DependencyLinked"));
224 assert!(json.contains("0.92"));
225
226 let deserialized: UcmEvent = serde_json::from_str(&json).unwrap();
228 assert_eq!(deserialized.event_id, event.event_id);
229 }
230}