Skip to main content

astrid_events/
event.rs

1//! Event types for the Astrid event bus.
2
3use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5use serde_json::Value;
6use uuid::Uuid;
7
8/// Metadata attached to every event.
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct EventMetadata {
11    /// Unique event identifier.
12    pub event_id: Uuid,
13    /// When the event was created.
14    pub timestamp: DateTime<Utc>,
15    /// Correlation ID for tracing related events.
16    pub correlation_id: Option<Uuid>,
17    /// Session ID if applicable.
18    pub session_id: Option<Uuid>,
19    /// User ID if applicable.
20    pub user_id: Option<Uuid>,
21    /// Source component that generated the event.
22    pub source: String,
23}
24
25impl EventMetadata {
26    /// Create new event metadata.
27    #[must_use]
28    pub fn new(source: impl Into<String>) -> Self {
29        Self {
30            event_id: Uuid::new_v4(),
31            timestamp: Utc::now(),
32            correlation_id: None,
33            session_id: None,
34            user_id: None,
35            source: source.into(),
36        }
37    }
38
39    /// Set correlation ID.
40    #[must_use]
41    pub fn with_correlation_id(mut self, id: Uuid) -> Self {
42        self.correlation_id = Some(id);
43        self
44    }
45
46    /// Set session ID.
47    #[must_use]
48    pub fn with_session_id(mut self, id: Uuid) -> Self {
49        self.session_id = Some(id);
50        self
51    }
52
53    /// Set user ID.
54    #[must_use]
55    pub fn with_user_id(mut self, id: Uuid) -> Self {
56        self.user_id = Some(id);
57        self
58    }
59}
60
61impl Default for EventMetadata {
62    fn default() -> Self {
63        Self::new("unknown")
64    }
65}
66
67/// All events that can occur in the Astrid runtime.
68///
69/// Always stored behind `Arc` in practice (`EventBus` publishes `Arc<AstridEvent>`),
70/// so the variant size difference is acceptable — no heap allocation per event.
71#[derive(Debug, Clone, Serialize, Deserialize)]
72#[serde(tag = "type", rename_all = "snake_case")]
73#[expect(clippy::large_enum_variant)]
74pub enum AstridEvent {
75    // ========== Agent Lifecycle ==========
76    /// Runtime started.
77    RuntimeStarted {
78        /// Event metadata.
79        metadata: EventMetadata,
80        /// Runtime version.
81        version: String,
82    },
83
84    /// Runtime stopped.
85    RuntimeStopped {
86        /// Event metadata.
87        metadata: EventMetadata,
88        /// Reason for stopping.
89        reason: Option<String>,
90    },
91
92    /// Agent started within the runtime.
93    AgentStarted {
94        /// Event metadata.
95        metadata: EventMetadata,
96        /// Agent ID.
97        agent_id: Uuid,
98        /// Agent name.
99        agent_name: String,
100    },
101
102    /// Agent stopped.
103    AgentStopped {
104        /// Event metadata.
105        metadata: EventMetadata,
106        /// Agent ID.
107        agent_id: Uuid,
108        /// Reason for stopping.
109        reason: Option<String>,
110    },
111
112    // ========== Session Events ==========
113    /// Session created.
114    SessionCreated {
115        /// Event metadata.
116        metadata: EventMetadata,
117        /// Session ID.
118        session_id: Uuid,
119    },
120
121    /// Session ended.
122    SessionEnded {
123        /// Event metadata.
124        metadata: EventMetadata,
125        /// Session ID.
126        session_id: Uuid,
127        /// Reason for ending.
128        reason: Option<String>,
129    },
130
131    /// Session resumed from persisted state.
132    SessionResumed {
133        /// Event metadata.
134        metadata: EventMetadata,
135        /// Session ID.
136        session_id: Uuid,
137    },
138
139    // ========== Message Flow ==========
140    /// User message received by the runtime.
141    MessageReceived {
142        /// Event metadata.
143        metadata: EventMetadata,
144        /// Message ID.
145        message_id: Uuid,
146        /// Platform the message came from.
147        platform: String,
148    },
149
150    /// Response message has been delivered to the user/platform.
151    ///
152    /// Fired after the message is confirmed sent. Useful for auditing,
153    /// logging, or triggering post-delivery side effects.
154    MessageSent {
155        /// Event metadata.
156        metadata: EventMetadata,
157        /// Message ID.
158        message_id: Uuid,
159        /// Target platform.
160        platform: String,
161    },
162
163    /// Message fully processed (response sent).
164    MessageProcessed {
165        /// Event metadata.
166        metadata: EventMetadata,
167        /// Message ID.
168        message_id: Uuid,
169        /// Duration in milliseconds.
170        duration_ms: u64,
171    },
172
173    // ========== Prompt / Cognitive Loop Events ==========
174    /// Prompt is being assembled before an LLM call.
175    ///
176    /// Capsules can inspect or modify the prompt context before it is sent
177    /// to the model.
178    PromptBuilding {
179        /// Event metadata.
180        metadata: EventMetadata,
181        /// Request ID correlating to the upcoming LLM call.
182        request_id: Uuid,
183    },
184
185    /// A response message is about to be sent to the user/platform.
186    ///
187    /// Allows capsules to intercept or transform outbound messages.
188    MessageSending {
189        /// Event metadata.
190        metadata: EventMetadata,
191        /// Message ID.
192        message_id: Uuid,
193        /// Target platform.
194        platform: String,
195    },
196
197    /// Context compaction is starting (trimming conversation history).
198    ContextCompactionStarted {
199        /// Event metadata.
200        metadata: EventMetadata,
201        /// Session ID being compacted.
202        session_id: Uuid,
203        /// Number of messages before compaction.
204        message_count: u32,
205    },
206
207    /// Context compaction completed.
208    ContextCompactionCompleted {
209        /// Event metadata.
210        metadata: EventMetadata,
211        /// Session ID that was compacted.
212        session_id: Uuid,
213        /// Messages remaining after compaction.
214        messages_remaining: u32,
215    },
216
217    /// Session is being reset (conversation history cleared).
218    SessionResetting {
219        /// Event metadata.
220        metadata: EventMetadata,
221        /// Session ID being reset.
222        session_id: Uuid,
223    },
224
225    /// Model selection is being resolved before an LLM call.
226    ///
227    /// Capsules can influence which model/provider is selected for a request.
228    ModelResolving {
229        /// Event metadata.
230        metadata: EventMetadata,
231        /// Request ID.
232        request_id: Uuid,
233        /// Candidate provider (may be overridden by capsule).
234        provider: Option<String>,
235        /// Candidate model (may be overridden by capsule).
236        model: Option<String>,
237    },
238
239    /// The agent's cognitive loop has finished its run.
240    ///
241    /// Fired after the final response is produced, before session teardown.
242    /// Capsules can inspect the complete run for logging or analytics.
243    AgentLoopCompleted {
244        /// Event metadata.
245        metadata: EventMetadata,
246        /// Agent ID.
247        agent_id: Uuid,
248        /// Total turns in the loop.
249        turns: u32,
250        /// Duration of the full loop in milliseconds.
251        duration_ms: u64,
252    },
253
254    /// A tool result is about to be persisted to conversation history.
255    ///
256    /// Capsules can intercept, redact, or transform the result before
257    /// it is stored.
258    ToolResultPersisting {
259        /// Event metadata.
260        metadata: EventMetadata,
261        /// Tool call ID.
262        call_id: Uuid,
263        /// Tool name.
264        tool_name: String,
265    },
266
267    // ========== LLM Events ==========
268    /// LLM request started.
269    LlmRequestStarted {
270        /// Event metadata.
271        metadata: EventMetadata,
272        /// Request ID.
273        request_id: Uuid,
274        /// Provider name.
275        provider: String,
276        /// Model name.
277        model: String,
278    },
279
280    /// LLM request completed (non-streaming or final).
281    LlmRequestCompleted {
282        /// Event metadata.
283        metadata: EventMetadata,
284        /// Request ID.
285        request_id: Uuid,
286        /// Whether the request succeeded.
287        success: bool,
288        /// Input tokens used.
289        input_tokens: Option<u32>,
290        /// Output tokens used.
291        output_tokens: Option<u32>,
292        /// Duration in milliseconds.
293        duration_ms: u64,
294    },
295
296    /// LLM streaming response started.
297    LlmStreamStarted {
298        /// Event metadata.
299        metadata: EventMetadata,
300        /// Request ID.
301        request_id: Uuid,
302        /// Model name.
303        model: String,
304    },
305
306    /// LLM stream chunk received.
307    LlmStreamChunk {
308        /// Event metadata.
309        metadata: EventMetadata,
310        /// Request ID.
311        request_id: Uuid,
312        /// Chunk index (0-based).
313        chunk_index: u32,
314        /// Number of tokens in this chunk.
315        token_count: u32,
316    },
317
318    /// LLM streaming response completed.
319    LlmStreamCompleted {
320        /// Event metadata.
321        metadata: EventMetadata,
322        /// Request ID.
323        request_id: Uuid,
324        /// Total input tokens.
325        input_tokens: Option<u32>,
326        /// Total output tokens.
327        output_tokens: Option<u32>,
328        /// Total duration in milliseconds.
329        duration_ms: u64,
330    },
331
332    // ========== Tool Events ==========
333    /// Tool call started (generic, any tool source).
334    ToolCallStarted {
335        /// Event metadata.
336        metadata: EventMetadata,
337        /// Tool call ID.
338        call_id: Uuid,
339        /// Tool name.
340        tool_name: String,
341        /// Server name (if MCP tool).
342        server_name: Option<String>,
343    },
344
345    /// Tool call completed successfully.
346    ToolCallCompleted {
347        /// Event metadata.
348        metadata: EventMetadata,
349        /// Tool call ID.
350        call_id: Uuid,
351        /// Tool name.
352        tool_name: String,
353        /// Duration in milliseconds.
354        duration_ms: u64,
355    },
356
357    /// Tool call failed.
358    ToolCallFailed {
359        /// Event metadata.
360        metadata: EventMetadata,
361        /// Tool call ID.
362        call_id: Uuid,
363        /// Tool name.
364        tool_name: String,
365        /// Error message.
366        error: String,
367        /// Duration in milliseconds.
368        duration_ms: u64,
369    },
370
371    // ========== MCP Events ==========
372    /// MCP server connected.
373    McpServerConnected {
374        /// Event metadata.
375        metadata: EventMetadata,
376        /// Server name.
377        server_name: String,
378        /// Protocol version.
379        protocol_version: String,
380    },
381
382    /// MCP server disconnected.
383    McpServerDisconnected {
384        /// Event metadata.
385        metadata: EventMetadata,
386        /// Server name.
387        server_name: String,
388        /// Reason for disconnection.
389        reason: Option<String>,
390    },
391
392    /// MCP tool called.
393    McpToolCalled {
394        /// Event metadata.
395        metadata: EventMetadata,
396        /// Server name.
397        server_name: String,
398        /// Tool name.
399        tool_name: String,
400        /// Tool arguments (may be redacted for security).
401        arguments: Option<Value>,
402    },
403
404    /// MCP tool completed.
405    McpToolCompleted {
406        /// Event metadata.
407        metadata: EventMetadata,
408        /// Server name.
409        server_name: String,
410        /// Tool name.
411        tool_name: String,
412        /// Whether the call succeeded.
413        success: bool,
414        /// Duration in milliseconds.
415        duration_ms: u64,
416    },
417
418    // ========== SubAgent Events ==========
419    /// Sub-agent spawned by a parent agent.
420    SubAgentSpawned {
421        /// Event metadata.
422        metadata: EventMetadata,
423        /// Sub-agent ID.
424        subagent_id: Uuid,
425        /// Parent agent ID.
426        parent_id: Uuid,
427        /// Task description.
428        task: String,
429        /// Depth in the agent tree.
430        depth: u32,
431    },
432
433    /// Sub-agent progress update.
434    SubAgentProgress {
435        /// Event metadata.
436        metadata: EventMetadata,
437        /// Sub-agent ID.
438        subagent_id: Uuid,
439        /// Progress message.
440        message: String,
441    },
442
443    /// Sub-agent completed successfully.
444    SubAgentCompleted {
445        /// Event metadata.
446        metadata: EventMetadata,
447        /// Sub-agent ID.
448        subagent_id: Uuid,
449        /// Duration in milliseconds.
450        duration_ms: u64,
451    },
452
453    /// Sub-agent failed.
454    SubAgentFailed {
455        /// Event metadata.
456        metadata: EventMetadata,
457        /// Sub-agent ID.
458        subagent_id: Uuid,
459        /// Error message.
460        error: String,
461        /// Duration in milliseconds.
462        duration_ms: u64,
463    },
464
465    /// Sub-agent cancelled.
466    SubAgentCancelled {
467        /// Event metadata.
468        metadata: EventMetadata,
469        /// Sub-agent ID.
470        subagent_id: Uuid,
471        /// Reason for cancellation.
472        reason: Option<String>,
473    },
474
475    // ========== Security Events ==========
476    /// Capability granted.
477    CapabilityGranted {
478        /// Event metadata.
479        metadata: EventMetadata,
480        /// Capability ID.
481        capability_id: Uuid,
482        /// Resource being accessed.
483        resource: String,
484        /// Action being performed.
485        action: String,
486    },
487
488    /// Capability revoked.
489    CapabilityRevoked {
490        /// Event metadata.
491        metadata: EventMetadata,
492        /// Capability ID.
493        capability_id: Uuid,
494        /// Reason for revocation.
495        reason: Option<String>,
496    },
497
498    /// Capability check performed.
499    CapabilityChecked {
500        /// Event metadata.
501        metadata: EventMetadata,
502        /// Resource being accessed.
503        resource: String,
504        /// Action being performed.
505        action: String,
506        /// Whether the check passed.
507        allowed: bool,
508    },
509
510    /// Authorization denied.
511    AuthorizationDenied {
512        /// Event metadata.
513        metadata: EventMetadata,
514        /// Resource being accessed.
515        resource: String,
516        /// Action being performed.
517        action: String,
518        /// Reason for denial.
519        reason: String,
520    },
521
522    /// Security violation detected.
523    SecurityViolation {
524        /// Event metadata.
525        metadata: EventMetadata,
526        /// Violation type.
527        violation_type: String,
528        /// Details of the violation.
529        details: String,
530    },
531
532    // ========== Approval Events ==========
533    /// Approval requested.
534    ApprovalRequested {
535        /// Event metadata.
536        metadata: EventMetadata,
537        /// Request ID.
538        request_id: Uuid,
539        /// Resource being accessed.
540        resource: String,
541        /// Action being performed.
542        action: String,
543        /// Description of what's being requested.
544        description: String,
545    },
546
547    /// Approval granted.
548    ApprovalGranted {
549        /// Event metadata.
550        metadata: EventMetadata,
551        /// Request ID.
552        request_id: Uuid,
553        /// Duration of approval (if limited).
554        duration: Option<String>,
555    },
556
557    /// Approval denied.
558    ApprovalDenied {
559        /// Event metadata.
560        metadata: EventMetadata,
561        /// Request ID.
562        request_id: Uuid,
563        /// Reason for denial.
564        reason: Option<String>,
565    },
566
567    // ========== Budget Events ==========
568    /// Budget allocated for a session or agent.
569    BudgetAllocated {
570        /// Event metadata.
571        metadata: EventMetadata,
572        /// Budget ID.
573        budget_id: Uuid,
574        /// Amount allocated (in smallest currency unit, e.g. cents).
575        amount_cents: u64,
576        /// Currency code.
577        currency: String,
578    },
579
580    /// Budget threshold warning.
581    BudgetWarning {
582        /// Event metadata.
583        metadata: EventMetadata,
584        /// Budget ID.
585        budget_id: Uuid,
586        /// Amount remaining (cents).
587        remaining_cents: u64,
588        /// Percentage used.
589        percent_used: f64,
590    },
591
592    /// Budget exceeded.
593    BudgetExceeded {
594        /// Event metadata.
595        metadata: EventMetadata,
596        /// Budget ID.
597        budget_id: Uuid,
598        /// Amount over budget (cents).
599        overage_cents: u64,
600    },
601
602    // ========== Capsule Events ==========
603    /// Capsule loaded successfully.
604    CapsuleLoaded {
605        /// Event metadata.
606        metadata: EventMetadata,
607        /// Capsule identifier.
608        capsule_id: String,
609        /// Capsule name.
610        capsule_name: String,
611    },
612
613    /// Capsule failed to load.
614    CapsuleFailed {
615        /// Event metadata.
616        metadata: EventMetadata,
617        /// Capsule identifier.
618        capsule_id: String,
619        /// Error message.
620        error: String,
621    },
622
623    /// Capsule unloaded.
624    CapsuleUnloaded {
625        /// Event metadata.
626        metadata: EventMetadata,
627        /// Capsule identifier.
628        capsule_id: String,
629        /// Capsule name.
630        capsule_name: String,
631    },
632
633    // ========== System Events ==========
634    /// Kernel daemon started.
635    KernelStarted {
636        /// Event metadata.
637        metadata: EventMetadata,
638        /// Kernel version.
639        version: String,
640    },
641
642    /// Kernel daemon shutting down.
643    KernelShutdown {
644        /// Event metadata.
645        metadata: EventMetadata,
646        /// Reason for shutdown.
647        reason: Option<String>,
648    },
649
650    /// Configuration reloaded from disk.
651    ConfigReloaded {
652        /// Event metadata.
653        metadata: EventMetadata,
654    },
655
656    /// Configuration value changed.
657    ConfigChanged {
658        /// Event metadata.
659        metadata: EventMetadata,
660        /// Config key that changed.
661        key: String,
662    },
663
664    /// Health check completed.
665    HealthCheckCompleted {
666        /// Event metadata.
667        metadata: EventMetadata,
668        /// Overall health state.
669        healthy: bool,
670        /// Number of checks performed.
671        checks_performed: u32,
672        /// Number of checks that failed.
673        checks_failed: u32,
674    },
675
676    // ========== Audit Events ==========
677    /// Audit entry created.
678    AuditEntryCreated {
679        /// Event metadata.
680        metadata: EventMetadata,
681        /// Audit entry ID.
682        entry_id: Uuid,
683        /// Entry type.
684        entry_type: String,
685    },
686
687    // ========== Error Events ==========
688    /// Error occurred.
689    ErrorOccurred {
690        /// Event metadata.
691        metadata: EventMetadata,
692        /// Error code.
693        code: String,
694        /// Error message.
695        message: String,
696        /// Stack trace if available.
697        stack_trace: Option<String>,
698    },
699
700    // ========== IPC Events ==========
701    /// An IPC message routed from a WASM guest or host.
702    Ipc {
703        /// Event metadata.
704        metadata: EventMetadata,
705        /// The decoded IPC message.
706        message: crate::ipc::IpcMessage,
707    },
708
709    // ========== Custom Events ==========
710    /// Custom event for extensions.
711    Custom {
712        /// Event metadata.
713        metadata: EventMetadata,
714        /// Event name.
715        name: String,
716        /// Event data.
717        data: Value,
718    },
719}
720
721impl AstridEvent {
722    /// Get the event metadata.
723    #[must_use]
724    pub fn metadata(&self) -> &EventMetadata {
725        match self {
726            Self::RuntimeStarted { metadata, .. }
727            | Self::RuntimeStopped { metadata, .. }
728            | Self::AgentStarted { metadata, .. }
729            | Self::AgentStopped { metadata, .. }
730            | Self::SessionCreated { metadata, .. }
731            | Self::SessionEnded { metadata, .. }
732            | Self::SessionResumed { metadata, .. }
733            | Self::PromptBuilding { metadata, .. }
734            | Self::MessageSending { metadata, .. }
735            | Self::ContextCompactionStarted { metadata, .. }
736            | Self::ContextCompactionCompleted { metadata, .. }
737            | Self::SessionResetting { metadata, .. }
738            | Self::ModelResolving { metadata, .. }
739            | Self::AgentLoopCompleted { metadata, .. }
740            | Self::ToolResultPersisting { metadata, .. }
741            | Self::MessageReceived { metadata, .. }
742            | Self::MessageSent { metadata, .. }
743            | Self::MessageProcessed { metadata, .. }
744            | Self::LlmRequestStarted { metadata, .. }
745            | Self::LlmRequestCompleted { metadata, .. }
746            | Self::LlmStreamStarted { metadata, .. }
747            | Self::LlmStreamChunk { metadata, .. }
748            | Self::LlmStreamCompleted { metadata, .. }
749            | Self::ToolCallStarted { metadata, .. }
750            | Self::ToolCallCompleted { metadata, .. }
751            | Self::ToolCallFailed { metadata, .. }
752            | Self::McpServerConnected { metadata, .. }
753            | Self::McpServerDisconnected { metadata, .. }
754            | Self::McpToolCalled { metadata, .. }
755            | Self::McpToolCompleted { metadata, .. }
756            | Self::SubAgentSpawned { metadata, .. }
757            | Self::SubAgentProgress { metadata, .. }
758            | Self::SubAgentCompleted { metadata, .. }
759            | Self::SubAgentFailed { metadata, .. }
760            | Self::SubAgentCancelled { metadata, .. }
761            | Self::CapsuleLoaded { metadata, .. }
762            | Self::CapsuleFailed { metadata, .. }
763            | Self::CapsuleUnloaded { metadata, .. }
764            | Self::CapabilityGranted { metadata, .. }
765            | Self::CapabilityRevoked { metadata, .. }
766            | Self::CapabilityChecked { metadata, .. }
767            | Self::AuthorizationDenied { metadata, .. }
768            | Self::SecurityViolation { metadata, .. }
769            | Self::ApprovalRequested { metadata, .. }
770            | Self::ApprovalGranted { metadata, .. }
771            | Self::ApprovalDenied { metadata, .. }
772            | Self::BudgetAllocated { metadata, .. }
773            | Self::BudgetWarning { metadata, .. }
774            | Self::BudgetExceeded { metadata, .. }
775            | Self::KernelStarted { metadata, .. }
776            | Self::KernelShutdown { metadata, .. }
777            | Self::ConfigReloaded { metadata, .. }
778            | Self::ConfigChanged { metadata, .. }
779            | Self::HealthCheckCompleted { metadata, .. }
780            | Self::AuditEntryCreated { metadata, .. }
781            | Self::ErrorOccurred { metadata, .. }
782            | Self::Ipc { metadata, .. }
783            | Self::Custom { metadata, .. } => metadata,
784        }
785    }
786
787    /// Get the event type as a string.
788    #[must_use]
789    pub fn event_type(&self) -> &'static str {
790        match self {
791            // Agent Lifecycle
792            Self::RuntimeStarted { .. } => "astrid.v1.lifecycle.runtime_started",
793            Self::RuntimeStopped { .. } => "astrid.v1.lifecycle.runtime_stopped",
794            Self::AgentStarted { .. } => "astrid.v1.lifecycle.agent_started",
795            Self::AgentStopped { .. } => "astrid.v1.lifecycle.agent_stopped",
796            // Session
797            Self::SessionCreated { .. } => "astrid.v1.lifecycle.session_created",
798            Self::SessionEnded { .. } => "astrid.v1.lifecycle.session_ended",
799            Self::SessionResumed { .. } => "astrid.v1.lifecycle.session_resumed",
800            // Prompt / Cognitive Loop
801            Self::PromptBuilding { .. } => "astrid.v1.lifecycle.prompt_building",
802            Self::MessageSending { .. } => "astrid.v1.lifecycle.message_sending",
803            Self::ContextCompactionStarted { .. } => {
804                "astrid.v1.lifecycle.context_compaction_started"
805            },
806            Self::ContextCompactionCompleted { .. } => {
807                "astrid.v1.lifecycle.context_compaction_completed"
808            },
809            Self::SessionResetting { .. } => "astrid.v1.lifecycle.session_resetting",
810            Self::ModelResolving { .. } => "astrid.v1.lifecycle.model_resolving",
811            Self::AgentLoopCompleted { .. } => "astrid.v1.lifecycle.agent_loop_completed",
812            Self::ToolResultPersisting { .. } => "astrid.v1.lifecycle.tool_result_persisting",
813            // Message Flow
814            Self::MessageReceived { .. } => "astrid.v1.lifecycle.message_received",
815            Self::MessageSent { .. } => "astrid.v1.lifecycle.message_sent",
816            Self::MessageProcessed { .. } => "astrid.v1.lifecycle.message_processed",
817            // LLM
818            Self::LlmRequestStarted { .. } => "astrid.v1.lifecycle.llm_request_started",
819            Self::LlmRequestCompleted { .. } => "astrid.v1.lifecycle.llm_request_completed",
820            Self::LlmStreamStarted { .. } => "astrid.v1.lifecycle.llm_stream_started",
821            Self::LlmStreamChunk { .. } => "astrid.v1.lifecycle.llm_stream_chunk",
822            Self::LlmStreamCompleted { .. } => "astrid.v1.lifecycle.llm_stream_completed",
823            // Tool
824            Self::ToolCallStarted { .. } => "astrid.v1.lifecycle.tool_call_started",
825            Self::ToolCallCompleted { .. } => "astrid.v1.lifecycle.tool_call_completed",
826            Self::ToolCallFailed { .. } => "astrid.v1.lifecycle.tool_call_failed",
827            // MCP
828            Self::McpServerConnected { .. } => "astrid.v1.lifecycle.mcp_server_connected",
829            Self::McpServerDisconnected { .. } => "astrid.v1.lifecycle.mcp_server_disconnected",
830            Self::McpToolCalled { .. } => "astrid.v1.lifecycle.mcp_tool_called",
831            Self::McpToolCompleted { .. } => "astrid.v1.lifecycle.mcp_tool_completed",
832            // SubAgent
833            Self::SubAgentSpawned { .. } => "astrid.v1.lifecycle.sub_agent_spawned",
834            Self::SubAgentProgress { .. } => "astrid.v1.lifecycle.sub_agent_progress",
835            Self::SubAgentCompleted { .. } => "astrid.v1.lifecycle.sub_agent_completed",
836            Self::SubAgentFailed { .. } => "astrid.v1.lifecycle.sub_agent_failed",
837            Self::SubAgentCancelled { .. } => "astrid.v1.lifecycle.sub_agent_cancelled",
838            // Capsule
839            Self::CapsuleLoaded { .. } => "astrid.v1.lifecycle.capsule_loaded",
840            Self::CapsuleFailed { .. } => "astrid.v1.lifecycle.capsule_failed",
841            Self::CapsuleUnloaded { .. } => "astrid.v1.lifecycle.capsule_unloaded",
842            // Security
843            Self::CapabilityGranted { .. } => "astrid.v1.lifecycle.capability_granted",
844            Self::CapabilityRevoked { .. } => "astrid.v1.lifecycle.capability_revoked",
845            Self::CapabilityChecked { .. } => "astrid.v1.lifecycle.capability_checked",
846            Self::AuthorizationDenied { .. } => "astrid.v1.lifecycle.authorization_denied",
847            Self::SecurityViolation { .. } => "astrid.v1.lifecycle.security_violation",
848            // Approval
849            Self::ApprovalRequested { .. } => "astrid.v1.lifecycle.approval_requested",
850            Self::ApprovalGranted { .. } => "astrid.v1.lifecycle.approval_granted",
851            Self::ApprovalDenied { .. } => "astrid.v1.lifecycle.approval_denied",
852            // Budget
853            Self::BudgetAllocated { .. } => "astrid.v1.lifecycle.budget_allocated",
854            Self::BudgetWarning { .. } => "astrid.v1.lifecycle.budget_warning",
855            Self::BudgetExceeded { .. } => "astrid.v1.lifecycle.budget_exceeded",
856            // System
857            Self::KernelStarted { .. } => "astrid.v1.lifecycle.kernel_started",
858            Self::KernelShutdown { .. } => "astrid.v1.lifecycle.kernel_shutdown",
859            Self::ConfigReloaded { .. } => "astrid.v1.lifecycle.config_reloaded",
860            Self::ConfigChanged { .. } => "astrid.v1.lifecycle.config_changed",
861            Self::HealthCheckCompleted { .. } => "astrid.v1.lifecycle.health_check_completed",
862            // Audit
863            Self::AuditEntryCreated { .. } => "astrid.v1.lifecycle.audit_entry_created",
864            // Error
865            Self::ErrorOccurred { .. } => "astrid.v1.lifecycle.error_occurred",
866            // IPC
867            Self::Ipc { .. } => "ipc",
868            // Custom
869            Self::Custom { .. } => "custom",
870        }
871    }
872
873    /// Check if this is a security-related event (test-only).
874    #[cfg(test)]
875    #[must_use]
876    pub(crate) fn is_security_event(&self) -> bool {
877        matches!(
878            self,
879            Self::CapabilityGranted { .. }
880                | Self::CapabilityRevoked { .. }
881                | Self::CapabilityChecked { .. }
882                | Self::AuthorizationDenied { .. }
883                | Self::SecurityViolation { .. }
884                | Self::ApprovalRequested { .. }
885                | Self::ApprovalGranted { .. }
886                | Self::ApprovalDenied { .. }
887        )
888    }
889}
890
891#[cfg(test)]
892mod tests {
893    use super::*;
894
895    #[test]
896    fn test_event_metadata_creation() {
897        let meta = EventMetadata::new("test_source");
898        assert_eq!(meta.source, "test_source");
899        assert!(meta.correlation_id.is_none());
900        assert!(meta.session_id.is_none());
901        assert!(meta.user_id.is_none());
902    }
903
904    #[test]
905    fn test_event_metadata_builder() {
906        let correlation = Uuid::new_v4();
907        let session = Uuid::new_v4();
908        let user = Uuid::new_v4();
909
910        let meta = EventMetadata::new("test")
911            .with_correlation_id(correlation)
912            .with_session_id(session)
913            .with_user_id(user);
914
915        assert_eq!(meta.correlation_id, Some(correlation));
916        assert_eq!(meta.session_id, Some(session));
917        assert_eq!(meta.user_id, Some(user));
918    }
919
920    #[test]
921    fn test_event_type() {
922        let event = AstridEvent::RuntimeStarted {
923            metadata: EventMetadata::new("runtime"),
924            version: "0.1.0".to_string(),
925        };
926        assert_eq!(event.event_type(), "astrid.v1.lifecycle.runtime_started");
927    }
928
929    #[test]
930    fn test_security_event_detection() {
931        let security_event = AstridEvent::CapabilityGranted {
932            metadata: EventMetadata::new("security"),
933            capability_id: Uuid::new_v4(),
934            resource: "tool:test".to_string(),
935            action: "execute".to_string(),
936        };
937        assert!(security_event.is_security_event());
938
939        let non_security_event = AstridEvent::RuntimeStarted {
940            metadata: EventMetadata::new("runtime"),
941            version: "0.1.0".to_string(),
942        };
943        assert!(!non_security_event.is_security_event());
944    }
945
946    #[test]
947    fn test_event_serialization() {
948        let event = AstridEvent::McpToolCalled {
949            metadata: EventMetadata::new("mcp"),
950            server_name: "filesystem".to_string(),
951            tool_name: "read_file".to_string(),
952            arguments: Some(serde_json::json!({"path": "/tmp/test.txt"})),
953        };
954
955        let json = serde_json::to_string(&event).unwrap();
956        assert!(json.contains("mcp_tool_called"));
957        assert!(json.contains("filesystem"));
958    }
959}