use crate::edge::RelationType;
use crate::entity::{DiscoverySource, EntityId, EntityKind};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UcmEvent {
pub event_id: Uuid,
pub timestamp: DateTime<Utc>,
pub causation_id: Option<Uuid>,
pub schema_version: u32,
pub payload: EventPayload,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum EventPayload {
EntityDiscovered {
entity_id: EntityId,
kind: EntityKind,
name: String,
file_path: String,
language: String,
source: DiscoverySource,
line_range: Option<(usize, usize)>,
},
EntityRemoved { entity_id: EntityId, reason: String },
DependencyLinked {
source_entity: EntityId,
target_entity: EntityId,
relation_type: RelationType,
confidence: f64,
source: DiscoverySource,
description: String,
},
ConfidenceUpdated {
source_entity: EntityId,
target_entity: EntityId,
new_evidence_confidence: f64,
source: DiscoverySource,
description: String,
},
ChangeDetected {
file_path: String,
change_type: ChangeType,
affected_entities: Vec<EntityId>,
before_snapshot: Option<String>,
after_snapshot: Option<String>,
},
ConflictFlagged {
entity_id: EntityId,
conflict_type: ConflictType,
sources: Vec<ConflictSource>,
description: String,
},
EdgeVerified {
source_entity: EntityId,
target_entity: EntityId,
},
IngestionCompleted {
source: DiscoverySource,
entities_count: usize,
edges_count: usize,
duration_ms: u64,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ChangeType {
SignatureChange,
BodyChange,
EntityAdded,
EntityDeleted,
FileRenamed { old_path: String },
ImportChange,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ConflictType {
RequirementDrift,
SourceConflict,
MissingData,
CoverageGap,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConflictSource {
pub source_type: String,
pub claimed_value: String,
pub confidence: f64,
}
impl UcmEvent {
pub fn new(payload: EventPayload) -> Self {
Self {
event_id: Uuid::now_v7(),
timestamp: Utc::now(),
causation_id: None,
schema_version: 1,
payload,
}
}
pub fn caused_by(payload: EventPayload, parent: Uuid) -> Self {
Self {
event_id: Uuid::now_v7(),
timestamp: Utc::now(),
causation_id: Some(parent),
schema_version: 1,
payload,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_event_creation() {
let event = UcmEvent::new(EventPayload::EntityDiscovered {
entity_id: EntityId::local("src/auth/service.ts", "validateToken"),
kind: EntityKind::Function {
is_async: true,
parameter_count: 1,
return_type: Some("boolean".into()),
},
name: "validateToken".into(),
file_path: "src/auth/service.ts".into(),
language: "typescript".into(),
source: DiscoverySource::StaticAnalysis,
line_range: Some((10, 25)),
});
assert_eq!(event.schema_version, 1);
assert!(event.causation_id.is_none());
}
#[test]
fn test_causation_chain() {
let parent = UcmEvent::new(EventPayload::ChangeDetected {
file_path: "src/auth/service.ts".into(),
change_type: ChangeType::SignatureChange,
affected_entities: vec![EntityId::local("src/auth/service.ts", "validateToken")],
before_snapshot: None,
after_snapshot: None,
});
let child = UcmEvent::caused_by(
EventPayload::ConfidenceUpdated {
source_entity: EntityId::local("src/auth/service.ts", "validateToken"),
target_entity: EntityId::local("src/api/middleware.ts", "authMiddleware"),
new_evidence_confidence: 0.95,
source: DiscoverySource::StaticAnalysis,
description: "re-analyzed after change".into(),
},
parent.event_id,
);
assert_eq!(child.causation_id, Some(parent.event_id));
}
#[test]
fn test_event_serialization() {
let event = UcmEvent::new(EventPayload::DependencyLinked {
source_entity: EntityId::local("src/auth/service.ts", "AuthService"),
target_entity: EntityId::local("src/db/client.ts", "DatabaseClient"),
relation_type: RelationType::DependsOn,
confidence: 0.92,
source: DiscoverySource::StaticAnalysis,
description: "import statement found".into(),
});
let json = serde_json::to_string_pretty(&event).unwrap();
assert!(json.contains("DependencyLinked"));
assert!(json.contains("0.92"));
let deserialized: UcmEvent = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.event_id, event.event_id);
}
}