Skip to main content

demo/
scenario.rs

1//! Owns the dashboard data scenario served by the demo example.
2
3// Import dashboard error values used by validation.
4use rust_supervisor::dashboard::error::DashboardError;
5// Import dashboard model contracts served over IPC.
6use rust_supervisor::dashboard::model::{
7    // Continue the demo expression.
8    ControlCommandKind,
9    // Import command request shape.
10    ControlCommandRequest,
11    // Import command result shape.
12    ControlCommandResult,
13    // Import topology criticality values.
14    DashboardCriticality,
15    // Continue the demo expression.
16    DashboardState,
17    // Import event record shape.
18    EventRecord,
19    // Import log record shape.
20    LogRecord,
21    // Import registration state values.
22    RegistrationState,
23    // Import runtime row shape.
24    RuntimeState,
25    // Import topology edge shape.
26    SupervisorEdge,
27    // Continue the demo expression.
28    SupervisorEdgeKind,
29    // Import topology node shape.
30    SupervisorNode,
31    // Import topology node kind values.
32    SupervisorNodeKind,
33    // Import topology graph shape.
34    SupervisorTopology,
35    // Continue the demo expression.
36    TargetConnectionState,
37    // Import target identity shape.
38    TargetProcessIdentity,
39    // Continue the demo expression.
40};
41// Import JSON construction for command deltas and event payloads.
42use serde_json::json;
43// Import ordered maps for stable serialized payloads.
44use std::collections::BTreeMap;
45// Import mutex storage for mutable demo child states.
46use std::sync::Mutex;
47// Import atomic state generation counters.
48use std::sync::atomic::{AtomicU64, Ordering};
49
50// Define the root path shown in the demo topology.
51const ROOT_PATH: &str = "/root";
52// Define the demo configuration version.
53const CONFIG_VERSION: &str = "demo-ui-scenario-v1";
54
55/// Mutable dashboard scenario served by the demo IPC target.
56pub(crate) struct DemoScenario {
57    /// Stable target process identifier.
58    target_id: String,
59    /// Human-readable target name.
60    display_name: String,
61    /// State generation counter.
62    state_generation: AtomicU64,
63    /// Mutable child rows.
64    children: Mutex<Vec<DemoChild>>,
65    /// Command event sequence counter.
66    activity_sequence: AtomicU64,
67    // Continue the demo expression.
68}
69
70// Derive clone and debug helpers for static child declarations.
71#[derive(Clone, Debug)]
72/// One child row in the dashboard demo scenario.
73struct DemoChild {
74    /// Stable child identifier.
75    id: String,
76    /// Human-readable child name.
77    name: String,
78    /// Lifecycle state label.
79    lifecycle: String,
80    /// Health state label.
81    health: String,
82    /// Readiness state label.
83    readiness: String,
84    /// Restart count shown by the UI.
85    restart_count: u64,
86    /// Whether the child remains visible.
87    present: bool,
88    // Continue the demo expression.
89}
90
91// Derive clone and debug helpers for command transition payloads.
92#[derive(Clone, Debug)]
93/// Lifecycle transition caused by one demo command.
94struct CommandTransition {
95    /// Lifecycle state before command application.
96    previous_lifecycle_state: String,
97    /// Lifecycle state after command application.
98    lifecycle_state: String,
99    // Continue the demo expression.
100}
101
102// Continue the demo expression.
103impl DemoScenario {
104    /// Creates the default UI scenario.
105    ///
106    /// # Arguments
107    ///
108    /// - `target_id`: Target process identifier.
109    /// - `display_name`: Human-readable target name.
110    ///
111    /// # Returns
112    ///
113    /// Returns a mutable scenario with the standard demo children.
114    pub(crate) fn new(target_id: String, display_name: String) -> Self {
115        // Store the target identity and initial rows.
116        Self {
117            // Keep the target identifier stable.
118            target_id,
119            // Keep the display name stable.
120            display_name,
121            // Start state generations at one.
122            state_generation: AtomicU64::new(1),
123            // Seed child rows for the UI topology.
124            children: Mutex::new(seed_children()),
125            // Start command activity sequences away from seed records.
126            activity_sequence: AtomicU64::new(1),
127            // End scenario initialization.
128        }
129        // End constructor.
130    }
131
132    /// Returns the target process identifier.
133    ///
134    /// # Arguments
135    ///
136    /// This function has no arguments.
137    ///
138    /// # Returns
139    ///
140    /// Returns the target identifier as a string slice.
141    pub(crate) fn target_id(&self) -> &str {
142        // Return the stored target identifier.
143        &self.target_id
144        // End target identifier access.
145    }
146
147    /// Builds the current dashboard state.
148    ///
149    /// # Arguments
150    ///
151    /// This function has no arguments.
152    ///
153    /// # Returns
154    ///
155    /// Returns a dashboard state payload for UI rendering.
156    pub(crate) fn state(&self) -> DashboardState {
157        // Lock child rows for a consistent state payload.
158        let children = self.children.lock().expect("demo scenario mutex");
159        // Collect visible child rows.
160        let visible = visible_children(&children);
161        // Build the full dashboard state.
162        DashboardState {
163            // Include target identity and connection state.
164            target: self.target_identity(),
165            // Include the topology graph.
166            topology: topology(&visible),
167            // Include runtime rows.
168            runtime_state: runtime_rows(&visible),
169            // Include recent event rows.
170            recent_events: event_records(&self.target_id, &visible),
171            // Include recent log rows.
172            recent_logs: log_records(&self.target_id, &visible),
173            // Show that events can be dropped by bounded buffers.
174            dropped_event_count: 2,
175            // Show that logs can be dropped by bounded buffers.
176            dropped_log_count: 1,
177            // Include a stable demo config version.
178            config_version: CONFIG_VERSION.to_owned(),
179            // Include the generation timestamp.
180            generated_at_unix_nanos: unix_nanos_now(),
181            // Increment state generation on every state build.
182            state_generation: self.state_generation.fetch_add(1, Ordering::Relaxed),
183            // End dashboard state construction.
184        }
185        // End state construction.
186    }
187
188    /// Applies one control command and returns its result.
189    ///
190    /// # Arguments
191    ///
192    /// - `command`: Decoded command request.
193    ///
194    /// # Returns
195    ///
196    /// Returns a structured command result or validation error.
197    pub(crate) fn command_result(
198        // Continue the demo expression.
199        &self,
200        // Continue the demo expression.
201        command: ControlCommandRequest,
202        // Continue the demo expression.
203    ) -> Result<ControlCommandResult, DashboardError> {
204        // Validate command input before state mutation.
205        self.validate_command(&command)?;
206        // Lock child rows while applying the command.
207        let mut children = self.children.lock().expect("demo scenario mutex");
208        // Apply the requested command.
209        let transition = apply_command(&mut children, &command)?;
210        // Build a UI-consumable state delta after mutation.
211        let delta = command_state_delta(
212            // Continue the demo expression.
213            &self.target_id,
214            // Continue the demo expression.
215            &children,
216            // Continue the demo expression.
217            &command,
218            // Continue the demo expression.
219            &transition,
220            // Continue the demo expression.
221            self.activity_sequence.fetch_add(1, Ordering::Relaxed),
222            // Continue the demo expression.
223            self.state_generation.fetch_add(1, Ordering::Relaxed),
224            // Continue the demo expression.
225        );
226        // Return a successful command result.
227        Ok(ControlCommandResult {
228            // Preserve the original command identifier.
229            command_id: command.command_id,
230            // Preserve the target identifier.
231            target_id: command.target_id,
232            // Mark the command as accepted.
233            accepted: true,
234            // Mark the command as completed.
235            status: "completed".to_owned(),
236            // No structured error is present.
237            error: None,
238            // Include the state delta summary.
239            state_delta: Some(delta),
240            // Record command completion time.
241            completed_at_unix_nanos: Some(unix_nanos_now()),
242            // End command result construction.
243        })
244        // End command result.
245    }
246
247    /// Builds the target identity portion of dashboard state.
248    ///
249    /// # Arguments
250    ///
251    /// This function has no arguments.
252    ///
253    /// # Returns
254    ///
255    /// Returns target identity metadata for the state payload.
256    fn target_identity(&self) -> TargetProcessIdentity {
257        // Build the connected target identity.
258        TargetProcessIdentity {
259            // Include target identifier.
260            target_id: self.target_id.clone(),
261            // Include display name.
262            display_name: self.display_name.clone(),
263            // Mark registration as active.
264            registration_state: RegistrationState::Active,
265            // Mark the target IPC as connected.
266            connection_state: TargetConnectionState::Connected,
267            // End target identity construction.
268        }
269        // End target identity construction.
270    }
271
272    /// Validates one command request.
273    ///
274    /// # Arguments
275    ///
276    /// - `command`: Command request supplied by relay.
277    ///
278    /// # Returns
279    ///
280    /// Returns success when the command can be applied.
281    fn validate_command(&self, command: &ControlCommandRequest) -> Result<(), DashboardError> {
282        // Reject commands for another target.
283        if command.target_id != self.target_id {
284            // Return target mismatch validation.
285            return Err(validation(
286                // Continue the demo expression.
287                &self.target_id,
288                // Continue the demo expression.
289                "command target_id must match demo target",
290                // Continue the demo expression.
291            ));
292            // End target mismatch branch.
293        }
294        // Reject missing command identifiers.
295        if command.command_id.trim().is_empty() {
296            // Return command identifier validation.
297            return Err(validation(&self.target_id, "command_id must not be empty"));
298            // End command identifier branch.
299        }
300        // Reject missing reasons.
301        if command.reason.trim().is_empty() {
302            // Return reason validation.
303            return Err(validation(
304                // Continue the demo expression.
305                &self.target_id,
306                // Continue the demo expression.
307                "command reason must not be empty",
308                // Continue the demo expression.
309            ));
310            // End reason branch.
311        }
312        // Reject missing requester identity.
313        if command.requested_by.trim().is_empty() {
314            // Return requester validation.
315            return Err(validation(
316                // Continue the demo expression.
317                &self.target_id,
318                // Continue the demo expression.
319                "requested_by must not be empty",
320                // Continue the demo expression.
321            ));
322            // End requester branch.
323        }
324        // Validate dangerous command confirmation.
325        if is_dangerous(command.command) && !command.confirmed {
326            // Return confirmation validation.
327            return Err(validation(
328                // Continue the demo expression.
329                &self.target_id,
330                // Continue the demo expression.
331                "dangerous command requires confirmation",
332                // Continue the demo expression.
333            ));
334            // End confirmation branch.
335        }
336        // Validate add-child manifests.
337        if command.command == ControlCommandKind::AddChild && missing_child_manifest(command) {
338            // Return child manifest validation.
339            return Err(validation(
340                // Continue the demo expression.
341                &self.target_id,
342                // Continue the demo expression.
343                "add_child requires child_manifest",
344                // Continue the demo expression.
345            ));
346            // End child manifest branch.
347        }
348        // Finish command validation successfully.
349        Ok(())
350        // End command validation.
351    }
352    // Continue the demo expression.
353}
354
355/// Builds the seed child rows for the demo.
356///
357/// # Arguments
358///
359/// This function has no arguments.
360///
361/// # Returns
362///
363/// Returns the static child rows used by the UI.
364fn seed_children() -> Vec<DemoChild> {
365    // Return every standard demo child.
366    vec![
367        // Include a failed child.
368        child(
369            // Continue the demo expression.
370            "duplicate_guard",
371            // Continue the demo expression.
372            "duplicate guard",
373            // Continue the demo expression.
374            "failed",
375            // Continue the demo expression.
376            "unhealthy",
377            // Continue the demo expression.
378            "not_ready",
379            // Continue the demo expression.
380            2,
381            // Continue the demo expression.
382        ),
383        // Include a restarting child.
384        child(
385            // Continue the demo expression.
386            "retry_scheduler",
387            // Continue the demo expression.
388            "retry scheduler",
389            // Continue the demo expression.
390            "restarting",
391            // Continue the demo expression.
392            "stale",
393            // Continue the demo expression.
394            "not_ready",
395            // Continue the demo expression.
396            3,
397            // Continue the demo expression.
398        ),
399        // Include a paused child.
400        child(
401            // Continue the demo expression.
402            "invoice_writer",
403            // Continue the demo expression.
404            "invoice writer",
405            // Continue the demo expression.
406            "paused",
407            // Continue the demo expression.
408            "healthy",
409            // Continue the demo expression.
410            "ready",
411            // Continue the demo expression.
412            0,
413            // Continue the demo expression.
414        ),
415        // Include a quarantined child.
416        child(
417            // Continue the demo expression.
418            "index_stream",
419            // Continue the demo expression.
420            "index stream",
421            // Continue the demo expression.
422            "quarantined",
423            // Continue the demo expression.
424            "unhealthy",
425            // Continue the demo expression.
426            "not_ready",
427            // Continue the demo expression.
428            5,
429            // Continue the demo expression.
430        ),
431        // Include a healthy running child.
432        child(
433            // Continue the demo expression.
434            "healthy_worker",
435            // Continue the demo expression.
436            "healthy worker",
437            // Continue the demo expression.
438            "running",
439            // Continue the demo expression.
440            "healthy",
441            // Continue the demo expression.
442            "ready",
443            // Continue the demo expression.
444            0,
445            // Continue the demo expression.
446        ),
447        // End child list.
448    ]
449    // End seed child construction.
450}
451
452/// Creates one demo child row.
453///
454/// # Arguments
455///
456/// - `id`: Stable child identifier.
457/// - `name`: Human-readable child name.
458/// - `lifecycle`: Lifecycle state label.
459/// - `health`: Health state label.
460/// - `readiness`: Readiness state label.
461/// - `restart_count`: Restart count.
462///
463/// # Returns
464///
465/// Returns a child row.
466fn child(
467    // Continue the demo expression.
468    id: &str,
469    // Continue the demo expression.
470    name: &str,
471    // Continue the demo expression.
472    lifecycle: &str,
473    // Continue the demo expression.
474    health: &str,
475    // Continue the demo expression.
476    readiness: &str,
477    // Continue the demo expression.
478    restart_count: u64,
479    // Continue the demo expression.
480) -> DemoChild {
481    // Build one child row.
482    DemoChild {
483        // Store the child identifier.
484        id: id.to_owned(),
485        // Store the child display name.
486        name: name.to_owned(),
487        // Store the lifecycle label.
488        lifecycle: lifecycle.to_owned(),
489        // Store the health label.
490        health: health.to_owned(),
491        // Store the readiness label.
492        readiness: readiness.to_owned(),
493        // Store the restart count.
494        restart_count,
495        // Mark the row visible.
496        present: true,
497        // End child row construction.
498    }
499    // End child construction.
500}
501
502/// Filters visible child rows.
503///
504/// # Arguments
505///
506/// - `children`: All child rows.
507///
508/// # Returns
509///
510/// Returns visible child rows.
511fn visible_children(children: &[DemoChild]) -> Vec<DemoChild> {
512    // Keep only rows that remain present.
513    children
514        // Iterate through all rows.
515        .iter()
516        // Keep visible rows.
517        .filter(|child| child.present)
518        // Clone visible rows for state construction.
519        .cloned()
520        // Collect visible rows.
521        .collect()
522    // End visible child filtering.
523}
524
525/// Builds topology for visible demo children.
526///
527/// # Arguments
528///
529/// - `children`: Visible child rows.
530///
531/// # Returns
532///
533/// Returns a topology graph.
534fn topology(children: &[DemoChild]) -> SupervisorTopology {
535    // Build the root node.
536    let root = root_node();
537    // Start the node list with the root.
538    let mut nodes = vec![root.clone()];
539    // Start the edge list.
540    let mut edges = Vec::new();
541    // Start the declaration order with the root.
542    let mut declaration_order = vec![ROOT_PATH.to_owned()];
543    // Add one node and edge per child.
544    for (index, child) in children.iter().enumerate() {
545        // Build the child path.
546        let path = child_path(&child.id);
547        // Record declaration order.
548        declaration_order.push(path.clone());
549        // Add the child node.
550        nodes.push(child_node(child, &path));
551        // Add the parent-child edge.
552        edges.push(parent_edge(index, &path));
553        // End child topology row.
554    }
555    // Return the topology.
556    SupervisorTopology {
557        // Include the root node.
558        root,
559        // Include all visible nodes.
560        nodes,
561        // Include all visible edges.
562        edges,
563        // Include declaration order.
564        declaration_order,
565        // End topology construction.
566    }
567    // End topology construction.
568}
569
570/// Builds the root topology node.
571///
572/// # Arguments
573///
574/// This function has no arguments.
575///
576/// # Returns
577///
578/// Returns the root node.
579fn root_node() -> SupervisorNode {
580    // Build the root node.
581    SupervisorNode {
582        // Use the root path as node identifier.
583        node_id: ROOT_PATH.to_owned(),
584        // Root has no child identifier.
585        child_id: None,
586        // Use the root path.
587        path: ROOT_PATH.to_owned(),
588        // Use a readable root name.
589        name: "root supervisor".to_owned(),
590        // Mark the node as root.
591        kind: SupervisorNodeKind::RootSupervisor,
592        // Root has no tags.
593        tags: Vec::new(),
594        // Root is critical.
595        criticality: DashboardCriticality::Critical,
596        // Summarize the root state.
597        state_summary: "root".to_owned(),
598        // Root has no diagnostics.
599        diagnostics: BTreeMap::new(),
600        // End root node construction.
601    }
602    // End root node construction.
603}
604
605/// Builds one child topology node.
606///
607/// # Arguments
608///
609/// - `child`: Child row.
610/// - `path`: Child path.
611///
612/// # Returns
613///
614/// Returns a child node.
615fn child_node(child: &DemoChild, path: &str) -> SupervisorNode {
616    // Build node diagnostics.
617    let diagnostics = diagnostics_for(child);
618    // Build the child node.
619    SupervisorNode {
620        // Use the path as the node identifier.
621        node_id: path.to_owned(),
622        // Include the child identifier.
623        child_id: Some(child.id.clone()),
624        // Include the child path.
625        path: path.to_owned(),
626        // Include the child name.
627        name: child.name.clone(),
628        // Mark the node as a child task.
629        kind: SupervisorNodeKind::ChildTask,
630        // Tag demo rows for filtering.
631        tags: vec!["demo".to_owned(), "ui".to_owned()],
632        // Use standard criticality.
633        criticality: DashboardCriticality::Standard,
634        // Use the lifecycle as state summary.
635        state_summary: child.lifecycle.clone(),
636        // Include diagnostics.
637        diagnostics,
638        // End child node construction.
639    }
640    // End child node construction.
641}
642
643/// Builds diagnostics for one child.
644///
645/// # Arguments
646///
647/// - `child`: Child row.
648///
649/// # Returns
650///
651/// Returns diagnostic fields.
652fn diagnostics_for(child: &DemoChild) -> BTreeMap<String, String> {
653    // Start with an empty map.
654    let mut diagnostics = BTreeMap::new();
655    // Add diagnostics for failed rows.
656    if child.lifecycle == "failed" {
657        // Add failure summary.
658        diagnostics.insert(
659            // Store the diagnostic key.
660            "message".to_owned(),
661            // Store the diagnostic message.
662            "duplicate event window exceeded".to_owned(),
663            // End diagnostic insertion.
664        );
665        // End failed diagnostic branch.
666    }
667    // Return diagnostics.
668    diagnostics
669    // End diagnostics construction.
670}
671
672/// Builds one parent-child edge.
673///
674/// # Arguments
675///
676/// - `index`: Child declaration index.
677/// - `path`: Child path.
678///
679/// # Returns
680///
681/// Returns one topology edge.
682fn parent_edge(index: usize, path: &str) -> SupervisorEdge {
683    // Build the edge.
684    SupervisorEdge {
685        // Build a stable edge identifier.
686        edge_id: format!("parent:{ROOT_PATH}->{path}"),
687        // Root is the source.
688        source_path: ROOT_PATH.to_owned(),
689        // Child path is the target.
690        target_path: path.to_owned(),
691        // Mark the edge as parent-child.
692        kind: SupervisorEdgeKind::ParentChild,
693        // Preserve declaration order.
694        order: index,
695        // End edge construction.
696    }
697    // End edge construction.
698}
699
700/// Builds runtime rows for visible children.
701///
702/// # Arguments
703///
704/// - `children`: Visible child rows.
705///
706/// # Returns
707///
708/// Returns runtime state rows.
709fn runtime_rows(children: &[DemoChild]) -> Vec<RuntimeState> {
710    // Convert children to runtime rows.
711    children
712        // Iterate over visible children.
713        .iter()
714        // Build one runtime row per child.
715        .map(runtime_row)
716        // Collect runtime rows.
717        .collect()
718    // End runtime row construction.
719}
720
721/// Builds one runtime row.
722///
723/// # Arguments
724///
725/// - `child`: Child row.
726///
727/// # Returns
728///
729/// Returns one runtime row.
730fn runtime_row(child: &DemoChild) -> RuntimeState {
731    // Build the runtime row.
732    RuntimeState {
733        // Include the child path.
734        child_path: child_path(&child.id),
735        // Include the lifecycle state.
736        lifecycle_state: child.lifecycle.clone(),
737        // Include the health state.
738        health: child.health.clone(),
739        // Include the readiness state.
740        readiness: child.readiness.clone(),
741        // Include the generation number.
742        generation: child.restart_count,
743        // Include the attempt number.
744        attempt: child.restart_count.saturating_add(1),
745        // Include restart count.
746        restart_count: child.restart_count,
747        // Include last failure when present.
748        last_failure: last_failure(child),
749        // Include last policy decision when present.
750        last_policy_decision: last_policy_decision(child),
751        // Include shutdown state.
752        shutdown_state: "running".to_owned(),
753        // End runtime row construction.
754    }
755    // End runtime row construction.
756}
757
758/// Builds recent events for visible children.
759///
760/// # Arguments
761///
762/// - `target_id`: Target process identifier.
763/// - `children`: Visible child rows.
764///
765/// # Returns
766///
767/// Returns recent event records.
768fn event_records(target_id: &str, children: &[DemoChild]) -> Vec<EventRecord> {
769    // Convert children to event records.
770    children
771        // Iterate over visible children.
772        .iter()
773        // Keep child indexes as stable sequences.
774        .enumerate()
775        // Build one event per child.
776        .map(|(index, child)| event_record(target_id, index, child))
777        // Collect event records.
778        .collect()
779    // End event record construction.
780}
781
782/// Builds one event record.
783///
784/// # Arguments
785///
786/// - `target_id`: Target process identifier.
787/// - `index`: Child index.
788/// - `child`: Child row.
789///
790/// # Returns
791///
792/// Returns one event record.
793fn event_record(target_id: &str, index: usize, child: &DemoChild) -> EventRecord {
794    // Compute a deterministic sequence.
795    let sequence = 1001_u64.saturating_add(index as u64);
796    // Build the event record.
797    EventRecord {
798        // Include target identifier.
799        target_id: target_id.to_owned(),
800        // Include target-local sequence.
801        sequence,
802        // Include correlation identifier.
803        correlation_id: format!("demo-{sequence}"),
804        // Include event type.
805        event_type: event_type(child).to_owned(),
806        // Include severity.
807        severity: severity(child).to_owned(),
808        // Include target path.
809        target_path: child_path(&child.id),
810        // Include child identifier.
811        child_id: Some(child.id.clone()),
812        // Include occurrence time.
813        occurred_at_unix_nanos: unix_nanos_now().saturating_sub(sequence as u128),
814        // Include configuration version.
815        config_version: CONFIG_VERSION.to_owned(),
816        // Include structured payload.
817        payload: json!({
818            // Include the child path field.
819            "child_path": child_path(&child.id),
820            // Include the previous lifecycle state field.
821            "previous_lifecycle_state": "unknown",
822            // Include the lifecycle state field.
823            "lifecycle_state": child.lifecycle,
824            // End event payload object.
825        }),
826        // End event record construction.
827    }
828    // End event record construction.
829}
830
831/// Builds recent log rows for visible children.
832///
833/// # Arguments
834///
835/// - `target_id`: Target process identifier.
836/// - `children`: Visible child rows.
837///
838/// # Returns
839///
840/// Returns recent log records.
841fn log_records(target_id: &str, children: &[DemoChild]) -> Vec<LogRecord> {
842    // Convert children to log rows.
843    children
844        // Iterate over visible children.
845        .iter()
846        // Keep child indexes as stable sequences.
847        .enumerate()
848        // Build one log row per child.
849        .map(|(index, child)| log_record(target_id, index, child))
850        // Collect log rows.
851        .collect()
852    // End log record construction.
853}
854
855/// Builds one log record.
856///
857/// # Arguments
858///
859/// - `target_id`: Target process identifier.
860/// - `index`: Child index.
861/// - `child`: Child row.
862///
863/// # Returns
864///
865/// Returns one log record.
866fn log_record(target_id: &str, index: usize, child: &DemoChild) -> LogRecord {
867    // Compute a deterministic sequence.
868    let sequence = 2001_u64.saturating_add(index as u64);
869    // Build structured log fields.
870    let mut fields = BTreeMap::new();
871    // Insert child path.
872    fields.insert("child_path".to_owned(), child_path(&child.id));
873    // Insert previous lifecycle state.
874    fields.insert("previous_lifecycle_state".to_owned(), "unknown".to_owned());
875    // Insert lifecycle state.
876    fields.insert("lifecycle_state".to_owned(), child.lifecycle.clone());
877    // Build the log record.
878    LogRecord {
879        // Include target identifier.
880        target_id: target_id.to_owned(),
881        // Include log sequence.
882        sequence: Some(sequence),
883        // Include correlation identifier.
884        correlation_id: Some(format!("demo-{sequence}")),
885        // Include log severity.
886        severity: severity(child).to_owned(),
887        // Include log message.
888        message: format!(
889            // Continue the demo expression.
890            "{} transitioned from unknown to {}",
891            // Continue the demo expression.
892            child.name, child.lifecycle,
893            // End transition message expression.
894        ),
895        // Include structured fields.
896        fields,
897        // Include occurrence time.
898        occurred_at_unix_nanos: unix_nanos_now().saturating_sub(sequence as u128),
899        // End log record construction.
900    }
901    // End log record construction.
902}
903
904/// Applies a command to the demo rows.
905///
906/// # Arguments
907///
908/// - `children`: Mutable child rows.
909/// - `command`: Command request.
910///
911/// # Returns
912///
913/// Returns a JSON state delta.
914fn apply_command(
915    // Continue the demo expression.
916    children: &mut Vec<DemoChild>,
917    // Continue the demo expression.
918    command: &ControlCommandRequest,
919    // Continue the demo expression.
920) -> Result<CommandTransition, DashboardError> {
921    // Apply tree shutdown without a child path.
922    if command.command == ControlCommandKind::ShutdownTree {
923        // Read first visible lifecycle before shutdown.
924        let previous = children
925            // Continue the demo expression.
926            .iter()
927            // Continue the demo expression.
928            .find(|child| child.present)
929            // Continue the demo expression.
930            .map(|child| child.lifecycle.clone())
931            // Continue the demo expression.
932            .unwrap_or_else(|| "unknown".to_owned());
933        // Mark visible children as stopped.
934        children
935            // Continue the demo expression.
936            .iter_mut()
937            // Continue the demo expression.
938            .for_each(|child| child.lifecycle = "stopped".to_owned());
939        // Return success.
940        return Ok(CommandTransition {
941            // Preserve the previous lifecycle state.
942            previous_lifecycle_state: previous,
943            // Store the lifecycle after shutdown.
944            lifecycle_state: "stopped".to_owned(),
945            // End transition construction.
946        });
947        // End shutdown branch.
948    }
949    // Resolve the target child identifier.
950    let child_id = command_child_id(command)?;
951    // Apply add-child separately.
952    if command.command == ControlCommandKind::AddChild {
953        // Read the lifecycle before adding or restoring.
954        let previous = children
955            // Continue the demo expression.
956            .iter()
957            // Continue the demo expression.
958            .find(|row| row.id == child_id && row.present)
959            // Continue the demo expression.
960            .map(|row| row.lifecycle.clone())
961            // Continue the demo expression.
962            .unwrap_or_else(|| "absent".to_owned());
963        // Add or restore the requested child.
964        add_child(children, &child_id);
965        // Return success.
966        return Ok(CommandTransition {
967            // Preserve the previous lifecycle state.
968            previous_lifecycle_state: previous,
969            // Store the lifecycle after adding or restoring.
970            lifecycle_state: "running".to_owned(),
971            // End transition construction.
972        });
973        // End add-child branch.
974    }
975    // Find the target child row.
976    let child = children
977        // Iterate through mutable rows.
978        .iter_mut()
979        // Match the target child identifier.
980        .find(|row| row.id == child_id && row.present)
981        // Convert absence into validation.
982        .ok_or_else(|| {
983            // Continue the demo expression.
984            validation(
985                // Continue the demo expression.
986                &command.target_id,
987                // Continue the demo expression.
988                "child_path does not match a visible child",
989                // Continue the demo expression.
990            )
991            // Continue the demo expression.
992        })?;
993    // Preserve the lifecycle before child command application.
994    let previous = child.lifecycle.clone();
995    // Apply the child command.
996    match command.command {
997        // Restart moves the child into restarting state.
998        ControlCommandKind::RestartChild => {
999            // Continue the demo expression.
1000            set_child_state(child, "restarting", "stale", "not_ready")
1001            // Continue the demo expression.
1002        }
1003        // Pause moves the child into paused state.
1004        ControlCommandKind::PauseChild => set_child_state(child, "paused", "healthy", "ready"),
1005        // Resume moves the child into running state.
1006        ControlCommandKind::ResumeChild => set_child_state(child, "running", "healthy", "ready"),
1007        // Quarantine moves the child into quarantined state.
1008        ControlCommandKind::QuarantineChild => {
1009            // Continue the demo expression.
1010            set_child_state(child, "quarantined", "unhealthy", "not_ready")
1011            // Continue the demo expression.
1012        }
1013        // Remove hides the child from later state responses.
1014        ControlCommandKind::RemoveChild => child.present = false,
1015        // Other variants are handled above.
1016        ControlCommandKind::AddChild | ControlCommandKind::ShutdownTree => {} // End command match.
1017                                                                              // Continue the demo expression.
1018    }
1019    // Return success.
1020    Ok(CommandTransition {
1021        // Preserve the previous lifecycle state.
1022        previous_lifecycle_state: previous,
1023        // Store the lifecycle after command application.
1024        lifecycle_state: lifecycle_after(command.command).to_owned(),
1025        // End transition construction.
1026    })
1027    // End command application.
1028}
1029
1030/// Adds or restores a child row.
1031///
1032/// # Arguments
1033///
1034/// - `children`: Mutable child rows.
1035/// - `child_id`: Child identifier.
1036///
1037/// # Returns
1038///
1039/// This function has no return value.
1040fn add_child(children: &mut Vec<DemoChild>, child_id: &str) {
1041    // Restore an existing row when it already exists.
1042    if let Some(child) = children.iter_mut().find(|row| row.id == child_id) {
1043        // Mark the existing row present.
1044        child.present = true;
1045        // Mark the existing row running.
1046        set_child_state(child, "running", "healthy", "ready");
1047        // Finish existing row handling.
1048        return;
1049        // End existing row branch.
1050    }
1051    // Add a new row.
1052    children.push(child(child_id, child_id, "running", "healthy", "ready", 0));
1053    // End child addition.
1054}
1055
1056/// Sets child state labels.
1057///
1058/// # Arguments
1059///
1060/// - `child`: Mutable child row.
1061/// - `lifecycle`: Lifecycle state label.
1062/// - `health`: Health state label.
1063/// - `readiness`: Readiness state label.
1064///
1065/// # Returns
1066///
1067/// This function has no return value.
1068fn set_child_state(child: &mut DemoChild, lifecycle: &str, health: &str, readiness: &str) {
1069    // Update lifecycle.
1070    child.lifecycle = lifecycle.to_owned();
1071    // Update health.
1072    child.health = health.to_owned();
1073    // Update readiness.
1074    child.readiness = readiness.to_owned();
1075    // Increment restart count for restarting state.
1076    if lifecycle == "restarting" {
1077        // Increase restart count.
1078        child.restart_count = child.restart_count.saturating_add(1);
1079        // End restarting branch.
1080    }
1081    // End state update.
1082}
1083
1084/// Builds a command delta for the UI.
1085///
1086/// # Arguments
1087///
1088/// - `target_id`: Target identifier.
1089/// - `children`: Current child rows after mutation.
1090/// - `command`: Command request.
1091/// - `sequence`: Command activity sequence.
1092/// - `state_generation`: State generation value.
1093///
1094/// # Returns
1095///
1096/// Returns a JSON delta.
1097fn command_state_delta(
1098    // Accept target identifier.
1099    target_id: &str,
1100    // Accept current child rows.
1101    children: &[DemoChild],
1102    // Accept original command.
1103    command: &ControlCommandRequest,
1104    // Accept lifecycle transition.
1105    transition: &CommandTransition,
1106    // Accept activity sequence.
1107    sequence: u64,
1108    // Accept state generation.
1109    state_generation: u64,
1110    // End command delta signature.
1111) -> serde_json::Value {
1112    // Collect visible rows after command application.
1113    let visible = visible_children(children);
1114    // Build the JSON delta consumed by the UI.
1115    json!({
1116        // Include command kind.
1117        "command": command_name(command.command),
1118        // Include child path.
1119        "child_path": command.target.child_path,
1120        // Include previous lifecycle state.
1121        "previous_lifecycle_state": transition.previous_lifecycle_state.as_str(),
1122        // Include lifecycle state.
1123        "lifecycle_state": transition.lifecycle_state.as_str(),
1124        // Include state generation.
1125        "state_generation": state_generation,
1126        // Include current topology after mutation.
1127        "topology": topology(&visible),
1128        // Include current runtime rows after mutation.
1129        "runtime_state": runtime_rows(&visible),
1130        // Include command event rows.
1131        "recent_events": [command_event_record(target_id, sequence, command, transition)],
1132        // Include command log rows.
1133        "recent_logs": [command_log_record(target_id, sequence, command, transition)],
1134        // Include dropped event count.
1135        "dropped_event_count": 2,
1136        // Include dropped log count.
1137        "dropped_log_count": 1,
1138        // End delta object.
1139    })
1140    // End delta construction.
1141}
1142
1143/// Builds an event row for a command.
1144fn command_event_record(
1145    // Accept target identifier.
1146    target_id: &str,
1147    // Accept activity sequence.
1148    sequence: u64,
1149    // Accept original command.
1150    command: &ControlCommandRequest,
1151    // Accept lifecycle transition.
1152    transition: &CommandTransition,
1153    // End command event signature.
1154) -> EventRecord {
1155    // Resolve target path.
1156    let target_path = command
1157        // Access command target.
1158        .target
1159        // Access child path.
1160        .child_path
1161        // Clone path value.
1162        .clone()
1163        // Use root when the command targets the tree.
1164        .unwrap_or_else(|| ROOT_PATH.to_owned());
1165    // Resolve child id.
1166    let child_id = child_id_from_path(&target_path);
1167    // Build event record.
1168    EventRecord {
1169        // Include target identifier.
1170        target_id: target_id.to_owned(),
1171        // Include command event sequence.
1172        sequence: 7000_u64.saturating_add(sequence),
1173        // Include command identifier.
1174        correlation_id: command.command_id.clone(),
1175        // Include command event type.
1176        event_type: command_event_type(command.command).to_owned(),
1177        // Include command severity.
1178        severity: command_severity(command.command).to_owned(),
1179        // Include affected target path.
1180        target_path,
1181        // Include affected child identifier.
1182        child_id,
1183        // Include occurrence time.
1184        occurred_at_unix_nanos: unix_nanos_now(),
1185        // Include config version.
1186        config_version: CONFIG_VERSION.to_owned(),
1187        // Include command payload.
1188        payload: json!({
1189            // Include child path.
1190            "child_path": command.target.child_path,
1191            // Include command name.
1192            "command": command_name(command.command),
1193            // Include previous lifecycle state.
1194            "previous_lifecycle_state": transition.previous_lifecycle_state.as_str(),
1195            // Include lifecycle state.
1196            "lifecycle_state": transition.lifecycle_state.as_str(),
1197            // Include command reason.
1198            "reason": command.reason,
1199            // End command payload.
1200        }),
1201        // End event record construction.
1202    }
1203    // End event record.
1204}
1205
1206/// Builds a log row for a command.
1207fn command_log_record(
1208    // Accept target identifier.
1209    target_id: &str,
1210    // Accept activity sequence.
1211    sequence: u64,
1212    // Accept original command.
1213    command: &ControlCommandRequest,
1214    // Accept lifecycle transition.
1215    transition: &CommandTransition,
1216    // End command log signature.
1217) -> LogRecord {
1218    // Resolve target path.
1219    let target_path = command
1220        // Access command target.
1221        .target
1222        // Access child path.
1223        .child_path
1224        // Clone path value.
1225        .clone()
1226        // Use root when the command targets the tree.
1227        .unwrap_or_else(|| ROOT_PATH.to_owned());
1228    // Start structured fields.
1229    let mut fields = BTreeMap::new();
1230    // Include child path field.
1231    fields.insert("child_path".to_owned(), target_path.clone());
1232    // Include command field.
1233    fields.insert("command".to_owned(), command_name(command.command).to_owned());
1234    // Include previous lifecycle field.
1235    fields.insert("previous_lifecycle_state".to_owned(), transition.previous_lifecycle_state.clone());
1236    // Include lifecycle field.
1237    fields.insert("lifecycle_state".to_owned(), transition.lifecycle_state.clone());
1238    // Build log record.
1239    LogRecord {
1240        // Include target identifier.
1241        target_id: target_id.to_owned(),
1242        // Include log sequence.
1243        sequence: Some(8000_u64.saturating_add(sequence)),
1244        // Include command identifier.
1245        correlation_id: Some(command.command_id.clone()),
1246        // Include log severity.
1247        severity: command_severity(command.command).to_owned(),
1248        // Include log message.
1249        message: format!(
1250            // Continue the demo expression.
1251            "{} {} completed, transitioned from {} to {}",
1252            // Continue the demo expression.
1253            target_path,
1254            // Continue the demo expression.
1255            command_name(command.command),
1256            // Continue the demo expression.
1257            transition.previous_lifecycle_state,
1258            // Continue the demo expression.
1259            transition.lifecycle_state,
1260            // End transition message expression.
1261        ),
1262        // Include structured fields.
1263        fields,
1264        // Include occurrence time.
1265        occurred_at_unix_nanos: unix_nanos_now(),
1266        // End log record construction.
1267    }
1268    // End log record.
1269}
1270
1271/// Returns command event type.
1272fn command_event_type(command: ControlCommandKind) -> &'static str {
1273    // Match command to event type.
1274    match command {
1275        // Return restart event type.
1276        ControlCommandKind::RestartChild => "child_restarted",
1277        // Return pause event type.
1278        ControlCommandKind::PauseChild => "child_paused",
1279        // Return resume event type.
1280        ControlCommandKind::ResumeChild => "child_resumed",
1281        // Return quarantine event type.
1282        ControlCommandKind::QuarantineChild => "child_quarantined",
1283        // Return remove event type.
1284        ControlCommandKind::RemoveChild => "child_removed",
1285        // Return add event type.
1286        ControlCommandKind::AddChild => "child_added",
1287        // Return shutdown event type.
1288        ControlCommandKind::ShutdownTree => "tree_stopped",
1289        // End event type match.
1290    }
1291    // End event type lookup.
1292}
1293
1294/// Returns command severity.
1295fn command_severity(command: ControlCommandKind) -> &'static str {
1296    // Match command to severity.
1297    match command {
1298        // Mark disruptive commands as warning.
1299        ControlCommandKind::QuarantineChild | ControlCommandKind::ShutdownTree => "warning",
1300        // Mark other commands as info.
1301        _ => "info",
1302        // End severity match.
1303    }
1304    // End severity lookup.
1305}
1306
1307/// Extracts child id from path.
1308fn child_id_from_path(path: &str) -> Option<String> {
1309    // Skip the root path.
1310    if path == ROOT_PATH {
1311        // Return no child identifier.
1312        return None;
1313        // End root branch.
1314    }
1315    // Extract final path segment.
1316    path.rsplit('/')
1317        // Keep non-empty segments.
1318        .find(|segment| !segment.is_empty())
1319        // Convert to owned string.
1320        .map(ToOwned::to_owned)
1321    // End child id extraction.
1322}
1323
1324/// Extracts the child identifier from a command.
1325///
1326/// # Arguments
1327///
1328/// - `command`: Command request.
1329///
1330/// # Returns
1331///
1332/// Returns the final path segment.
1333fn command_child_id(command: &ControlCommandRequest) -> Result<String, DashboardError> {
1334    // Read the command child path.
1335    let path = command.target.child_path.as_deref().ok_or_else(|| {
1336        // Return missing child path validation.
1337        validation(
1338            // Continue the demo expression.
1339            &command.target_id,
1340            // Continue the demo expression.
1341            "child_path is required for child command",
1342            // Continue the demo expression.
1343        )
1344        // End missing child path validation.
1345    })?;
1346    // Extract the final non-empty path segment.
1347    let child_id = path
1348        // Continue the demo expression.
1349        .rsplit('/')
1350        // Continue the demo expression.
1351        .find(|segment| !segment.is_empty())
1352        // Continue the demo expression.
1353        .unwrap_or(path);
1354    // Return the child identifier.
1355    Ok(child_id.to_owned())
1356    // End child identifier extraction.
1357}
1358
1359/// Returns whether a command is dangerous.
1360///
1361/// # Arguments
1362///
1363/// - `command`: Command kind.
1364///
1365/// # Returns
1366///
1367/// Returns whether confirmation is required.
1368fn is_dangerous(command: ControlCommandKind) -> bool {
1369    // Match commands that require confirmation.
1370    matches!(
1371        // Use the supplied command.
1372        command,
1373        // Include remove child.
1374        ControlCommandKind::RemoveChild
1375            // Include add child.
1376            | ControlCommandKind::AddChild
1377            // Include shutdown tree.
1378            | ControlCommandKind::ShutdownTree // End dangerous command match.
1379                                               // Continue the demo expression.
1380    )
1381    // End dangerous command predicate.
1382}
1383
1384/// Returns whether add-child is missing a manifest.
1385///
1386/// # Arguments
1387///
1388/// - `command`: Command request.
1389///
1390/// # Returns
1391///
1392/// Returns true when the manifest is absent or blank.
1393fn missing_child_manifest(command: &ControlCommandRequest) -> bool {
1394    // Read the optional manifest.
1395    let manifest = command.target.child_manifest.as_deref().unwrap_or_default();
1396    // Return blank status.
1397    manifest.trim().is_empty()
1398    // End manifest predicate.
1399}
1400
1401/// Returns lifecycle after a command.
1402///
1403/// # Arguments
1404///
1405/// - `command`: Command kind.
1406///
1407/// # Returns
1408///
1409/// Returns the lifecycle label after command application.
1410fn lifecycle_after(command: ControlCommandKind) -> &'static str {
1411    // Match command lifecycle outcomes.
1412    match command {
1413        // Restarted children are restarting.
1414        ControlCommandKind::RestartChild => "restarting",
1415        // Paused children are paused.
1416        ControlCommandKind::PauseChild => "paused",
1417        // Resumed children are running.
1418        ControlCommandKind::ResumeChild => "running",
1419        // Quarantined children are quarantined.
1420        ControlCommandKind::QuarantineChild => "quarantined",
1421        // Removed children are removed.
1422        ControlCommandKind::RemoveChild => "removed",
1423        // Added children are running.
1424        ControlCommandKind::AddChild => "running",
1425        // Shutdown is represented separately.
1426        ControlCommandKind::ShutdownTree => "stopped",
1427        // End lifecycle match.
1428    }
1429    // End lifecycle lookup.
1430}
1431
1432/// Returns the command wire name.
1433///
1434/// # Arguments
1435///
1436/// - `command`: Command kind.
1437///
1438/// # Returns
1439///
1440/// Returns the command label.
1441fn command_name(command: ControlCommandKind) -> &'static str {
1442    // Match command names.
1443    match command {
1444        // Return restart name.
1445        ControlCommandKind::RestartChild => "restart_child",
1446        // Return pause name.
1447        ControlCommandKind::PauseChild => "pause_child",
1448        // Return resume name.
1449        ControlCommandKind::ResumeChild => "resume_child",
1450        // Return quarantine name.
1451        ControlCommandKind::QuarantineChild => "quarantine_child",
1452        // Return remove name.
1453        ControlCommandKind::RemoveChild => "remove_child",
1454        // Return add name.
1455        ControlCommandKind::AddChild => "add_child",
1456        // Return shutdown name.
1457        ControlCommandKind::ShutdownTree => "shutdown_tree",
1458        // End command name match.
1459    }
1460    // End command name lookup.
1461}
1462
1463/// Builds child path from identifier.
1464///
1465/// # Arguments
1466///
1467/// - `child_id`: Child identifier.
1468///
1469/// # Returns
1470///
1471/// Returns an absolute demo child path.
1472fn child_path(child_id: &str) -> String {
1473    // Build the path string.
1474    format!("{ROOT_PATH}/{child_id}")
1475    // End child path construction.
1476}
1477
1478/// Returns event type for one child state.
1479///
1480/// # Arguments
1481///
1482/// - `child`: Child row.
1483///
1484/// # Returns
1485///
1486/// Returns a dashboard event type.
1487fn event_type(child: &DemoChild) -> &'static str {
1488    // Match lifecycle state.
1489    match child.lifecycle.as_str() {
1490        // Failed rows emit child_failed.
1491        "failed" => "child_failed",
1492        // Restarting rows emit child_restarted.
1493        "restarting" => "child_restarted",
1494        // Paused rows emit child_paused.
1495        "paused" => "child_paused",
1496        // Quarantined rows emit child_quarantined.
1497        "quarantined" => "child_quarantined",
1498        // Other rows emit child_running.
1499        _ => "child_running",
1500        // End event type match.
1501    }
1502    // End event type lookup.
1503}
1504
1505/// Returns severity for one child state.
1506///
1507/// # Arguments
1508///
1509/// - `child`: Child row.
1510///
1511/// # Returns
1512///
1513/// Returns a dashboard severity label.
1514fn severity(child: &DemoChild) -> &'static str {
1515    // Match lifecycle state.
1516    match child.lifecycle.as_str() {
1517        // Failed and quarantined rows are errors.
1518        "failed" | "quarantined" => "error",
1519        // Restarting rows are warnings.
1520        "restarting" => "warning",
1521        // Other rows are informational.
1522        _ => "info",
1523        // End severity match.
1524    }
1525    // End severity lookup.
1526}
1527
1528/// Returns last failure for failed children.
1529///
1530/// # Arguments
1531///
1532/// - `child`: Child row.
1533///
1534/// # Returns
1535///
1536/// Returns an optional failure string.
1537fn last_failure(child: &DemoChild) -> Option<String> {
1538    // Return failure text for failed rows.
1539    if child.lifecycle == "failed" {
1540        // Return the failure string.
1541        Some("duplicate event window exceeded".to_owned())
1542        // End failed branch.
1543    } else {
1544        // Return no failure for other rows.
1545        None
1546        // End non-failed branch.
1547    }
1548    // End failure lookup.
1549}
1550
1551/// Returns last policy decision for active policy rows.
1552///
1553/// # Arguments
1554///
1555/// - `child`: Child row.
1556///
1557/// # Returns
1558///
1559/// Returns an optional policy decision string.
1560fn last_policy_decision(child: &DemoChild) -> Option<String> {
1561    // Return policy summary for notable states.
1562    if child.lifecycle == "failed" || child.lifecycle == "quarantined" {
1563        // Return quarantine policy.
1564        Some("quarantine".to_owned())
1565        // End policy branch.
1566    } else if child.lifecycle == "restarting" {
1567        // Return restart policy.
1568        Some("restart".to_owned())
1569        // End restart branch.
1570    } else {
1571        // Return no policy summary.
1572        None
1573        // End default branch.
1574    }
1575    // End policy lookup.
1576}
1577
1578/// Builds a validation error for the scenario.
1579///
1580/// # Arguments
1581///
1582/// - `target_id`: Target process identifier.
1583/// - `message`: Validation message.
1584///
1585/// # Returns
1586///
1587/// Returns a dashboard validation error.
1588fn validation(target_id: &str, message: &str) -> DashboardError {
1589    // Create a target-scoped validation error.
1590    DashboardError::validation("command_validate", Some(target_id.to_owned()), message)
1591    // End validation construction.
1592}
1593
1594/// Reads current wall-clock time as Unix nanoseconds.
1595///
1596/// # Arguments
1597///
1598/// This function has no arguments.
1599///
1600/// # Returns
1601///
1602/// Returns zero when the clock is before the Unix epoch.
1603fn unix_nanos_now() -> u128 {
1604    // Read duration since Unix epoch.
1605    std::time::SystemTime::now()
1606        // Convert the system time into a duration.
1607        .duration_since(std::time::UNIX_EPOCH)
1608        // Fall back to zero on clock skew.
1609        .unwrap_or(std::time::Duration::ZERO)
1610        // Convert to nanoseconds.
1611        .as_nanos()
1612    // End time conversion.
1613}
1614
1615// Compile tests only when the demo example is tested directly.
1616#[cfg(test)]
1617// Group scenario tests with the scenario module.
1618mod tests {
1619    // Import the scenario under test.
1620    use super::DemoScenario;
1621    // Import command model values for command tests.
1622    use rust_supervisor::dashboard::model::{
1623        // Import command kind values.
1624        ControlCommandKind,
1625        // Import command request shape.
1626        ControlCommandRequest,
1627        // Import command target shape.
1628        ControlCommandTarget,
1629        // Import dashboard state shape.
1630        DashboardState,
1631        // End imports.
1632    };
1633
1634    // Define the target identifier used by tests.
1635    const TEST_TARGET_ID: &str = "payments-worker-a";
1636    // Define the target display name used by tests.
1637    const TEST_DISPLAY_NAME: &str = "payments worker a";
1638
1639    /// Verifies that the state payload covers the visible UI surface.
1640    #[test]
1641    /// Runs the state surface test.
1642    fn state_contains_ui_surface() {
1643        // Build the demo scenario.
1644        let scenario = scenario();
1645        // Build the current state.
1646        let state = scenario.state();
1647        // Assert topology has root plus children.
1648        assert!(state.topology.nodes.len() >= 6);
1649        // Assert runtime rows include all demo children.
1650        assert!(state.runtime_state.len() >= 5);
1651        // Assert recent events are visible.
1652        assert!(!state.recent_events.is_empty());
1653        // Assert recent logs are visible.
1654        assert!(!state.recent_logs.is_empty());
1655        // Assert dropped event count is visible.
1656        assert_eq!(state.dropped_event_count, 2);
1657        // Assert dropped log count is visible.
1658        assert_eq!(state.dropped_log_count, 1);
1659        // Assert all lifecycle states are present.
1660        for lifecycle in ["failed", "restarting", "paused", "quarantined", "running"] {
1661            // Check the lifecycle state.
1662            let present = has_lifecycle(&state, lifecycle);
1663            // Assert lifecycle presence.
1664            assert!(present);
1665            // End lifecycle assertion.
1666        }
1667        // End state surface test.
1668    }
1669
1670    /// Verifies that command results preserve command identifiers.
1671    #[test]
1672    /// Runs the command result test.
1673    fn command_result_preserves_command_id() {
1674        // Build the demo scenario.
1675        let scenario = scenario();
1676        // Build a pause command.
1677        let command = command(
1678            // Use pause command.
1679            ControlCommandKind::PauseChild,
1680            // Use command identifier.
1681            "cmd-1",
1682            // Use target child path.
1683            "/root/healthy_worker",
1684            // Mark confirmation present.
1685            true,
1686            // End pause command construction.
1687        );
1688        // Apply the command.
1689        let result = scenario.command_result(command).expect("command result");
1690        // Assert command identifier is preserved.
1691        assert_eq!(result.command_id, "cmd-1");
1692        // Assert command completed.
1693        assert_eq!(result.status, "completed");
1694        // Build updated state.
1695        let state = scenario.state();
1696        // Assert the child was paused.
1697        let paused = has_child_state(&state, "/root/healthy_worker", "paused");
1698        // Assert paused child state.
1699        assert!(paused);
1700        // End command result test.
1701    }
1702
1703    /// Verifies that pause command deltas include UI-consumable runtime rows.
1704    #[test]
1705    /// Runs the pause command delta test.
1706    fn pause_child_delta_contains_runtime_state() {
1707        // Build the demo scenario.
1708        let scenario = scenario();
1709        // Build a pause command.
1710        let command = command(
1711            // Use pause command.
1712            ControlCommandKind::PauseChild,
1713            // Use command identifier.
1714            "cmd-pause",
1715            // Use target child path.
1716            "/root/healthy_worker",
1717            // Mark confirmation present.
1718            true,
1719            // End pause command construction.
1720        );
1721        // Apply the command.
1722        let result = scenario.command_result(command).expect("command result");
1723        // Read the delta.
1724        let delta = result.state_delta.expect("state delta");
1725        // Check the paused child appears in delta.
1726        let paused = delta_runtime_has_child_state(&delta, "/root/healthy_worker", "paused");
1727        // Assert the paused child appears in delta.
1728        assert!(paused);
1729        // End pause delta test.
1730    }
1731
1732    /// Verifies that remove command deltas update topology and include logs.
1733    #[test]
1734    /// Runs the remove command delta test.
1735    fn remove_child_delta_removes_topology_node_and_logs_command() {
1736        // Build the demo scenario.
1737        let scenario = scenario();
1738        // Build a remove command.
1739        let command = command(
1740            // Use remove command.
1741            ControlCommandKind::RemoveChild,
1742            // Use command identifier.
1743            "cmd-remove",
1744            // Use target child path.
1745            "/root/healthy_worker",
1746            // Mark confirmation present.
1747            true,
1748            // End remove command construction.
1749        );
1750        // Apply the command.
1751        let result = scenario.command_result(command).expect("command result");
1752        // Read the delta.
1753        let delta = result.state_delta.expect("state delta");
1754        // Check removed child is absent.
1755        let removed = !delta_topology_has_node(&delta, "/root/healthy_worker");
1756        // Assert removed child is absent.
1757        assert!(removed);
1758        // Check remove command log exists.
1759        let logged = delta_logs_contain(&delta, "remove_child");
1760        // Assert remove command log exists.
1761        assert!(logged);
1762        // End remove delta test.
1763    }
1764
1765    /// Verifies that command activity describes lifecycle transitions.
1766    #[test]
1767    /// Runs the lifecycle transition test.
1768    fn command_delta_describes_lifecycle_transition() {
1769        // Build the demo scenario.
1770        let scenario = scenario();
1771        // Build a pause command.
1772        let command = command(
1773            // Use pause command.
1774            ControlCommandKind::PauseChild,
1775            // Use command identifier.
1776            "cmd-transition",
1777            // Use target child path.
1778            "/root/healthy_worker",
1779            // Mark confirmation present.
1780            true,
1781            // End pause command construction.
1782        );
1783        // Apply the command.
1784        let result = scenario.command_result(command).expect("command result");
1785        // Read the delta.
1786        let delta = result.state_delta.expect("state delta");
1787        // Assert the previous lifecycle is present.
1788        assert_eq!(
1789            // Read previous lifecycle from event payload.
1790            delta["recent_events"][0]["payload"]["previous_lifecycle_state"].as_str(),
1791            // Compare expected previous lifecycle.
1792            Some("running"),
1793            // End previous lifecycle assertion.
1794        );
1795        // Assert the current lifecycle is present.
1796        assert_eq!(
1797            // Read current lifecycle from event payload.
1798            delta["recent_events"][0]["payload"]["lifecycle_state"].as_str(),
1799            // Compare expected current lifecycle.
1800            Some("paused"),
1801            // End current lifecycle assertion.
1802        );
1803        // Read the log message.
1804        let message = delta["recent_logs"][0]["message"].as_str().unwrap_or_default();
1805        // Assert the log message describes the transition.
1806        assert!(message.contains("running to paused"));
1807        // Assert structured log fields describe the previous lifecycle.
1808        assert_eq!(
1809            // Read previous lifecycle from log fields.
1810            delta["recent_logs"][0]["fields"]["previous_lifecycle_state"].as_str(),
1811            // Compare expected previous lifecycle.
1812            Some("running"),
1813            // End previous log lifecycle assertion.
1814        );
1815        // Assert structured log fields describe the current lifecycle.
1816        assert_eq!(
1817            // Read current lifecycle from log fields.
1818            delta["recent_logs"][0]["fields"]["lifecycle_state"].as_str(),
1819            // Compare expected current lifecycle.
1820            Some("paused"),
1821            // End current log lifecycle assertion.
1822        );
1823        // End transition delta test.
1824    }
1825
1826    /// Verifies that add-child requires a manifest.
1827    #[test]
1828    /// Runs the add-child validation test.
1829    fn add_child_requires_manifest() {
1830        // Build the demo scenario.
1831        let scenario = scenario();
1832        // Build an add-child command without a manifest.
1833        let command = command(
1834            // Use add-child command.
1835            ControlCommandKind::AddChild,
1836            // Use command identifier.
1837            "cmd-2",
1838            // Use target child path.
1839            "/root/new_worker",
1840            // Mark confirmation present.
1841            true,
1842            // End add-child command construction.
1843        );
1844        // Apply the command.
1845        let error = scenario
1846            // Apply command.
1847            .command_result(command)
1848            // Expect validation error.
1849            .expect_err("validation error");
1850        // Assert validation failure is returned.
1851        assert_eq!(error.code, "validation_failed");
1852        // End validation test.
1853    }
1854
1855    /// Builds a demo command request.
1856    ///
1857    /// # Arguments
1858    ///
1859    /// - `kind`: Command kind.
1860    /// - `command_id`: Command identifier.
1861    /// - `child_path`: Child path.
1862    /// - `confirmed`: Confirmation flag.
1863    ///
1864    /// # Returns
1865    ///
1866    /// Returns a command request.
1867    fn command(
1868        // Accept command kind.
1869        kind: ControlCommandKind,
1870        // Accept command identifier.
1871        command_id: &str,
1872        // Accept child path.
1873        child_path: &str,
1874        // Accept confirmation flag.
1875        confirmed: bool,
1876        // End command helper signature.
1877    ) -> ControlCommandRequest {
1878        // Build command request.
1879        ControlCommandRequest {
1880            // Include command identifier.
1881            command_id: command_id.to_owned(),
1882            // Include target identifier.
1883            target_id: TEST_TARGET_ID.to_owned(),
1884            // Include command kind.
1885            command: kind,
1886            // Include child target.
1887            target: ControlCommandTarget {
1888                // Include child path.
1889                child_path: Some(child_path.to_owned()),
1890                // Omit child manifest.
1891                child_manifest: None,
1892                // End command target.
1893            },
1894            // Include reason.
1895            reason: "demo test".to_owned(),
1896            // Include requester identity.
1897            requested_by: "tester".to_owned(),
1898            // Include confirmation.
1899            confirmed,
1900            // Include request time.
1901            requested_at_unix_nanos: 1,
1902            // End command request.
1903        }
1904        // End command construction.
1905    }
1906
1907    /// Builds a default test scenario.
1908    ///
1909    /// # Arguments
1910    ///
1911    /// This function has no arguments.
1912    ///
1913    /// # Returns
1914    ///
1915    /// Returns a demo scenario.
1916    fn scenario() -> DemoScenario {
1917        // Build the test scenario.
1918        DemoScenario::new(TEST_TARGET_ID.to_owned(), TEST_DISPLAY_NAME.to_owned())
1919        // End test scenario construction.
1920    }
1921
1922    /// Checks whether a delta contains one runtime row with a lifecycle.
1923    fn delta_runtime_has_child_state(
1924        // Accept delta value.
1925        delta: &serde_json::Value,
1926        // Accept child path.
1927        child_path: &str,
1928        // Accept lifecycle.
1929        lifecycle: &str,
1930        // End helper signature.
1931    ) -> bool {
1932        // Access runtime state.
1933        let Some(rows) = delta.get("runtime_state").and_then(|value| value.as_array()) else {
1934            // Return absence.
1935            return false;
1936            // End missing runtime state branch.
1937        };
1938        // Scan runtime rows.
1939        rows.iter().any(|row| {
1940            // Compare child path.
1941            row.get("child_path").and_then(|value| value.as_str()) == Some(child_path)
1942                // Compare lifecycle state.
1943                && row.get("lifecycle_state").and_then(|value| value.as_str()) == Some(lifecycle)
1944            // End runtime row predicate.
1945        })
1946        // End runtime delta lookup.
1947    }
1948
1949    /// Checks whether a delta topology contains one node path.
1950    fn delta_topology_has_node(
1951        // Accept delta value.
1952        delta: &serde_json::Value,
1953        // Accept node path.
1954        path: &str,
1955        // End helper signature.
1956    ) -> bool {
1957        // Access topology nodes.
1958        let Some(nodes) = delta
1959            // Access topology.
1960            .get("topology")
1961            // Access node list.
1962            .and_then(|value| value.get("nodes"))
1963            // Convert to array.
1964            .and_then(|value| value.as_array())
1965        // Handle missing topology.
1966        else {
1967            // Return absence.
1968            return false;
1969            // End missing topology branch.
1970        };
1971        // Scan topology nodes.
1972        nodes
1973            // Iterate nodes.
1974            .iter()
1975            // Match path.
1976            .any(|node| node.get("path").and_then(|value| value.as_str()) == Some(path))
1977        // End topology lookup.
1978    }
1979
1980    /// Checks whether a delta log message contains text.
1981    fn delta_logs_contain(
1982        // Accept delta value.
1983        delta: &serde_json::Value,
1984        // Accept expected text.
1985        expected: &str,
1986        // End helper signature.
1987    ) -> bool {
1988        // Access recent logs.
1989        let Some(logs) = delta
1990            // Access recent logs.
1991            .get("recent_logs")
1992            // Convert to array.
1993            .and_then(|value| value.as_array())
1994        // Handle missing logs.
1995        else {
1996            // Return absence.
1997            return false;
1998            // End missing log branch.
1999        };
2000        // Scan log rows.
2001        logs
2002            // Iterate logs.
2003            .iter()
2004            // Match message text.
2005            .any(|log| {
2006                // Read message.
2007                log.get("message")
2008                    // Convert to string.
2009                    .and_then(|value| value.as_str())
2010                    // Match expected text.
2011                    .is_some_and(|message| message.contains(expected))
2012                // End log predicate.
2013            })
2014        // End log lookup.
2015    }
2016
2017    /// Checks whether a lifecycle appears in state.
2018    ///
2019    /// # Arguments
2020    ///
2021    /// - `state`: Dashboard state.
2022    /// - `lifecycle`: Lifecycle label.
2023    ///
2024    /// # Returns
2025    ///
2026    /// Returns true when a runtime row has the lifecycle.
2027    fn has_lifecycle(state: &DashboardState, lifecycle: &str) -> bool {
2028        // Scan runtime rows.
2029        state
2030            // Access runtime rows.
2031            .runtime_state
2032            // Iterate rows.
2033            .iter()
2034            // Match lifecycle.
2035            .any(|row| row.lifecycle_state == lifecycle)
2036        // End lifecycle lookup.
2037    }
2038
2039    /// Checks whether a child has a lifecycle.
2040    ///
2041    /// # Arguments
2042    ///
2043    /// - `state`: Dashboard state.
2044    /// - `child_path`: Child path.
2045    /// - `lifecycle`: Lifecycle label.
2046    ///
2047    /// # Returns
2048    ///
2049    /// Returns true when the child row has the lifecycle.
2050    fn has_child_state(state: &DashboardState, child_path: &str, lifecycle: &str) -> bool {
2051        // Scan runtime rows.
2052        state
2053            // Access runtime rows.
2054            .runtime_state
2055            // Iterate rows.
2056            .iter()
2057            // Match child path and lifecycle.
2058            .any(|row| row.child_path == child_path && row.lifecycle_state == lifecycle)
2059        // End child state lookup.
2060    }
2061    // End scenario tests.
2062}