llm_analytics_hub/schemas/
events.rs

1//! Analytics Event Schema
2//!
3//! Unified event schema that accommodates telemetry, security, cost, and governance events
4//! from all modules in the LLM ecosystem.
5
6use chrono::{DateTime, Utc};
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9use uuid::Uuid;
10
11/// Schema version for event compatibility and migration
12pub const SCHEMA_VERSION: &str = "1.0.0";
13
14/// Common fields present in all analytics events
15#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
16pub struct CommonEventFields {
17    /// Unique identifier for this event
18    #[serde(default = "Uuid::new_v4")]
19    pub event_id: Uuid,
20
21    /// ISO 8601 timestamp when the event occurred
22    pub timestamp: DateTime<Utc>,
23
24    /// Source module that generated this event
25    pub source_module: SourceModule,
26
27    /// Type of event being reported
28    pub event_type: EventType,
29
30    /// Correlation ID for tracing related events across modules
31    #[serde(skip_serializing_if = "Option::is_none")]
32    pub correlation_id: Option<Uuid>,
33
34    /// Parent event ID for hierarchical event relationships
35    #[serde(skip_serializing_if = "Option::is_none")]
36    pub parent_event_id: Option<Uuid>,
37
38    /// Schema version for backward compatibility
39    #[serde(default = "default_schema_version")]
40    pub schema_version: String,
41
42    /// Severity level of the event
43    pub severity: Severity,
44
45    /// Environment where the event occurred
46    pub environment: String,
47
48    /// Additional custom tags for filtering and grouping
49    #[serde(default)]
50    pub tags: HashMap<String, String>,
51}
52
53fn default_schema_version() -> String {
54    SCHEMA_VERSION.to_string()
55}
56
57/// Source modules in the LLM ecosystem
58#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
59#[serde(rename_all = "kebab-case")]
60pub enum SourceModule {
61    /// LLM-Observatory: Performance and telemetry monitoring
62    LlmObservatory,
63
64    /// LLM-Sentinel: Security monitoring and threat detection
65    LlmSentinel,
66
67    /// LLM-CostOps: Cost tracking and optimization
68    LlmCostOps,
69
70    /// LLM-Governance-Dashboard: Policy and compliance monitoring
71    LlmGovernanceDashboard,
72
73    /// LLM-Registry: Asset and model registry
74    LlmRegistry,
75
76    /// LLM-Policy-Engine: Policy evaluation and enforcement
77    LlmPolicyEngine,
78
79    /// LLM-Analytics-Hub: Self-monitoring events
80    LlmAnalyticsHub,
81}
82
83/// High-level event type classification
84#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
85#[serde(rename_all = "snake_case")]
86pub enum EventType {
87    /// Telemetry and performance events
88    Telemetry,
89
90    /// Security-related events
91    Security,
92
93    /// Cost and resource consumption events
94    Cost,
95
96    /// Governance and compliance events
97    Governance,
98
99    /// System lifecycle events
100    Lifecycle,
101
102    /// Audit trail events
103    Audit,
104
105    /// Alert and notification events
106    Alert,
107}
108
109/// Event severity levels
110#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
111#[serde(rename_all = "lowercase")]
112pub enum Severity {
113    Debug,
114    Info,
115    Warning,
116    Error,
117    Critical,
118}
119
120/// Unified analytics event containing common fields and module-specific payload
121#[derive(Debug, Clone, Serialize, Deserialize)]
122pub struct AnalyticsEvent {
123    /// Common fields shared by all events
124    #[serde(flatten)]
125    pub common: CommonEventFields,
126
127    /// Module-specific event payload
128    pub payload: EventPayload,
129}
130
131/// Module-specific event payloads
132#[derive(Debug, Clone, Serialize, Deserialize)]
133#[serde(tag = "payload_type", content = "data")]
134pub enum EventPayload {
135    /// Telemetry events from LLM-Observatory
136    #[serde(rename = "telemetry")]
137    Telemetry(TelemetryPayload),
138
139    /// Security events from LLM-Sentinel
140    #[serde(rename = "security")]
141    Security(SecurityPayload),
142
143    /// Cost events from LLM-CostOps
144    #[serde(rename = "cost")]
145    Cost(CostPayload),
146
147    /// Governance events from LLM-Governance-Dashboard
148    #[serde(rename = "governance")]
149    Governance(GovernancePayload),
150
151    /// Generic custom payload
152    #[serde(rename = "custom")]
153    Custom(CustomPayload),
154}
155
156// ============================================================================
157// TELEMETRY PAYLOADS (LLM-Observatory)
158// ============================================================================
159
160/// Telemetry event payload from LLM-Observatory
161#[derive(Debug, Clone, Serialize, Deserialize)]
162#[serde(tag = "telemetry_type")]
163pub enum TelemetryPayload {
164    /// Request latency measurement
165    #[serde(rename = "latency")]
166    Latency(LatencyMetrics),
167
168    /// Throughput measurement
169    #[serde(rename = "throughput")]
170    Throughput(ThroughputMetrics),
171
172    /// Error rate tracking
173    #[serde(rename = "error_rate")]
174    ErrorRate(ErrorRateMetrics),
175
176    /// Token usage statistics
177    #[serde(rename = "token_usage")]
178    TokenUsage(TokenUsageMetrics),
179
180    /// Model performance metrics
181    #[serde(rename = "model_performance")]
182    ModelPerformance(ModelPerformanceMetrics),
183}
184
185#[derive(Debug, Clone, Serialize, Deserialize)]
186pub struct LatencyMetrics {
187    /// Model or service identifier
188    pub model_id: String,
189
190    /// Request identifier
191    pub request_id: String,
192
193    /// Total latency in milliseconds
194    pub total_latency_ms: f64,
195
196    /// Time to first token (TTFT) in milliseconds
197    #[serde(skip_serializing_if = "Option::is_none")]
198    pub ttft_ms: Option<f64>,
199
200    /// Tokens per second
201    #[serde(skip_serializing_if = "Option::is_none")]
202    pub tokens_per_second: Option<f64>,
203
204    /// Latency breakdown by component
205    #[serde(skip_serializing_if = "Option::is_none")]
206    pub breakdown: Option<LatencyBreakdown>,
207}
208
209#[derive(Debug, Clone, Serialize, Deserialize)]
210pub struct LatencyBreakdown {
211    pub queue_time_ms: f64,
212    pub processing_time_ms: f64,
213    pub network_time_ms: f64,
214    pub other_ms: f64,
215}
216
217#[derive(Debug, Clone, Serialize, Deserialize)]
218pub struct ThroughputMetrics {
219    pub model_id: String,
220    pub requests_per_second: f64,
221    pub tokens_per_second: f64,
222    pub concurrent_requests: u32,
223    pub window_duration_seconds: u32,
224}
225
226#[derive(Debug, Clone, Serialize, Deserialize)]
227pub struct ErrorRateMetrics {
228    pub model_id: String,
229    pub total_requests: u64,
230    pub failed_requests: u64,
231    pub error_rate_percent: f64,
232    pub error_breakdown: HashMap<String, u64>,
233    pub window_duration_seconds: u32,
234}
235
236#[derive(Debug, Clone, Serialize, Deserialize)]
237pub struct TokenUsageMetrics {
238    pub model_id: String,
239    pub request_id: String,
240    pub prompt_tokens: u32,
241    pub completion_tokens: u32,
242    pub total_tokens: u32,
243}
244
245#[derive(Debug, Clone, Serialize, Deserialize)]
246pub struct ModelPerformanceMetrics {
247    pub model_id: String,
248    pub accuracy: Option<f64>,
249    pub quality_score: Option<f64>,
250    pub user_satisfaction: Option<f64>,
251    pub custom_metrics: HashMap<String, f64>,
252}
253
254// ============================================================================
255// SECURITY PAYLOADS (LLM-Sentinel)
256// ============================================================================
257
258/// Security event payload from LLM-Sentinel
259#[derive(Debug, Clone, Serialize, Deserialize)]
260#[serde(tag = "security_type")]
261pub enum SecurityPayload {
262    /// Threat detection event
263    #[serde(rename = "threat")]
264    Threat(ThreatEvent),
265
266    /// Vulnerability detection
267    #[serde(rename = "vulnerability")]
268    Vulnerability(VulnerabilityEvent),
269
270    /// Compliance violation
271    #[serde(rename = "compliance_violation")]
272    ComplianceViolation(ComplianceViolationEvent),
273
274    /// Authentication/Authorization event
275    #[serde(rename = "auth")]
276    Auth(AuthEvent),
277
278    /// Data privacy event
279    #[serde(rename = "privacy")]
280    Privacy(PrivacyEvent),
281}
282
283#[derive(Debug, Clone, Serialize, Deserialize)]
284pub struct ThreatEvent {
285    pub threat_id: String,
286    pub threat_type: ThreatType,
287    pub threat_level: ThreatLevel,
288    pub source_ip: Option<String>,
289    pub target_resource: String,
290    pub attack_vector: String,
291    pub mitigation_status: MitigationStatus,
292    pub indicators_of_compromise: Vec<String>,
293}
294
295#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
296#[serde(rename_all = "snake_case")]
297pub enum ThreatType {
298    PromptInjection,
299    DataExfiltration,
300    ModelPoisoning,
301    DenialOfService,
302    UnauthorizedAccess,
303    MaliciousInput,
304    Other(String),
305}
306
307#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
308#[serde(rename_all = "lowercase")]
309pub enum ThreatLevel {
310    Low,
311    Medium,
312    High,
313    Critical,
314}
315
316#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
317#[serde(rename_all = "snake_case")]
318pub enum MitigationStatus {
319    Detected,
320    Blocked,
321    Mitigated,
322    Investigating,
323    Resolved,
324}
325
326#[derive(Debug, Clone, Serialize, Deserialize)]
327pub struct VulnerabilityEvent {
328    pub vulnerability_id: String,
329    pub cve_id: Option<String>,
330    pub severity_score: f64,
331    pub affected_component: String,
332    pub description: String,
333    pub remediation_status: RemediationStatus,
334}
335
336#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
337#[serde(rename_all = "snake_case")]
338pub enum RemediationStatus {
339    Identified,
340    PatchAvailable,
341    Patching,
342    Patched,
343    Accepted,
344}
345
346#[derive(Debug, Clone, Serialize, Deserialize)]
347pub struct ComplianceViolationEvent {
348    pub violation_id: String,
349    pub regulation: String,
350    pub requirement: String,
351    pub violation_description: String,
352    pub affected_data_types: Vec<String>,
353    pub remediation_required: bool,
354}
355
356#[derive(Debug, Clone, Serialize, Deserialize)]
357pub struct AuthEvent {
358    pub user_id: String,
359    pub action: AuthAction,
360    pub resource: String,
361    pub success: bool,
362    pub failure_reason: Option<String>,
363}
364
365#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
366#[serde(rename_all = "snake_case")]
367pub enum AuthAction {
368    Login,
369    Logout,
370    AccessAttempt,
371    PermissionDenied,
372    TokenGenerated,
373    TokenRevoked,
374}
375
376#[derive(Debug, Clone, Serialize, Deserialize)]
377pub struct PrivacyEvent {
378    pub data_type: String,
379    pub operation: PrivacyOperation,
380    pub user_consent: bool,
381    pub data_subjects: Vec<String>,
382    pub purpose: String,
383}
384
385#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
386#[serde(rename_all = "snake_case")]
387pub enum PrivacyOperation {
388    DataAccess,
389    DataCollection,
390    DataSharing,
391    DataDeletion,
392    ConsentUpdate,
393}
394
395// ============================================================================
396// COST PAYLOADS (LLM-CostOps)
397// ============================================================================
398
399/// Cost event payload from LLM-CostOps
400#[derive(Debug, Clone, Serialize, Deserialize)]
401#[serde(tag = "cost_type")]
402pub enum CostPayload {
403    /// Token usage cost
404    #[serde(rename = "token_cost")]
405    TokenCost(TokenCostEvent),
406
407    /// API cost tracking
408    #[serde(rename = "api_cost")]
409    ApiCost(ApiCostEvent),
410
411    /// Resource consumption
412    #[serde(rename = "resource_consumption")]
413    ResourceConsumption(ResourceConsumptionEvent),
414
415    /// Budget alert
416    #[serde(rename = "budget_alert")]
417    BudgetAlert(BudgetAlertEvent),
418}
419
420#[derive(Debug, Clone, Serialize, Deserialize)]
421pub struct TokenCostEvent {
422    pub model_id: String,
423    pub request_id: String,
424    pub prompt_tokens: u32,
425    pub completion_tokens: u32,
426    pub total_tokens: u32,
427    pub cost_per_prompt_token: f64,
428    pub cost_per_completion_token: f64,
429    pub total_cost_usd: f64,
430    pub currency: String,
431}
432
433#[derive(Debug, Clone, Serialize, Deserialize)]
434pub struct ApiCostEvent {
435    pub provider: String,
436    pub api_endpoint: String,
437    pub request_count: u64,
438    pub cost_per_request: f64,
439    pub total_cost_usd: f64,
440    pub billing_period: String,
441}
442
443#[derive(Debug, Clone, Serialize, Deserialize)]
444pub struct ResourceConsumptionEvent {
445    pub resource_type: ResourceType,
446    pub resource_id: String,
447    pub quantity: f64,
448    pub unit: String,
449    pub cost_usd: f64,
450    pub utilization_percent: f64,
451}
452
453#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
454#[serde(rename_all = "snake_case")]
455pub enum ResourceType {
456    Compute,
457    Storage,
458    Network,
459    Memory,
460    Gpu,
461    Other(String),
462}
463
464#[derive(Debug, Clone, Serialize, Deserialize)]
465pub struct BudgetAlertEvent {
466    pub budget_id: String,
467    pub budget_name: String,
468    pub budget_limit_usd: f64,
469    pub current_spend_usd: f64,
470    pub threshold_percent: f64,
471    pub alert_type: BudgetAlertType,
472}
473
474#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
475#[serde(rename_all = "snake_case")]
476pub enum BudgetAlertType {
477    Warning,
478    Critical,
479    Exceeded,
480}
481
482// ============================================================================
483// GOVERNANCE PAYLOADS (LLM-Governance-Dashboard)
484// ============================================================================
485
486/// Governance event payload from LLM-Governance-Dashboard
487#[derive(Debug, Clone, Serialize, Deserialize)]
488#[serde(tag = "governance_type")]
489pub enum GovernancePayload {
490    /// Policy violation event
491    #[serde(rename = "policy_violation")]
492    PolicyViolation(PolicyViolationEvent),
493
494    /// Audit trail event
495    #[serde(rename = "audit_trail")]
496    AuditTrail(AuditTrailEvent),
497
498    /// Compliance check result
499    #[serde(rename = "compliance_check")]
500    ComplianceCheck(ComplianceCheckEvent),
501
502    /// Data lineage tracking
503    #[serde(rename = "data_lineage")]
504    DataLineage(DataLineageEvent),
505}
506
507#[derive(Debug, Clone, Serialize, Deserialize)]
508pub struct PolicyViolationEvent {
509    pub policy_id: String,
510    pub policy_name: String,
511    pub violation_description: String,
512    pub violated_rules: Vec<String>,
513    pub resource_id: String,
514    pub user_id: Option<String>,
515    pub severity: PolicyViolationSeverity,
516    pub auto_remediated: bool,
517}
518
519#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
520#[serde(rename_all = "lowercase")]
521pub enum PolicyViolationSeverity {
522    Low,
523    Medium,
524    High,
525    Critical,
526}
527
528#[derive(Debug, Clone, Serialize, Deserialize)]
529pub struct AuditTrailEvent {
530    pub action: String,
531    pub actor: String,
532    pub resource_type: String,
533    pub resource_id: String,
534    pub changes: HashMap<String, serde_json::Value>,
535    pub ip_address: Option<String>,
536    pub user_agent: Option<String>,
537}
538
539#[derive(Debug, Clone, Serialize, Deserialize)]
540pub struct ComplianceCheckEvent {
541    pub check_id: String,
542    pub framework: String,
543    pub controls_checked: Vec<String>,
544    pub passed: bool,
545    pub findings: Vec<ComplianceFinding>,
546    pub score: f64,
547}
548
549#[derive(Debug, Clone, Serialize, Deserialize)]
550pub struct ComplianceFinding {
551    pub control_id: String,
552    pub status: ComplianceStatus,
553    pub description: String,
554    pub evidence: Option<String>,
555}
556
557#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
558#[serde(rename_all = "snake_case")]
559pub enum ComplianceStatus {
560    Pass,
561    Fail,
562    NotApplicable,
563    Manual,
564}
565
566#[derive(Debug, Clone, Serialize, Deserialize)]
567pub struct DataLineageEvent {
568    pub data_asset_id: String,
569    pub operation: DataOperation,
570    pub source: Option<String>,
571    pub destination: Option<String>,
572    pub transformation: Option<String>,
573    pub lineage_path: Vec<String>,
574}
575
576#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
577#[serde(rename_all = "snake_case")]
578pub enum DataOperation {
579    Create,
580    Read,
581    Update,
582    Delete,
583    Transform,
584    Aggregate,
585}
586
587// ============================================================================
588// CUSTOM PAYLOAD
589// ============================================================================
590
591/// Custom payload for extensibility
592#[derive(Debug, Clone, Serialize, Deserialize)]
593pub struct CustomPayload {
594    pub custom_type: String,
595    pub data: serde_json::Value,
596}
597
598#[cfg(test)]
599mod tests {
600    use super::*;
601    use pretty_assertions::assert_eq;
602
603    // ============================================================================
604    // COMMON EVENT FIELDS TESTS
605    // ============================================================================
606
607    #[test]
608    fn test_common_event_fields_default_values() {
609        let common = CommonEventFields {
610            event_id: Uuid::new_v4(),
611            timestamp: Utc::now(),
612            source_module: SourceModule::LlmAnalyticsHub,
613            event_type: EventType::Telemetry,
614            correlation_id: None,
615            parent_event_id: None,
616            schema_version: SCHEMA_VERSION.to_string(),
617            severity: Severity::Info,
618            environment: "test".to_string(),
619            tags: HashMap::new(),
620        };
621
622        assert_eq!(common.schema_version, "1.0.0");
623        assert!(common.correlation_id.is_none());
624        assert!(common.parent_event_id.is_none());
625        assert!(common.tags.is_empty());
626    }
627
628    #[test]
629    fn test_severity_ordering() {
630        assert!(Severity::Critical > Severity::Error);
631        assert!(Severity::Error > Severity::Warning);
632        assert!(Severity::Warning > Severity::Info);
633        assert!(Severity::Info > Severity::Debug);
634    }
635
636    #[test]
637    fn test_source_module_serialization() {
638        let modules = vec![
639            SourceModule::LlmObservatory,
640            SourceModule::LlmSentinel,
641            SourceModule::LlmCostOps,
642            SourceModule::LlmGovernanceDashboard,
643        ];
644
645        for module in modules {
646            let json = serde_json::to_string(&module).unwrap();
647            let deserialized: SourceModule = serde_json::from_str(&json).unwrap();
648            assert_eq!(module, deserialized);
649        }
650    }
651
652    // ============================================================================
653    // TELEMETRY PAYLOAD TESTS
654    // ============================================================================
655
656    #[test]
657    fn test_latency_metrics_complete() {
658        let latency = LatencyMetrics {
659            model_id: "gpt-4".to_string(),
660            request_id: "req-123".to_string(),
661            total_latency_ms: 1523.45,
662            ttft_ms: Some(234.12),
663            tokens_per_second: Some(45.6),
664            breakdown: Some(LatencyBreakdown {
665                queue_time_ms: 100.0,
666                processing_time_ms: 1200.0,
667                network_time_ms: 200.0,
668                other_ms: 23.45,
669            }),
670        };
671
672        let json = serde_json::to_string(&latency).unwrap();
673        let deserialized: LatencyMetrics = serde_json::from_str(&json).unwrap();
674
675        assert_eq!(deserialized.model_id, "gpt-4");
676        assert_eq!(deserialized.total_latency_ms, 1523.45);
677        assert!(deserialized.breakdown.is_some());
678    }
679
680    #[test]
681    fn test_throughput_metrics() {
682        let throughput = ThroughputMetrics {
683            model_id: "claude-3".to_string(),
684            requests_per_second: 100.5,
685            tokens_per_second: 5000.0,
686            concurrent_requests: 25,
687            window_duration_seconds: 60,
688        };
689
690        let json = serde_json::to_string(&throughput).unwrap();
691        assert!(json.contains("claude-3"));
692        assert!(json.contains("100.5"));
693    }
694
695    #[test]
696    fn test_error_rate_metrics() {
697        let mut error_breakdown = HashMap::new();
698        error_breakdown.insert("timeout".to_string(), 5);
699        error_breakdown.insert("rate_limit".to_string(), 3);
700
701        let error_rate = ErrorRateMetrics {
702            model_id: "gpt-4".to_string(),
703            total_requests: 1000,
704            failed_requests: 8,
705            error_rate_percent: 0.8,
706            error_breakdown,
707            window_duration_seconds: 300,
708        };
709
710        assert_eq!(error_rate.error_rate_percent, 0.8);
711        assert_eq!(error_rate.failed_requests, 8);
712    }
713
714    #[test]
715    fn test_token_usage_metrics() {
716        let token_usage = TokenUsageMetrics {
717            model_id: "gpt-4".to_string(),
718            request_id: "req-456".to_string(),
719            prompt_tokens: 100,
720            completion_tokens: 200,
721            total_tokens: 300,
722        };
723
724        assert_eq!(token_usage.total_tokens, token_usage.prompt_tokens + token_usage.completion_tokens);
725    }
726
727    #[test]
728    fn test_telemetry_event_serialization() {
729        let event = AnalyticsEvent {
730            common: CommonEventFields {
731                event_id: Uuid::new_v4(),
732                timestamp: Utc::now(),
733                source_module: SourceModule::LlmObservatory,
734                event_type: EventType::Telemetry,
735                correlation_id: Some(Uuid::new_v4()),
736                parent_event_id: None,
737                schema_version: SCHEMA_VERSION.to_string(),
738                severity: Severity::Info,
739                environment: "production".to_string(),
740                tags: HashMap::new(),
741            },
742            payload: EventPayload::Telemetry(TelemetryPayload::Latency(LatencyMetrics {
743                model_id: "gpt-4".to_string(),
744                request_id: "req-123".to_string(),
745                total_latency_ms: 1523.45,
746                ttft_ms: Some(234.12),
747                tokens_per_second: Some(45.6),
748                breakdown: None,
749            })),
750        };
751
752        let json = serde_json::to_string_pretty(&event).unwrap();
753        assert!(json.contains("telemetry"));
754        assert!(json.contains("gpt-4"));
755
756        // Test deserialization round-trip
757        let deserialized: AnalyticsEvent = serde_json::from_str(&json).unwrap();
758        match deserialized.payload {
759            EventPayload::Telemetry(TelemetryPayload::Latency(metrics)) => {
760                assert_eq!(metrics.model_id, "gpt-4");
761            },
762            _ => panic!("Wrong payload type"),
763        }
764    }
765
766    // ============================================================================
767    // SECURITY PAYLOAD TESTS
768    // ============================================================================
769
770    #[test]
771    fn test_threat_event_all_types() {
772        let threat_types = vec![
773            ThreatType::PromptInjection,
774            ThreatType::DataExfiltration,
775            ThreatType::ModelPoisoning,
776            ThreatType::DenialOfService,
777            ThreatType::UnauthorizedAccess,
778            ThreatType::MaliciousInput,
779        ];
780
781        for threat_type in threat_types {
782            let threat = ThreatEvent {
783                threat_id: format!("threat-{:?}", threat_type),
784                threat_type: threat_type.clone(),
785                threat_level: ThreatLevel::High,
786                source_ip: Some("192.168.1.1".to_string()),
787                target_resource: "test-resource".to_string(),
788                attack_vector: "test-vector".to_string(),
789                mitigation_status: MitigationStatus::Detected,
790                indicators_of_compromise: vec![],
791            };
792
793            let json = serde_json::to_string(&threat).unwrap();
794            let deserialized: ThreatEvent = serde_json::from_str(&json).unwrap();
795            assert_eq!(deserialized.threat_type, threat_type);
796        }
797    }
798
799    #[test]
800    fn test_threat_level_ordering() {
801        assert!(ThreatLevel::Critical > ThreatLevel::High);
802        assert!(ThreatLevel::High > ThreatLevel::Medium);
803        assert!(ThreatLevel::Medium > ThreatLevel::Low);
804    }
805
806    #[test]
807    fn test_vulnerability_event() {
808        let vuln = VulnerabilityEvent {
809            vulnerability_id: "vuln-123".to_string(),
810            cve_id: Some("CVE-2024-1234".to_string()),
811            severity_score: 7.5,
812            affected_component: "llm-model".to_string(),
813            description: "SQL injection vulnerability".to_string(),
814            remediation_status: RemediationStatus::PatchAvailable,
815        };
816
817        let json = serde_json::to_string(&vuln).unwrap();
818        assert!(json.contains("CVE-2024-1234"));
819        assert!(json.contains("7.5"));
820    }
821
822    #[test]
823    fn test_auth_event() {
824        let auth = AuthEvent {
825            user_id: "user-123".to_string(),
826            action: AuthAction::Login,
827            resource: "/api/models".to_string(),
828            success: true,
829            failure_reason: None,
830        };
831
832        assert!(auth.success);
833        assert!(auth.failure_reason.is_none());
834    }
835
836    #[test]
837    fn test_privacy_event() {
838        let privacy = PrivacyEvent {
839            data_type: "pii".to_string(),
840            operation: PrivacyOperation::DataAccess,
841            user_consent: true,
842            data_subjects: vec!["user-1".to_string(), "user-2".to_string()],
843            purpose: "analytics".to_string(),
844        };
845
846        assert_eq!(privacy.data_subjects.len(), 2);
847        assert!(privacy.user_consent);
848    }
849
850    #[test]
851    fn test_security_event_serialization() {
852        let event = AnalyticsEvent {
853            common: CommonEventFields {
854                event_id: Uuid::new_v4(),
855                timestamp: Utc::now(),
856                source_module: SourceModule::LlmSentinel,
857                event_type: EventType::Security,
858                correlation_id: None,
859                parent_event_id: None,
860                schema_version: SCHEMA_VERSION.to_string(),
861                severity: Severity::Critical,
862                environment: "production".to_string(),
863                tags: HashMap::new(),
864            },
865            payload: EventPayload::Security(SecurityPayload::Threat(ThreatEvent {
866                threat_id: "threat-456".to_string(),
867                threat_type: ThreatType::PromptInjection,
868                threat_level: ThreatLevel::High,
869                source_ip: Some("192.168.1.1".to_string()),
870                target_resource: "model-endpoint-1".to_string(),
871                attack_vector: "malicious prompt".to_string(),
872                mitigation_status: MitigationStatus::Blocked,
873                indicators_of_compromise: vec!["ioc1".to_string(), "ioc2".to_string()],
874            })),
875        };
876
877        let json = serde_json::to_string_pretty(&event).unwrap();
878        assert!(json.contains("security"));
879        assert!(json.contains("prompt_injection"));
880    }
881
882    // ============================================================================
883    // COST PAYLOAD TESTS
884    // ============================================================================
885
886    #[test]
887    fn test_token_cost_event() {
888        let cost = TokenCostEvent {
889            model_id: "gpt-4".to_string(),
890            request_id: "req-123".to_string(),
891            prompt_tokens: 100,
892            completion_tokens: 200,
893            total_tokens: 300,
894            cost_per_prompt_token: 0.00003,
895            cost_per_completion_token: 0.00006,
896            total_cost_usd: 0.015,
897            currency: "USD".to_string(),
898        };
899
900        let calculated_cost = (100.0 * 0.00003) + (200.0 * 0.00006);
901        assert!((cost.total_cost_usd - calculated_cost).abs() < 0.001);
902    }
903
904    #[test]
905    fn test_budget_alert_event() {
906        let alert = BudgetAlertEvent {
907            budget_id: "budget-123".to_string(),
908            budget_name: "Q1 LLM Budget".to_string(),
909            budget_limit_usd: 10000.0,
910            current_spend_usd: 9500.0,
911            threshold_percent: 95.0,
912            alert_type: BudgetAlertType::Critical,
913        };
914
915        assert_eq!(alert.alert_type, BudgetAlertType::Critical);
916        assert!(alert.current_spend_usd > alert.budget_limit_usd * 0.9);
917    }
918
919    #[test]
920    fn test_resource_consumption_event() {
921        let resource = ResourceConsumptionEvent {
922            resource_type: ResourceType::Gpu,
923            resource_id: "gpu-1".to_string(),
924            quantity: 8.0,
925            unit: "hours".to_string(),
926            cost_usd: 24.0,
927            utilization_percent: 85.0,
928        };
929
930        assert_eq!(resource.resource_type, ResourceType::Gpu);
931        assert_eq!(resource.cost_usd, 24.0);
932    }
933
934    // ============================================================================
935    // GOVERNANCE PAYLOAD TESTS
936    // ============================================================================
937
938    #[test]
939    fn test_policy_violation_event() {
940        let violation = PolicyViolationEvent {
941            policy_id: "pol-123".to_string(),
942            policy_name: "Data Retention Policy".to_string(),
943            violation_description: "Data retained beyond allowed period".to_string(),
944            violated_rules: vec!["rule-1".to_string(), "rule-2".to_string()],
945            resource_id: "dataset-456".to_string(),
946            user_id: Some("user-789".to_string()),
947            severity: PolicyViolationSeverity::High,
948            auto_remediated: false,
949        };
950
951        assert_eq!(violation.violated_rules.len(), 2);
952        assert!(!violation.auto_remediated);
953    }
954
955    #[test]
956    fn test_audit_trail_event() {
957        let mut changes = HashMap::new();
958        changes.insert("field1".to_string(), serde_json::json!("old_value"));
959
960        let audit = AuditTrailEvent {
961            action: "update".to_string(),
962            actor: "user-123".to_string(),
963            resource_type: "model".to_string(),
964            resource_id: "model-456".to_string(),
965            changes,
966            ip_address: Some("10.0.0.1".to_string()),
967            user_agent: Some("Mozilla/5.0".to_string()),
968        };
969
970        assert_eq!(audit.action, "update");
971        assert!(audit.changes.len() > 0);
972    }
973
974    #[test]
975    fn test_compliance_check_event() {
976        let findings = vec![
977            ComplianceFinding {
978                control_id: "SOC2-CC6.1".to_string(),
979                status: ComplianceStatus::Pass,
980                description: "Logical access controls implemented".to_string(),
981                evidence: Some("IAM policies configured".to_string()),
982            },
983            ComplianceFinding {
984                control_id: "SOC2-CC6.2".to_string(),
985                status: ComplianceStatus::Fail,
986                description: "MFA not enabled for all users".to_string(),
987                evidence: None,
988            },
989        ];
990
991        let check = ComplianceCheckEvent {
992            check_id: "check-123".to_string(),
993            framework: "SOC2".to_string(),
994            controls_checked: vec!["CC6.1".to_string(), "CC6.2".to_string()],
995            passed: false,
996            findings,
997            score: 0.5,
998        };
999
1000        assert!(!check.passed);
1001        assert_eq!(check.findings.len(), 2);
1002    }
1003
1004    #[test]
1005    fn test_data_lineage_event() {
1006        let lineage = DataLineageEvent {
1007            data_asset_id: "asset-123".to_string(),
1008            operation: DataOperation::Transform,
1009            source: Some("raw_data".to_string()),
1010            destination: Some("processed_data".to_string()),
1011            transformation: Some("anonymization".to_string()),
1012            lineage_path: vec!["raw".to_string(), "cleaned".to_string(), "anonymized".to_string()],
1013        };
1014
1015        assert_eq!(lineage.operation, DataOperation::Transform);
1016        assert_eq!(lineage.lineage_path.len(), 3);
1017    }
1018
1019    // ============================================================================
1020    // CUSTOM PAYLOAD TESTS
1021    // ============================================================================
1022
1023    #[test]
1024    fn test_custom_payload() {
1025        let custom_data = serde_json::json!({
1026            "custom_field": "custom_value",
1027            "nested": {
1028                "field": 123
1029            }
1030        });
1031
1032        let custom = CustomPayload {
1033            custom_type: "test_custom".to_string(),
1034            data: custom_data,
1035        };
1036
1037        let json = serde_json::to_string(&custom).unwrap();
1038        assert!(json.contains("test_custom"));
1039        assert!(json.contains("custom_field"));
1040    }
1041
1042    // ============================================================================
1043    // EVENT HIERARCHY TESTS
1044    // ============================================================================
1045
1046    #[test]
1047    fn test_event_with_correlation() {
1048        let correlation_id = Uuid::new_v4();
1049        let parent_id = Uuid::new_v4();
1050
1051        let event = AnalyticsEvent {
1052            common: CommonEventFields {
1053                event_id: Uuid::new_v4(),
1054                timestamp: Utc::now(),
1055                source_module: SourceModule::LlmAnalyticsHub,
1056                event_type: EventType::Audit,
1057                correlation_id: Some(correlation_id),
1058                parent_event_id: Some(parent_id),
1059                schema_version: SCHEMA_VERSION.to_string(),
1060                severity: Severity::Info,
1061                environment: "test".to_string(),
1062                tags: HashMap::new(),
1063            },
1064            payload: EventPayload::Custom(CustomPayload {
1065                custom_type: "test".to_string(),
1066                data: serde_json::json!({}),
1067            }),
1068        };
1069
1070        assert_eq!(event.common.correlation_id, Some(correlation_id));
1071        assert_eq!(event.common.parent_event_id, Some(parent_id));
1072    }
1073
1074    #[test]
1075    fn test_event_with_tags() {
1076        let mut tags = HashMap::new();
1077        tags.insert("environment".to_string(), "production".to_string());
1078        tags.insert("region".to_string(), "us-east-1".to_string());
1079        tags.insert("version".to_string(), "1.0.0".to_string());
1080
1081        let event = AnalyticsEvent {
1082            common: CommonEventFields {
1083                event_id: Uuid::new_v4(),
1084                timestamp: Utc::now(),
1085                source_module: SourceModule::LlmAnalyticsHub,
1086                event_type: EventType::Telemetry,
1087                correlation_id: None,
1088                parent_event_id: None,
1089                schema_version: SCHEMA_VERSION.to_string(),
1090                severity: Severity::Info,
1091                environment: "production".to_string(),
1092                tags: tags.clone(),
1093            },
1094            payload: EventPayload::Custom(CustomPayload {
1095                custom_type: "test".to_string(),
1096                data: serde_json::json!({}),
1097            }),
1098        };
1099
1100        assert_eq!(event.common.tags.len(), 3);
1101        assert_eq!(event.common.tags.get("region"), Some(&"us-east-1".to_string()));
1102    }
1103
1104    // ============================================================================
1105    // ROUND-TRIP SERIALIZATION TESTS
1106    // ============================================================================
1107
1108    #[test]
1109    fn test_all_event_types_round_trip() {
1110        let event_types = vec![
1111            EventType::Telemetry,
1112            EventType::Security,
1113            EventType::Cost,
1114            EventType::Governance,
1115            EventType::Lifecycle,
1116            EventType::Audit,
1117            EventType::Alert,
1118        ];
1119
1120        for event_type in event_types {
1121            let json = serde_json::to_string(&event_type).unwrap();
1122            let deserialized: EventType = serde_json::from_str(&json).unwrap();
1123            assert_eq!(event_type, deserialized);
1124        }
1125    }
1126
1127    #[test]
1128    fn test_schema_version_compatibility() {
1129        assert_eq!(SCHEMA_VERSION, "1.0.0");
1130
1131        let common = CommonEventFields {
1132            event_id: Uuid::new_v4(),
1133            timestamp: Utc::now(),
1134            source_module: SourceModule::LlmAnalyticsHub,
1135            event_type: EventType::Telemetry,
1136            correlation_id: None,
1137            parent_event_id: None,
1138            schema_version: "1.0.0".to_string(),
1139            severity: Severity::Info,
1140            environment: "test".to_string(),
1141            tags: HashMap::new(),
1142        };
1143
1144        assert_eq!(common.schema_version, SCHEMA_VERSION);
1145    }
1146}