Skip to main content

shep_core/
status.rs

1//! Process lifecycle status
2
3use core::fmt;
4
5use serde::{Deserialize, Serialize};
6
7/// Lifecycle state of a sheep (one managed process)
8///
9/// The serialized strings are the wire contract; `waiting-restart` means a
10/// backoff or restart delay is pending.
11// wire format: changing these strings is a breaking change
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
13#[serde(rename_all = "kebab-case")]
14pub enum ProcStatus {
15    /// Spawned, not yet ready
16    Starting,
17    /// Running and (if configured) ready
18    Online,
19    /// This instance is going away and is not a restart target
20    ///
21    /// Reachable from exactly one path: a reload's `SpawnNew` step, which
22    /// marks the instance being replaced before its replacement is spawned,
23    /// so the two never both count as running. Nothing else sets it — an
24    /// operator's `stop` leaves a sheep `Online` for its whole kill ladder
25    /// instead, so this status names reload's transient specifically, never
26    /// "any kill ladder in progress". A scheduled restart or an out-of-band
27    /// liveness/memory-limit restart must both reject a sheep in this status
28    /// rather than race the fresh replacement coming to take over its slot.
29    Stopping,
30    /// Cleanly stopped; not scheduled to run
31    Stopped,
32    /// Restart budget exhausted or spawn failed
33    Errored,
34    /// Restart pending after a backoff or configured delay
35    WaitingRestart,
36}
37
38impl fmt::Display for ProcStatus {
39    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40        f.write_str(match self {
41            Self::Starting => "starting",
42            Self::Online => "online",
43            Self::Stopping => "stopping",
44            Self::Stopped => "stopped",
45            Self::Errored => "errored",
46            Self::WaitingRestart => "waiting-restart",
47        })
48    }
49}
50
51#[cfg(test)]
52mod tests {
53    use super::*;
54
55    #[test]
56    fn wire_strings_are_stable() {
57        // wire format: these six strings are the protocol contract (spec §4)
58        let cases = [
59            (ProcStatus::Starting, "\"starting\""),
60            (ProcStatus::Online, "\"online\""),
61            (ProcStatus::Stopping, "\"stopping\""),
62            (ProcStatus::Stopped, "\"stopped\""),
63            (ProcStatus::Errored, "\"errored\""),
64            (ProcStatus::WaitingRestart, "\"waiting-restart\""),
65        ];
66        for (status, json) in cases {
67            assert_eq!(serde_json::to_string(&status).unwrap(), json);
68            assert_eq!(serde_json::from_str::<ProcStatus>(json).unwrap(), status);
69            assert_eq!(format!("\"{status}\""), json);
70        }
71    }
72}