Skip to main content

rust_supervisor/event/
payload.rs

1//! Lifecycle event payloads and event envelopes.
2//!
3//! This module owns the observable shape of supervisor lifecycle facts. It keeps
4//! payloads typed so state, journal, metrics, and tests do not infer behavior
5//! from strings.
6
7use crate::error::types::TaskFailure;
8use crate::event::time::{CorrelationId, EventSequence, When};
9use crate::id::types::{ChildId, SupervisorPath};
10use serde::{Deserialize, Serialize};
11
12/// Location data attached to a supervisor event.
13#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
14pub struct Where {
15    /// Stable supervisor path that owns the fact.
16    pub supervisor_path: SupervisorPath,
17    /// Parent child identifier when the fact belongs to a nested node.
18    pub parent_id: Option<ChildId>,
19    /// Child identifier related to the fact.
20    pub child_id: Option<ChildId>,
21    /// Human-readable child name.
22    pub child_name: Option<String>,
23    /// Tokio task identifier when it is available.
24    pub tokio_task_id: Option<String>,
25    /// Host name reported by the runtime.
26    pub host: Option<String>,
27    /// Process identifier that emitted the event.
28    pub pid: u32,
29    /// Current thread name when available.
30    pub thread_name: Option<String>,
31    /// Rust module path that emitted the event.
32    pub module_path: Option<String>,
33    /// Source file that emitted the event.
34    pub source_file: Option<String>,
35    /// Source line that emitted the event.
36    pub source_line: Option<u32>,
37}
38
39impl Where {
40    /// Creates a location for a supervisor path.
41    ///
42    /// # Arguments
43    ///
44    /// - `supervisor_path`: Path that owns this lifecycle fact.
45    ///
46    /// # Returns
47    ///
48    /// Returns a [`Where`] value with process and thread defaults.
49    ///
50    /// # Examples
51    ///
52    /// ```
53    /// let location = rust_supervisor::event::payload::Where::new(
54    ///     rust_supervisor::id::types::SupervisorPath::root(),
55    /// );
56    /// assert_eq!(location.supervisor_path.to_string(), "/");
57    /// ```
58    pub fn new(supervisor_path: SupervisorPath) -> Self {
59        Self {
60            supervisor_path,
61            parent_id: None,
62            child_id: None,
63            child_name: None,
64            tokio_task_id: None,
65            host: None,
66            pid: std::process::id(),
67            thread_name: std::thread::current().name().map(ToOwned::to_owned),
68            module_path: None,
69            source_file: None,
70            source_line: None,
71        }
72    }
73
74    /// Adds child identity to the location.
75    ///
76    /// # Arguments
77    ///
78    /// - `child_id`: Stable child identifier.
79    /// - `child_name`: Human-readable child name.
80    ///
81    /// # Returns
82    ///
83    /// Returns the updated [`Where`] value.
84    pub fn with_child(mut self, child_id: ChildId, child_name: impl Into<String>) -> Self {
85        self.child_id = Some(child_id);
86        self.child_name = Some(child_name.into());
87        self
88    }
89}
90
91/// State transition recorded by an event payload.
92#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
93pub struct StateTransition {
94    /// State before the transition.
95    pub from: String,
96    /// State after the transition.
97    pub to: String,
98}
99
100impl StateTransition {
101    /// Creates a state transition description.
102    ///
103    /// # Arguments
104    ///
105    /// - `from`: Previous state name.
106    /// - `to`: New state name.
107    ///
108    /// # Returns
109    ///
110    /// Returns a [`StateTransition`].
111    pub fn new(from: impl Into<String>, to: impl Into<String>) -> Self {
112        Self {
113            from: from.into(),
114            to: to.into(),
115        }
116    }
117}
118
119/// Policy decision data stored with an event.
120#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
121pub struct PolicyDecision {
122    /// Low-cardinality decision name.
123    pub decision: String,
124    /// Delay in milliseconds when restart is delayed.
125    pub delay_ms: Option<u64>,
126    /// Human-readable reason for diagnostics.
127    pub reason: Option<String>,
128}
129
130impl PolicyDecision {
131    /// Creates a policy decision value.
132    ///
133    /// # Arguments
134    ///
135    /// - `decision`: Low-cardinality decision name.
136    /// - `delay_ms`: Optional delay in milliseconds.
137    /// - `reason`: Optional diagnostic reason.
138    ///
139    /// # Returns
140    ///
141    /// Returns a [`PolicyDecision`].
142    pub fn new(decision: impl Into<String>, delay_ms: Option<u64>, reason: Option<String>) -> Self {
143        Self {
144            decision: decision.into(),
145            delay_ms,
146            reason,
147        }
148    }
149}
150
151/// Command audit data attached to command lifecycle events.
152#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
153pub struct CommandAudit {
154    /// Stable command identifier.
155    pub command_id: String,
156    /// Actor that requested the command.
157    pub requested_by: String,
158    /// Operator-provided reason.
159    pub reason: String,
160    /// Target path for the command.
161    pub target_path: SupervisorPath,
162    /// Accepted time in nanoseconds since the Unix epoch.
163    pub accepted_at_unix_nanos: u128,
164    /// Command result summary.
165    pub result: String,
166}
167
168/// Typed payload for supervisor lifecycle events.
169#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
170pub enum What {
171    /// Child is being started.
172    ChildStarting {
173        /// Optional state transition carried by this event.
174        transition: Option<StateTransition>,
175    },
176    /// Child is running.
177    ChildRunning {
178        /// Optional state transition carried by this event.
179        transition: Option<StateTransition>,
180    },
181    /// Child is ready.
182    ChildReady {
183        /// Optional state transition carried by this event.
184        transition: Option<StateTransition>,
185    },
186    /// Child emitted a heartbeat.
187    ChildHeartbeat {
188        /// Heartbeat age in milliseconds.
189        age_ms: u64,
190    },
191    /// Child failed with a typed failure.
192    ChildFailed {
193        /// Failure payload reported by the task.
194        failure: TaskFailure,
195    },
196    /// Child panicked.
197    ChildPanicked {
198        /// Panic category used for metrics.
199        category: String,
200    },
201    /// Restart backoff was scheduled.
202    BackoffScheduled {
203        /// Backoff delay in milliseconds.
204        delay_ms: u64,
205    },
206    /// Child is restarting.
207    ChildRestarting {
208        /// Restart generation after the transition.
209        generation: u64,
210    },
211    /// Child restarted.
212    ChildRestarted {
213        /// Restart count for the child window.
214        restart_count: u64,
215    },
216    /// Child was quarantined.
217    ChildQuarantined {
218        /// Quarantine reason.
219        reason: String,
220    },
221    /// Child stopped.
222    ChildStopped {
223        /// Exit reason.
224        reason: String,
225    },
226    /// Child became unhealthy.
227    ChildUnhealthy {
228        /// Unhealthy reason.
229        reason: String,
230    },
231    /// Meltdown fuse was tripped.
232    Meltdown {
233        /// Scope that tripped the fuse.
234        scope: String,
235    },
236    /// Shutdown was requested.
237    ShutdownRequested {
238        /// Shutdown cause.
239        cause: String,
240    },
241    /// Shutdown phase changed.
242    ShutdownPhaseChanged {
243        /// Previous phase name.
244        from: String,
245        /// New phase name.
246        to: String,
247    },
248    /// Shutdown completed.
249    ShutdownCompleted {
250        /// Shutdown result summary.
251        result: String,
252    },
253    /// Control command was accepted.
254    CommandAccepted {
255        /// Command audit payload.
256        audit: CommandAudit,
257    },
258    /// Control command completed.
259    CommandCompleted {
260        /// Command audit payload.
261        audit: CommandAudit,
262    },
263    /// Event subscriber lagged.
264    SubscriberLagged {
265        /// Number of missed events.
266        missed: u64,
267    },
268}
269
270impl What {
271    /// Returns a low-cardinality event name.
272    ///
273    /// # Arguments
274    ///
275    /// This function has no arguments.
276    ///
277    /// # Returns
278    ///
279    /// Returns the stable event name.
280    ///
281    /// # Examples
282    ///
283    /// ```
284    /// let event = rust_supervisor::event::payload::What::ChildRunning {
285    ///     transition: None,
286    /// };
287    /// assert_eq!(event.name(), "ChildRunning");
288    /// ```
289    pub fn name(&self) -> &'static str {
290        match self {
291            Self::ChildStarting { .. } => "ChildStarting",
292            Self::ChildRunning { .. } => "ChildRunning",
293            Self::ChildReady { .. } => "ChildReady",
294            Self::ChildHeartbeat { .. } => "ChildHeartbeat",
295            Self::ChildFailed { .. } => "ChildFailed",
296            Self::ChildPanicked { .. } => "ChildPanicked",
297            Self::BackoffScheduled { .. } => "BackoffScheduled",
298            Self::ChildRestarting { .. } => "ChildRestarting",
299            Self::ChildRestarted { .. } => "ChildRestarted",
300            Self::ChildQuarantined { .. } => "ChildQuarantined",
301            Self::ChildStopped { .. } => "ChildStopped",
302            Self::ChildUnhealthy { .. } => "ChildUnhealthy",
303            Self::Meltdown { .. } => "Meltdown",
304            Self::ShutdownRequested { .. } => "ShutdownRequested",
305            Self::ShutdownPhaseChanged { .. } => "ShutdownPhaseChanged",
306            Self::ShutdownCompleted { .. } => "ShutdownCompleted",
307            Self::CommandAccepted { .. } => "CommandAccepted",
308            Self::CommandCompleted { .. } => "CommandCompleted",
309            Self::SubscriberLagged { .. } => "SubscriberLagged",
310        }
311    }
312}
313
314/// Complete lifecycle event envelope.
315#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
316pub struct SupervisorEvent {
317    /// Time information for the lifecycle fact.
318    pub when: When,
319    /// Location information for the lifecycle fact.
320    pub r#where: Where,
321    /// Typed event payload.
322    pub what: What,
323    /// Optional policy decision related to the event.
324    pub policy: Option<PolicyDecision>,
325    /// Monotonic event sequence.
326    pub sequence: EventSequence,
327    /// Correlation identifier shared by related signals.
328    pub correlation_id: CorrelationId,
329    /// Configuration version that produced this fact.
330    pub config_version: u64,
331}
332
333impl SupervisorEvent {
334    /// Creates a supervisor lifecycle event.
335    ///
336    /// # Arguments
337    ///
338    /// - `when`: Event timing.
339    /// - `r#where`: Event location.
340    /// - `what`: Event payload.
341    /// - `sequence`: Monotonic event sequence.
342    /// - `correlation_id`: Correlation identifier for related signals.
343    /// - `config_version`: Configuration version for this event.
344    ///
345    /// # Returns
346    ///
347    /// Returns a [`SupervisorEvent`].
348    ///
349    /// # Examples
350    ///
351    /// ```
352    /// let event = rust_supervisor::event::payload::SupervisorEvent::new(
353    ///     rust_supervisor::event::time::When::new(
354    ///         rust_supervisor::event::time::EventTime::deterministic(
355    ///             1,
356    ///             1,
357    ///             0,
358    ///             rust_supervisor::id::types::Generation::initial(),
359    ///             rust_supervisor::id::types::Attempt::first(),
360    ///         ),
361    ///     ),
362    ///     rust_supervisor::event::payload::Where::new(
363    ///         rust_supervisor::id::types::SupervisorPath::root(),
364    ///     ),
365    ///     rust_supervisor::event::payload::What::ChildRunning { transition: None },
366    ///     rust_supervisor::event::time::EventSequence::new(1),
367    ///     rust_supervisor::event::time::CorrelationId::from_uuid(uuid::Uuid::nil()),
368    ///     1,
369    /// );
370    /// assert_eq!(event.what.name(), "ChildRunning");
371    /// ```
372    pub fn new(
373        when: When,
374        r#where: Where,
375        what: What,
376        sequence: EventSequence,
377        correlation_id: CorrelationId,
378        config_version: u64,
379    ) -> Self {
380        Self {
381            when,
382            r#where,
383            what,
384            policy: None,
385            sequence,
386            correlation_id,
387            config_version,
388        }
389    }
390
391    /// Attaches a policy decision to an event.
392    ///
393    /// # Arguments
394    ///
395    /// - `policy`: Policy decision produced for this lifecycle fact.
396    ///
397    /// # Returns
398    ///
399    /// Returns the updated [`SupervisorEvent`].
400    pub fn with_policy(mut self, policy: PolicyDecision) -> Self {
401        self.policy = Some(policy);
402        self
403    }
404}