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}