Skip to main content

rust_supervisor/control/
command.rs

1//! Runtime control command contract.
2//!
3//! This module owns auditable command inputs and command results. Runtime code
4//! executes these commands and records state changes.
5
6use crate::control::outcome::{ChildControlResult, ChildRuntimeRecord};
7use crate::error::types::SupervisorError;
8use crate::id::types::{ChildId, SupervisorPath};
9use crate::shutdown::coordinator::ShutdownResult;
10use serde::{Deserialize, Serialize};
11use uuid::Uuid;
12
13/// Stable identifier for an accepted control command.
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
15pub struct CommandId {
16    /// UUID value assigned when a command is created.
17    pub value: Uuid,
18}
19
20impl CommandId {
21    /// Creates a command identifier.
22    ///
23    /// # Arguments
24    ///
25    /// This function has no arguments.
26    ///
27    /// # Returns
28    ///
29    /// Returns a new [`CommandId`].
30    ///
31    /// # Examples
32    ///
33    /// ```
34    /// let id = rust_supervisor::control::command::CommandId::new();
35    /// assert!(!id.value.is_nil());
36    /// ```
37    pub fn new() -> Self {
38        Self {
39            value: Uuid::new_v4(),
40        }
41    }
42
43    /// Creates a command identifier from an existing UUID.
44    ///
45    /// Used by dashboard IPC when the relay supplies a command_id for
46    /// end-to-end tracing.
47    ///
48    /// # Arguments
49    ///
50    /// - `value`: UUID value to use.
51    ///
52    /// # Returns
53    ///
54    /// Returns a [`CommandId`] with the supplied UUID.
55    pub const fn from_uuid(value: Uuid) -> Self {
56        Self { value }
57    }
58}
59
60impl Default for CommandId {
61    /// Creates the default command identifier.
62    fn default() -> Self {
63        Self::new()
64    }
65}
66
67/// Audit metadata attached to each runtime control command.
68#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
69pub struct CommandMeta {
70    /// Command identifier used for audit correlation.
71    pub command_id: CommandId,
72    /// Caller that requested the command.
73    pub requested_by: String,
74    /// Human-readable command reason.
75    pub reason: String,
76}
77
78impl CommandMeta {
79    /// Creates command metadata with a newly generated command identifier.
80    ///
81    /// # Arguments
82    ///
83    /// - `requested_by`: Caller that requested the command.
84    /// - `reason`: Human-readable command reason.
85    ///
86    /// # Returns
87    ///
88    /// Returns a [`CommandMeta`] value with a generated command identifier.
89    pub fn new(requested_by: impl Into<String>, reason: impl Into<String>) -> Self {
90        Self {
91            command_id: CommandId::new(),
92            requested_by: requested_by.into(),
93            reason: reason.into(),
94        }
95    }
96
97    /// Creates command metadata with an explicit command identifier.
98    ///
99    /// Used by dashboard IPC when the relay supplies a command_id for
100    /// end-to-end tracing across relay, target, and UI.
101    ///
102    /// # Arguments
103    ///
104    /// - `command_id`: Explicit command identifier.
105    /// - `requested_by`: Caller that requested the command.
106    /// - `reason`: Human-readable command reason.
107    ///
108    /// # Returns
109    ///
110    /// Returns a [`CommandMeta`] value with the supplied command identifier.
111    pub fn with_id(
112        command_id: CommandId,
113        requested_by: impl Into<String>,
114        reason: impl Into<String>,
115    ) -> Self {
116        Self {
117            command_id,
118            requested_by: requested_by.into(),
119            reason: reason.into(),
120        }
121    }
122
123    /// Validates audit metadata before command dispatch.
124    ///
125    /// # Arguments
126    ///
127    /// This function has no arguments.
128    ///
129    /// # Returns
130    ///
131    /// Returns `Ok(())` when actor and reason fields are non-empty.
132    pub(crate) fn validate(&self) -> Result<(), SupervisorError> {
133        validate_required_text(&self.requested_by, "requested_by")?;
134        validate_required_text(&self.reason, "reason")
135    }
136}
137
138/// Runtime command sent to the control loop.
139#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
140pub enum ControlCommand {
141    /// Adds a child description under a supervisor path.
142    AddChild {
143        /// Audit metadata for the command.
144        meta: CommandMeta,
145        /// Target supervisor path.
146        target: SupervisorPath,
147        /// Child manifest text owned by the caller.
148        child_manifest: String,
149    },
150    /// Removes a child after shutting it down.
151    RemoveChild {
152        /// Audit metadata for the command.
153        meta: CommandMeta,
154        /// Target child identifier.
155        child_id: ChildId,
156    },
157    /// Restarts a child explicitly.
158    RestartChild {
159        /// Audit metadata for the command.
160        meta: CommandMeta,
161        /// Target child identifier.
162        child_id: ChildId,
163    },
164    /// Pauses automatic governance for a child.
165    PauseChild {
166        /// Audit metadata for the command.
167        meta: CommandMeta,
168        /// Target child identifier.
169        child_id: ChildId,
170    },
171    /// Resumes automatic governance for a child.
172    ResumeChild {
173        /// Audit metadata for the command.
174        meta: CommandMeta,
175        /// Target child identifier.
176        child_id: ChildId,
177    },
178    /// Quarantines a child and stops automatic restarts.
179    QuarantineChild {
180        /// Audit metadata for the command.
181        meta: CommandMeta,
182        /// Target child identifier.
183        child_id: ChildId,
184    },
185    /// Starts shutdown for the whole supervisor tree.
186    ShutdownTree {
187        /// Audit metadata for the command.
188        meta: CommandMeta,
189    },
190    /// Reads current runtime state.
191    CurrentState {
192        /// Audit metadata for the command.
193        meta: CommandMeta,
194    },
195}
196
197impl ControlCommand {
198    /// Returns audit metadata for this command.
199    ///
200    /// # Arguments
201    ///
202    /// This function has no arguments.
203    ///
204    /// # Returns
205    ///
206    /// Returns a shared reference to [`CommandMeta`].
207    pub fn meta(&self) -> &CommandMeta {
208        match self {
209            Self::AddChild { meta, .. }
210            | Self::RemoveChild { meta, .. }
211            | Self::RestartChild { meta, .. }
212            | Self::PauseChild { meta, .. }
213            | Self::ResumeChild { meta, .. }
214            | Self::QuarantineChild { meta, .. }
215            | Self::ShutdownTree { meta }
216            | Self::CurrentState { meta } => meta,
217        }
218    }
219
220    /// Validates audit metadata attached to this command.
221    ///
222    /// # Arguments
223    ///
224    /// This function has no arguments.
225    ///
226    /// # Returns
227    ///
228    /// Returns `Ok(())` when the command carries auditable metadata.
229    pub(crate) fn validate_audit_metadata(&self) -> Result<(), SupervisorError> {
230        self.meta().validate()
231    }
232}
233
234/// Validates one required text field.
235///
236/// # Arguments
237///
238/// - `value`: Text value supplied by the command caller.
239/// - `field`: Field name used in the diagnostic message.
240///
241/// # Returns
242///
243/// Returns `Ok(())` when the value is not blank.
244fn validate_required_text(value: &str, field: &str) -> Result<(), SupervisorError> {
245    if value.trim().is_empty() {
246        return Err(SupervisorError::InvalidTransition {
247            message: format!("control command {field} must not be empty"),
248        });
249    }
250    Ok(())
251}
252
253/// Current runtime state returned by `current_state`.
254#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
255pub struct CurrentState {
256    /// Number of children known to the control loop.
257    pub child_count: usize,
258    /// Whether tree shutdown has completed.
259    pub shutdown_completed: bool,
260    /// Runtime state records for declared children.
261    pub child_runtime_records: Vec<ChildRuntimeRecord>,
262}
263
264/// Result returned after a control command is executed.
265#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
266pub enum CommandResult {
267    /// Child was accepted by the control loop.
268    ChildAdded {
269        /// Child manifest stored by the runtime.
270        child_manifest: String,
271    },
272    /// Child control result after a command.
273    ChildControl {
274        /// Outcome produced by the control command.
275        outcome: ChildControlResult,
276    },
277    /// Current state query result.
278    CurrentState {
279        /// Runtime current state.
280        state: CurrentState,
281    },
282    /// Shutdown command result.
283    Shutdown {
284        /// Shutdown phase and cause.
285        result: ShutdownResult,
286    },
287}