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}