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}