Skip to main content

rust_supervisor/dashboard/
model.rs

1//! Shared dashboard data model.
2//!
3//! These structs are the JSON contract shared by target IPC, relay, and the
4//! dashboard UI. They intentionally use owned values so callers can serialize,
5//! clone, and test messages without borrowing runtime internals.
6
7use crate::control::command::{CommandResult, CurrentState};
8use crate::control::outcome::{
9    ChildAttemptStatus, ChildControlFailure, ChildControlFailurePhase, ChildControlOperation,
10    ChildControlResult as RuntimeChildControlResult, ChildLivenessState, ChildRuntimeRecord,
11    ChildStopState, GenerationFenceDecision, GenerationFenceOutcome, GenerationFencePhase,
12    PendingRestartSummary, RestartLimitState,
13};
14use crate::readiness::signal::ReadinessState;
15use schemars::JsonSchema;
16use serde::{Deserialize, Serialize};
17use serde_json::Value;
18use std::collections::BTreeMap;
19
20/// Supported command metadata sent to the relay.
21#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
22pub struct SupportedCommand {
23    /// Wire command name.
24    pub name: String,
25    /// Whether the command can be retried with the same command identifier.
26    pub idempotent: bool,
27    /// Command timeout in seconds.
28    pub timeout_seconds: u64,
29}
30
31/// Target process registration payload sent to the relay.
32#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
33pub struct TargetProcessRegistration {
34    /// Stable target process identifier.
35    pub target_id: String,
36    /// Human-readable display name.
37    pub display_name: String,
38    /// Local Unix domain socket path exposed by the target.
39    pub ipc_path: String,
40    /// Lease duration in seconds.
41    pub lease_seconds: u64,
42    /// Commands supported by this target.
43    pub supported_commands: Vec<SupportedCommand>,
44}
45
46/// Current registration state for a target process.
47#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
48#[serde(rename_all = "snake_case")]
49pub enum RegistrationState {
50    /// Registration was accepted and is visible.
51    Active,
52    /// Registration was rejected.
53    Rejected,
54    /// Registration lease expired.
55    Expired,
56}
57
58/// Current relay connection state for a target process.
59#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
60#[serde(rename_all = "snake_case")]
61pub enum TargetConnectionState {
62    /// Target is registered but no session has bound it.
63    Registered,
64    /// Relay is connecting to target IPC.
65    Connecting,
66    /// Relay is connected to target IPC.
67    Connected,
68    /// Relay is reconnecting to target IPC.
69    Reconnecting,
70    /// Target IPC is unavailable.
71    Unavailable,
72    /// Registration lease expired.
73    Expired,
74}
75
76/// Target identity shown in dashboard state payloads and target lists.
77#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
78pub struct TargetProcessIdentity {
79    /// Stable target process identifier.
80    pub target_id: String,
81    /// Human-readable display name.
82    pub display_name: String,
83    /// Current registration state.
84    pub registration_state: RegistrationState,
85    /// Current relay connection state.
86    pub connection_state: TargetConnectionState,
87}
88
89/// Complete dashboard state returned when a target is opened or reconnected.
90#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
91pub struct DashboardState {
92    /// Target process identity.
93    pub target: TargetProcessIdentity,
94    /// Supervisor topology.
95    pub topology: SupervisorTopology,
96    /// Runtime state rows indexed by child path.
97    pub runtime_state: Vec<RuntimeState>,
98    /// Runtime records returned by the control loop current state.
99    pub child_runtime_records: Vec<DashboardChildRuntimeRecord>,
100    /// Recent events retained by the target.
101    pub recent_events: Vec<EventRecord>,
102    /// Recent logs retained by the target.
103    pub recent_logs: Vec<LogRecord>,
104    /// Number of dropped events.
105    pub dropped_event_count: u64,
106    /// Number of dropped logs.
107    pub dropped_log_count: u64,
108    /// Configuration version string.
109    pub config_version: String,
110    /// Generated time as Unix nanoseconds.
111    pub generated_at_unix_nanos: u128,
112    /// Monotonic state generation for this target.
113    pub state_generation: u64,
114}
115
116/// Supervisor graph for dashboard rendering.
117#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
118pub struct SupervisorTopology {
119    /// Root supervisor node.
120    pub root: SupervisorNode,
121    /// All visible nodes including the root.
122    pub nodes: Vec<SupervisorNode>,
123    /// Parent-child and dependency edges.
124    pub edges: Vec<SupervisorEdge>,
125    /// Node paths in declaration order.
126    pub declaration_order: Vec<String>,
127}
128
129/// Node kind visible in the topology.
130#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
131#[serde(rename_all = "snake_case")]
132pub enum SupervisorNodeKind {
133    /// Root supervisor node.
134    RootSupervisor,
135    /// Child task node.
136    ChildTask,
137}
138
139/// Criticality shown by dashboard nodes.
140#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
141#[serde(rename_all = "snake_case")]
142pub enum DashboardCriticality {
143    /// Critical child.
144    Critical,
145    /// Standard child.
146    Standard,
147    /// Best-effort child.
148    BestEffort,
149}
150
151/// Node displayed in the supervisor topology.
152#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
153pub struct SupervisorNode {
154    /// Stable node identifier.
155    pub node_id: String,
156    /// Optional child identifier.
157    pub child_id: Option<String>,
158    /// Absolute child path.
159    pub path: String,
160    /// Human-readable node name.
161    pub name: String,
162    /// Node kind.
163    pub kind: SupervisorNodeKind,
164    /// Low-cardinality tags.
165    pub tags: Vec<String>,
166    /// Node criticality.
167    pub criticality: DashboardCriticality,
168    /// Current state summary.
169    pub state_summary: String,
170    /// Key diagnostic fields.
171    pub diagnostics: BTreeMap<String, String>,
172}
173
174/// Edge kind visible in the topology.
175#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
176#[serde(rename_all = "snake_case")]
177pub enum SupervisorEdgeKind {
178    /// Parent-child edge.
179    ParentChild,
180    /// Dependency edge.
181    Dependency,
182}
183
184/// Edge displayed in the supervisor topology.
185#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
186pub struct SupervisorEdge {
187    /// Stable edge identifier.
188    pub edge_id: String,
189    /// Source node path.
190    pub source_path: String,
191    /// Target node path.
192    pub target_path: String,
193    /// Edge kind.
194    pub kind: SupervisorEdgeKind,
195    /// Declaration or dependency order.
196    pub order: usize,
197}
198
199/// Runtime state shown for one child.
200#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
201pub struct RuntimeState {
202    /// Child path.
203    pub child_path: String,
204    /// Lifecycle state label.
205    pub lifecycle_state: String,
206    /// Health status label.
207    pub health: String,
208    /// Readiness status label.
209    pub readiness: String,
210    /// Child generation.
211    pub generation: u64,
212    /// Child child_start_count.
213    pub child_start_count: u64,
214    /// Restart count.
215    pub restart_count: u64,
216    /// Optional last failure summary.
217    pub last_failure: Option<String>,
218    /// Optional last policy decision summary.
219    pub last_policy_decision: Option<String>,
220    /// Supervisor shutdown state label.
221    pub shutdown_state: String,
222}
223
224/// Managed child state derived for dashboard display.
225#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
226#[serde(rename_all = "snake_case")]
227pub enum DashboardManagedChildState {
228    /// Child is active and should be displayed as running.
229    Running,
230    /// Child is paused.
231    Paused,
232    /// Child is quarantined.
233    Quarantined,
234    /// Child is removed.
235    Removed,
236}
237
238impl From<ChildControlOperation> for DashboardManagedChildState {
239    /// Converts a runtime control operation into a dashboard managed state.
240    fn from(value: ChildControlOperation) -> Self {
241        match value {
242            ChildControlOperation::Active => Self::Running,
243            ChildControlOperation::Paused => Self::Paused,
244            ChildControlOperation::Quarantined => Self::Quarantined,
245            ChildControlOperation::Removed => Self::Removed,
246        }
247    }
248}
249
250impl DashboardManagedChildState {
251    /// Returns the stable dashboard label.
252    ///
253    /// # Arguments
254    ///
255    /// This function has no arguments.
256    ///
257    /// # Returns
258    ///
259    /// Returns the lifecycle label used by runtime rows.
260    pub fn as_label(&self) -> &'static str {
261        match self {
262            Self::Running => "running",
263            Self::Paused => "paused",
264            Self::Quarantined => "quarantined",
265            Self::Removed => "removed",
266        }
267    }
268}
269
270/// Child control operation label for dashboard payloads.
271#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
272#[serde(rename_all = "snake_case")]
273pub enum DashboardChildControlOperation {
274    /// Runtime state remains active.
275    Active,
276    /// Runtime state is paused.
277    Paused,
278    /// Runtime state is quarantined.
279    Quarantined,
280    /// Runtime state is removed or waiting for removal.
281    Removed,
282}
283
284impl From<ChildControlOperation> for DashboardChildControlOperation {
285    /// Converts a runtime control operation into a dashboard operation.
286    fn from(value: ChildControlOperation) -> Self {
287        match value {
288            ChildControlOperation::Active => Self::Active,
289            ChildControlOperation::Paused => Self::Paused,
290            ChildControlOperation::Quarantined => Self::Quarantined,
291            ChildControlOperation::Removed => Self::Removed,
292        }
293    }
294}
295
296/// Attempt status label for dashboard payloads.
297#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
298#[serde(rename_all = "snake_case")]
299pub enum DashboardChildAttemptStatus {
300    /// Child attempt is starting.
301    Starting,
302    /// Child attempt is running.
303    Running,
304    /// Child attempt is ready.
305    Ready,
306    /// Child attempt is cancelling.
307    Cancelling,
308    /// Child attempt has stopped.
309    Stopped,
310}
311
312impl From<ChildAttemptStatus> for DashboardChildAttemptStatus {
313    /// Converts a runtime attempt status into a dashboard attempt status.
314    fn from(value: ChildAttemptStatus) -> Self {
315        match value {
316            ChildAttemptStatus::Starting => Self::Starting,
317            ChildAttemptStatus::Running => Self::Running,
318            ChildAttemptStatus::Ready => Self::Ready,
319            ChildAttemptStatus::Cancelling => Self::Cancelling,
320            ChildAttemptStatus::Stopped => Self::Stopped,
321        }
322    }
323}
324
325/// Stop state label for dashboard payloads.
326#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
327#[serde(rename_all = "snake_case")]
328pub enum DashboardChildStopState {
329    /// No stop action is in progress.
330    Idle,
331    /// No active attempt exists.
332    NoActiveAttempt,
333    /// Cancellation was delivered.
334    CancelDelivered,
335    /// Stop completed.
336    Completed,
337    /// Stop failed.
338    Failed,
339}
340
341impl From<ChildStopState> for DashboardChildStopState {
342    /// Converts a runtime stop state into a dashboard stop state.
343    fn from(value: ChildStopState) -> Self {
344        match value {
345            ChildStopState::Idle => Self::Idle,
346            ChildStopState::NoActiveAttempt => Self::NoActiveAttempt,
347            ChildStopState::CancelDelivered => Self::CancelDelivered,
348            ChildStopState::Completed => Self::Completed,
349            ChildStopState::Failed => Self::Failed,
350        }
351    }
352}
353
354/// Readiness state label for dashboard payloads.
355#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
356#[serde(rename_all = "snake_case")]
357pub enum DashboardReadinessState {
358    /// Readiness has not been reported.
359    Unreported,
360    /// Child reported readiness.
361    Ready,
362    /// Child reported that it is not ready.
363    NotReady,
364}
365
366impl From<ReadinessState> for DashboardReadinessState {
367    /// Converts a runtime readiness state into a dashboard readiness state.
368    fn from(value: ReadinessState) -> Self {
369        match value {
370            ReadinessState::Unreported => Self::Unreported,
371            ReadinessState::Ready => Self::Ready,
372            ReadinessState::NotReady => Self::NotReady,
373        }
374    }
375}
376
377impl DashboardReadinessState {
378    /// Returns the stable dashboard label.
379    ///
380    /// # Arguments
381    ///
382    /// This function has no arguments.
383    ///
384    /// # Returns
385    ///
386    /// Returns the readiness label used by runtime rows.
387    pub fn as_label(&self) -> &'static str {
388        match self {
389            Self::Unreported => "unreported",
390            Self::Ready => "ready",
391            Self::NotReady => "not_ready",
392        }
393    }
394}
395
396/// Failure phase label for dashboard payloads.
397#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
398#[serde(rename_all = "snake_case")]
399pub enum DashboardChildControlFailurePhase {
400    /// Waiting for completion failed.
401    WaitCompletion,
402}
403
404impl From<ChildControlFailurePhase> for DashboardChildControlFailurePhase {
405    /// Converts a runtime failure phase into a dashboard failure phase.
406    fn from(value: ChildControlFailurePhase) -> Self {
407        match value {
408            ChildControlFailurePhase::WaitCompletion => Self::WaitCompletion,
409        }
410    }
411}
412
413/// Liveness facts shown by dashboard runtime records.
414#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
415pub struct DashboardChildLivenessState {
416    /// Last heartbeat as Unix nanoseconds.
417    pub last_heartbeat_at_unix_nanos: Option<u128>,
418    /// Whether the heartbeat is stale.
419    pub heartbeat_stale: bool,
420    /// Latest readiness state.
421    pub readiness: DashboardReadinessState,
422}
423
424impl DashboardChildLivenessState {
425    /// Converts runtime liveness into a dashboard liveness model.
426    ///
427    /// # Arguments
428    ///
429    /// - `value`: Runtime liveness state.
430    ///
431    /// # Returns
432    ///
433    /// Returns a dashboard liveness state.
434    pub fn from_liveness(value: &ChildLivenessState) -> Self {
435        Self {
436            last_heartbeat_at_unix_nanos: value.last_heartbeat_at_unix_nanos,
437            heartbeat_stale: value.heartbeat_stale,
438            readiness: DashboardReadinessState::from(value.readiness),
439        }
440    }
441}
442
443/// Restart limit facts shown by dashboard runtime records.
444#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
445pub struct DashboardRestartLimitState {
446    /// Restart accounting window in milliseconds.
447    pub window_millis: u128,
448    /// Restart limit inside the window.
449    pub limit: u32,
450    /// Restart count used so far.
451    pub used: u32,
452    /// Remaining restart count.
453    pub remaining: u32,
454    /// Whether the restart limit is exhausted.
455    pub exhausted: bool,
456    /// Last update timestamp in Unix nanoseconds.
457    pub updated_at_unix_nanos: u128,
458}
459
460impl DashboardRestartLimitState {
461    /// Converts runtime restart limit into a dashboard restart limit model.
462    ///
463    /// # Arguments
464    ///
465    /// - `value`: Runtime restart limit state.
466    ///
467    /// # Returns
468    ///
469    /// Returns a dashboard restart limit state.
470    pub fn from_restart_limit(value: &RestartLimitState) -> Self {
471        Self {
472            window_millis: value.window.as_millis(),
473            limit: value.limit,
474            used: value.used,
475            remaining: value.remaining,
476            exhausted: value.exhausted,
477            updated_at_unix_nanos: value.updated_at_unix_nanos,
478        }
479    }
480}
481
482/// Structured control failure shown by dashboard payloads.
483#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
484pub struct DashboardChildControlFailure {
485    /// Failure phase.
486    pub phase: DashboardChildControlFailurePhase,
487    /// Human-readable failure reason.
488    pub reason: String,
489    /// Whether callers can retry.
490    pub recoverable: bool,
491}
492
493impl DashboardChildControlFailure {
494    /// Converts a runtime control failure into a dashboard failure model.
495    ///
496    /// # Arguments
497    ///
498    /// - `value`: Runtime control failure.
499    ///
500    /// # Returns
501    ///
502    /// Returns a dashboard control failure.
503    pub fn from_failure(value: &ChildControlFailure) -> Self {
504        Self {
505            phase: DashboardChildControlFailurePhase::from(value.phase),
506            reason: value.reason.clone(),
507            recoverable: value.recoverable,
508        }
509    }
510}
511
512/// Dashboard projection for generation fencing phases.
513#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
514#[serde(rename_all = "snake_case")]
515pub enum DashboardGenerationFencePhase {
516    /// No fencing wait state.
517    Open,
518    /// Restart accepted; waiting for the old attempt to stop.
519    WaitingForOldStop,
520    /// Abort escalated against the outdated attempt.
521    AbortingOld,
522    /// Fence cleared; eligible to spawn the queued generation when other gates permit.
523    ReadyToStart,
524    /// Record removed or fencing closed due to supervisor shutdown semantics.
525    Closed,
526}
527
528impl From<GenerationFencePhase> for DashboardGenerationFencePhase {
529    /// Mirrors the runtime enumeration into dashboard JSON payloads.
530    fn from(value: GenerationFencePhase) -> Self {
531        match value {
532            GenerationFencePhase::Open => Self::Open,
533            GenerationFencePhase::WaitingForOldStop => Self::WaitingForOldStop,
534            GenerationFencePhase::AbortingOld => Self::AbortingOld,
535            GenerationFencePhase::ReadyToStart => Self::ReadyToStart,
536            GenerationFencePhase::Closed => Self::Closed,
537        }
538    }
539}
540
541/// Dashboard projection for generation fencing decisions tied to restart commands.
542#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
543#[serde(rename_all = "snake_case")]
544pub enum DashboardGenerationFenceDecision {
545    /// Immediately spawned due to lacking an active attempt.
546    StartedImmediately,
547    /// Queued until an active attempt observes stop semantics.
548    QueuedAfterStop,
549    /// Duplicate restart merged onto the existing pending fence request.
550    AlreadyPending,
551    /// Supervisor shutdown prevents further restart progress.
552    BlockedByShutdown,
553    /// Structured rejection surfaced through dashboard conflicts.
554    Rejected,
555}
556
557impl From<GenerationFenceDecision> for DashboardGenerationFenceDecision {
558    /// Maps runtime restart fence decisions onto dashboard labels.
559    fn from(value: GenerationFenceDecision) -> Self {
560        match value {
561            GenerationFenceDecision::StartedImmediately => Self::StartedImmediately,
562            GenerationFenceDecision::QueuedAfterStop => Self::QueuedAfterStop,
563            GenerationFenceDecision::AlreadyPending => Self::AlreadyPending,
564            GenerationFenceDecision::BlockedByShutdown => Self::BlockedByShutdown,
565            GenerationFenceDecision::Rejected => Self::Rejected,
566        }
567    }
568}
569
570/// Dashboard projection covering optional pending restart fingerprints.
571#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
572pub struct DashboardPendingRestartSummary {
573    /// Old generation locked during the queued restart handshake.
574    pub old_generation: u64,
575    /// Old attempt locked during the queued restart handshake.
576    pub old_attempt: u64,
577    /// Target generation that should start after cleanup completes.
578    pub target_generation: u64,
579}
580
581impl From<&PendingRestartSummary> for DashboardPendingRestartSummary {
582    /// Converts numeric runtime identifiers into wire-friendly dashboard fields.
583    fn from(value: &PendingRestartSummary) -> Self {
584        Self {
585            old_generation: value.old_generation.value,
586            old_attempt: value.old_attempt.value,
587            target_generation: value.target_generation.value,
588        }
589    }
590}
591
592/// Dashboard projection of structured generation fencing command outcomes.
593#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
594pub struct DashboardGenerationFenceOutcome {
595    /// High-level fencing decision surfaced to operators.
596    pub decision: DashboardGenerationFenceDecision,
597    /// Serialized old generation counterpart when meaningful.
598    pub old_generation: Option<u64>,
599    /// Serialized old attempt counterpart when meaningful.
600    pub old_attempt: Option<u64>,
601    /// Serialized queued target generation counterpart when meaningful.
602    pub target_generation: Option<u64>,
603    /// Mirrors runtime cancel delivery semantics during restart flows.
604    pub cancel_delivered: bool,
605    /// Mirrors runtime abort delivery semantics during restart flows.
606    pub abort_requested: bool,
607    /// Optional structured failure propagated when decisions reject restart work.
608    pub conflict: Option<DashboardChildControlFailure>,
609}
610
611impl From<&GenerationFenceOutcome> for DashboardGenerationFenceOutcome {
612    /// Converts nested runtime payloads into serialized dashboard equivalents.
613    fn from(outcome: &GenerationFenceOutcome) -> Self {
614        Self {
615            decision: outcome.decision.into(),
616            old_generation: outcome.old_generation.map(|generation| generation.value),
617            old_attempt: outcome.old_attempt.map(|attempt| attempt.value),
618            target_generation: outcome.target_generation.map(|generation| generation.value),
619            cancel_delivered: outcome.cancel_delivered,
620            abort_requested: outcome.abort_requested,
621            conflict: outcome
622                .conflict
623                .as_ref()
624                .map(DashboardChildControlFailure::from_failure),
625        }
626    }
627}
628
629/// Dashboard projection of one child runtime record.
630#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
631pub struct DashboardChildRuntimeRecord {
632    /// Stable child identifier.
633    pub child_id: String,
634    /// Child path in the supervisor tree.
635    pub child_path: String,
636    /// Current active generation.
637    pub generation: Option<u64>,
638    /// Current active attempt.
639    pub attempt: Option<u64>,
640    /// Current attempt status.
641    pub status: Option<DashboardChildAttemptStatus>,
642    /// Current control operation.
643    pub operation: DashboardChildControlOperation,
644    /// Managed child state derived from operation.
645    pub managed_child_state: DashboardManagedChildState,
646    /// Current liveness state.
647    pub liveness: DashboardChildLivenessState,
648    /// Current restart limit state.
649    pub restart_limit: DashboardRestartLimitState,
650    /// Current stop progress.
651    pub stop_state: DashboardChildStopState,
652    /// Most recent control failure.
653    pub failure: Option<DashboardChildControlFailure>,
654    /// Generation fencing phase mirroring runtime projection.
655    pub generation_fence_phase: DashboardGenerationFencePhase,
656    /// Pending restart summary when the runtime still waits on the old attempt.
657    pub pending_restart: Option<DashboardPendingRestartSummary>,
658}
659
660impl DashboardChildRuntimeRecord {
661    /// Converts a runtime record into a dashboard runtime record.
662    ///
663    /// # Arguments
664    ///
665    /// - `record`: Runtime child record returned by current state.
666    ///
667    /// # Returns
668    ///
669    /// Returns a dashboard runtime record.
670    pub fn from_runtime_record(record: &ChildRuntimeRecord) -> Self {
671        Self {
672            child_id: record.child_id.to_string(),
673            child_path: record.path.to_string(),
674            generation: record.generation.map(|generation| generation.value),
675            attempt: record.attempt.map(|attempt| attempt.value),
676            status: record.status.map(DashboardChildAttemptStatus::from),
677            operation: DashboardChildControlOperation::from(record.operation),
678            managed_child_state: DashboardManagedChildState::from(record.operation),
679            liveness: DashboardChildLivenessState::from_liveness(&record.liveness),
680            restart_limit: DashboardRestartLimitState::from_restart_limit(&record.restart_limit),
681            stop_state: DashboardChildStopState::from(record.stop_state),
682            failure: record
683                .failure
684                .as_ref()
685                .map(DashboardChildControlFailure::from_failure),
686            generation_fence_phase: record.generation_fence_phase.into(),
687            pending_restart: record
688                .pending_restart
689                .as_ref()
690                .map(DashboardPendingRestartSummary::from),
691        }
692    }
693}
694
695/// Dashboard projection of a runtime current state result.
696#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
697pub struct DashboardCurrentState {
698    /// Number of children known to the control loop.
699    pub child_count: usize,
700    /// Whether tree shutdown has completed.
701    pub shutdown_completed: bool,
702    /// Runtime state records for declared children.
703    pub child_runtime_records: Vec<DashboardChildRuntimeRecord>,
704}
705
706impl DashboardCurrentState {
707    /// Converts a runtime current state into a dashboard current state.
708    ///
709    /// # Arguments
710    ///
711    /// - `state`: Runtime current state.
712    ///
713    /// # Returns
714    ///
715    /// Returns a dashboard current state.
716    pub fn from_current_state(state: &CurrentState) -> Self {
717        Self {
718            child_count: state.child_count,
719            shutdown_completed: state.shutdown_completed,
720            child_runtime_records: state
721                .child_runtime_records
722                .iter()
723                .map(DashboardChildRuntimeRecord::from_runtime_record)
724                .collect(),
725        }
726    }
727}
728
729/// Dashboard projection of a child control command result.
730#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
731pub struct DashboardChildControlResult {
732    /// Stable child identifier.
733    pub child_id: String,
734    /// Active attempt targeted by the command.
735    pub attempt: Option<u64>,
736    /// Active generation targeted by the command.
737    pub generation: Option<u64>,
738    /// Control operation before command handling.
739    pub operation_before: DashboardChildControlOperation,
740    /// Control operation after command handling.
741    pub operation_after: DashboardChildControlOperation,
742    /// Managed child state before command handling.
743    pub managed_child_state_before: DashboardManagedChildState,
744    /// Managed child state after command handling.
745    pub managed_child_state_after: DashboardManagedChildState,
746    /// Current attempt status.
747    pub status: Option<DashboardChildAttemptStatus>,
748    /// Whether this command delivered cancellation.
749    pub cancel_delivered: bool,
750    /// Stop progress after command handling.
751    pub stop_state: DashboardChildStopState,
752    /// Current restart limit state.
753    pub restart_limit: DashboardRestartLimitState,
754    /// Current liveness state.
755    pub liveness: DashboardChildLivenessState,
756    /// Whether this command reused existing state idempotently.
757    pub idempotent: bool,
758    /// Current failure reason.
759    pub failure: Option<DashboardChildControlFailure>,
760    /// Optional serialized generation fence outcome for restart-style commands.
761    pub generation_fence: Option<DashboardGenerationFenceOutcome>,
762}
763
764impl DashboardChildControlResult {
765    /// Converts a runtime child control result into a dashboard control result.
766    ///
767    /// # Arguments
768    ///
769    /// - `outcome`: Runtime child control result.
770    ///
771    /// # Returns
772    ///
773    /// Returns a dashboard child control result.
774    pub fn from_child_control_result(outcome: &RuntimeChildControlResult) -> Self {
775        Self {
776            child_id: outcome.child_id.to_string(),
777            attempt: outcome.attempt.map(|attempt| attempt.value),
778            generation: outcome.generation.map(|generation| generation.value),
779            operation_before: DashboardChildControlOperation::from(outcome.operation_before),
780            operation_after: DashboardChildControlOperation::from(outcome.operation_after),
781            managed_child_state_before: DashboardManagedChildState::from(outcome.operation_before),
782            managed_child_state_after: DashboardManagedChildState::from(outcome.operation_after),
783            status: outcome.status.map(DashboardChildAttemptStatus::from),
784            cancel_delivered: outcome.cancel_delivered,
785            stop_state: DashboardChildStopState::from(outcome.stop_state),
786            restart_limit: DashboardRestartLimitState::from_restart_limit(&outcome.restart_limit),
787            liveness: DashboardChildLivenessState::from_liveness(&outcome.liveness),
788            idempotent: outcome.idempotent,
789            failure: outcome
790                .failure
791                .as_ref()
792                .map(DashboardChildControlFailure::from_failure),
793            generation_fence: outcome
794                .generation_fence
795                .as_ref()
796                .map(DashboardGenerationFenceOutcome::from),
797        }
798    }
799}
800
801/// Command result shape returned through dashboard state deltas.
802#[allow(clippy::large_enum_variant)]
803#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
804#[serde(tag = "type", rename_all = "snake_case")]
805pub enum DashboardCommandResult {
806    /// Child was accepted by the control loop.
807    ChildAdded {
808        /// Child manifest stored by the runtime.
809        child_manifest: String,
810    },
811    /// Child control result after a command.
812    ChildControl {
813        /// Dashboard child control result.
814        outcome: DashboardChildControlResult,
815    },
816    /// Current state query result.
817    CurrentState {
818        /// Runtime current state.
819        state: DashboardCurrentState,
820    },
821    /// Shutdown command result.
822    Shutdown {
823        /// Shutdown result serialized by the shutdown module.
824        result: Value,
825    },
826}
827
828impl DashboardCommandResult {
829    /// Converts a runtime command result into a dashboard command result.
830    ///
831    /// # Arguments
832    ///
833    /// - `result`: Runtime command result.
834    ///
835    /// # Returns
836    ///
837    /// Returns a dashboard command result.
838    pub fn from_command_result(result: &CommandResult) -> Result<Self, serde_json::Error> {
839        match result {
840            CommandResult::ChildAdded { child_manifest } => Ok(Self::ChildAdded {
841                child_manifest: child_manifest.clone(),
842            }),
843            CommandResult::ChildControl { outcome } => Ok(Self::ChildControl {
844                outcome: DashboardChildControlResult::from_child_control_result(outcome),
845            }),
846            CommandResult::CurrentState { state } => Ok(Self::CurrentState {
847                state: DashboardCurrentState::from_current_state(state),
848            }),
849            CommandResult::Shutdown { result } => Ok(Self::Shutdown {
850                result: serde_json::to_value(result)?,
851            }),
852        }
853    }
854}
855
856/// Serializes a runtime command result using the dashboard return model.
857///
858/// # Arguments
859///
860/// - `result`: Runtime command result.
861///
862/// # Returns
863///
864/// Returns a JSON value with the dashboard command result shape.
865pub fn dashboard_command_result_value(result: &CommandResult) -> Result<Value, serde_json::Error> {
866    serde_json::to_value(DashboardCommandResult::from_command_result(result)?)
867}
868
869/// Derives the managed child state that corresponds to an operation.
870///
871/// # Arguments
872///
873/// - `operation`: Runtime control operation.
874///
875/// Converts a child runtime record into the existing dashboard runtime row.
876///
877/// # Arguments
878///
879/// - `record`: Runtime child record returned by current state.
880/// - `shutdown_completed`: Whether the supervisor shutdown has completed.
881///
882/// # Returns
883///
884/// Returns a dashboard runtime row that preserves the existing UI list shape.
885pub fn runtime_state_from_child_runtime_record(
886    record: &ChildRuntimeRecord,
887    shutdown_completed: bool,
888) -> RuntimeState {
889    let managed_child_state = DashboardManagedChildState::from(record.operation);
890    let readiness = DashboardReadinessState::from(record.liveness.readiness);
891    RuntimeState {
892        child_path: record.path.to_string(),
893        lifecycle_state: managed_child_state.as_label().to_owned(),
894        health: if record.liveness.heartbeat_stale {
895            "stale".to_owned()
896        } else {
897            "healthy".to_owned()
898        },
899        readiness: readiness.as_label().to_owned(),
900        generation: record
901            .generation
902            .map(|generation| generation.value)
903            .unwrap_or(0),
904        child_start_count: record.attempt.map(|attempt| attempt.value).unwrap_or(0),
905        restart_count: u64::from(record.restart_limit.used),
906        last_failure: record
907            .failure
908            .as_ref()
909            .map(|failure| failure.reason.clone()),
910        last_policy_decision: Some(format!(
911            "restart_limit_remaining={}",
912            record.restart_limit.remaining
913        )),
914        shutdown_state: if shutdown_completed {
915            "completed".to_owned()
916        } else {
917            "running".to_owned()
918        },
919    }
920}
921
922/// Event record streamed from a target process.
923#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
924pub struct EventRecord {
925    /// Target process identifier.
926    pub target_id: String,
927    /// Target-local monotonic sequence.
928    pub sequence: u64,
929    /// Correlation identifier.
930    pub correlation_id: String,
931    /// Event type label.
932    pub event_type: String,
933    /// Severity label.
934    pub severity: String,
935    /// Target path.
936    pub target_path: String,
937    /// Optional child identifier.
938    pub child_id: Option<String>,
939    /// Occurred time as Unix nanoseconds.
940    pub occurred_at_unix_nanos: u128,
941    /// Configuration version.
942    pub config_version: String,
943    /// Event payload.
944    pub payload: Value,
945}
946
947/// Log record streamed from a target process.
948#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
949pub struct LogRecord {
950    /// Target process identifier.
951    pub target_id: String,
952    /// Optional target-local sequence.
953    pub sequence: Option<u64>,
954    /// Optional correlation identifier.
955    pub correlation_id: Option<String>,
956    /// Severity label.
957    pub severity: String,
958    /// Log message.
959    pub message: String,
960    /// Structured log fields.
961    pub fields: BTreeMap<String, String>,
962    /// Occurred time as Unix nanoseconds.
963    pub occurred_at_unix_nanos: u128,
964}
965
966/// Supported control command names.
967#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
968#[serde(rename_all = "snake_case")]
969pub enum ControlCommandKind {
970    /// Restart a child.
971    RestartChild,
972    /// Pause a child.
973    PauseChild,
974    /// Resume a child.
975    ResumeChild,
976    /// Quarantine a child.
977    QuarantineChild,
978    /// Remove a child.
979    RemoveChild,
980    /// Add a child.
981    AddChild,
982    /// Shut down the whole tree.
983    ShutdownTree,
984}
985
986/// Target selector for a control command.
987#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
988pub struct ControlCommandTarget {
989    /// Optional child path for child-scoped commands.
990    pub child_path: Option<String>,
991    /// Optional child manifest for add-child commands.
992    pub child_manifest: Option<String>,
993}
994
995/// Control command request forwarded by relay to target IPC.
996#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
997pub struct ControlCommandRequest {
998    /// Command identifier.
999    pub command_id: String,
1000    /// Target process identifier.
1001    pub target_id: String,
1002    /// Command kind.
1003    pub command: ControlCommandKind,
1004    /// Command target.
1005    pub target: ControlCommandTarget,
1006    /// Non-empty reason.
1007    pub reason: String,
1008    /// Authenticated requester derived by relay.
1009    pub requested_by: String,
1010    /// Whether dangerous command confirmation is present.
1011    pub confirmed: bool,
1012    /// Request time as Unix nanoseconds.
1013    pub requested_at_unix_nanos: u128,
1014}
1015
1016/// Control command result returned by target IPC.
1017#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
1018pub struct ControlCommandResult {
1019    /// Command identifier.
1020    pub command_id: String,
1021    /// Target process identifier.
1022    pub target_id: String,
1023    /// Whether target accepted the command.
1024    pub accepted: bool,
1025    /// Status label.
1026    pub status: String,
1027    /// Optional structured error.
1028    pub error: Option<crate::dashboard::error::DashboardError>,
1029    /// Optional state delta.
1030    pub state_delta: Option<Value>,
1031    /// Completion time as Unix nanoseconds.
1032    pub completed_at_unix_nanos: Option<u128>,
1033}
1034
1035/// Audit event emitted for accepted, rejected, and completed commands.
1036#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
1037pub struct AuditEvent {
1038    /// Audit event identifier.
1039    pub audit_id: String,
1040    /// Remote identity summary.
1041    pub identity: String,
1042    /// Target process identifier.
1043    pub target_id: String,
1044    /// Command identifier.
1045    pub command_id: String,
1046    /// Command kind.
1047    pub command: ControlCommandKind,
1048    /// Command target.
1049    pub target: ControlCommandTarget,
1050    /// Operator-provided reason.
1051    pub reason: String,
1052    /// Result summary.
1053    pub result: String,
1054    /// Occurred time as Unix nanoseconds.
1055    pub occurred_at_unix_nanos: u128,
1056}