Skip to main content

appcore_ops/
availability.rs

1// =============================================================================
2//        #######
3//     ###       ###     F: availability.rs
4//    ##   ## ##   ##    P: AppCore-Runtime
5//         ## ##
6//                       C: 2026/07/23 23:50:45 by dnettoRaw
7//    ##   ## ##   ##    U: 2026/07/23 23:50:45 by dnettoRaw
8//      ###########      S: 1.0.1-rc.8
9// =============================================================================
10
11//! Stable Runtime liveness and readiness semantics.
12
13use crate::HealthStatus;
14use appcore_core::RuntimeOperationalMode;
15use serde::{Deserialize, Serialize};
16
17/// Coarse operational availability exposed to supervisors and routers.
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
19#[serde(rename_all = "snake_case")]
20pub enum RuntimeAvailabilityState {
21    /// Runtime can serve the work allowed by its configured mode.
22    Ready,
23    /// Runtime serves a reduced local workload while a dependency is impaired.
24    Degraded,
25    /// Runtime is alive but policy or security prevents normal traffic.
26    Restricted,
27    /// Runtime is alive and local-first reads may continue without coordination.
28    Isolated,
29    /// Runtime is not alive and accepts no traffic.
30    Stopped,
31}
32
33/// Stable health projection for process, local, distributed, and write traffic.
34#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
35pub struct RuntimeAvailabilityReport {
36    /// Coarse availability state.
37    pub state: RuntimeAvailabilityState,
38    /// Process watchdog signal. False only after the Runtime stops.
39    pub liveness: bool,
40    /// Whether policy-authorized local queries can be served.
41    pub local_readiness: bool,
42    /// Whether work requiring cluster coordination can be served.
43    pub distributed_readiness: bool,
44    /// Whether state-changing commands can be accepted.
45    pub write_readiness: bool,
46}
47
48impl RuntimeAvailabilityReport {
49    /// Projects component health and operational mode into stable semantics.
50    pub fn evaluate(health: HealthStatus, mode: RuntimeOperationalMode) -> Self {
51        if health == HealthStatus::Stopped {
52            return Self::stopped();
53        }
54        if health == HealthStatus::Restricted {
55            return Self::restricted();
56        }
57        match mode {
58            RuntimeOperationalMode::Isolated => Self {
59                state: RuntimeAvailabilityState::Isolated,
60                liveness: true,
61                local_readiness: true,
62                distributed_readiness: false,
63                write_readiness: false,
64            },
65            RuntimeOperationalMode::Degraded => Self::degraded(true),
66            RuntimeOperationalMode::Starting
67            | RuntimeOperationalMode::Discovering
68            | RuntimeOperationalMode::Syncing => Self::degraded(false),
69            RuntimeOperationalMode::ReadOnly => Self::ready(false),
70            RuntimeOperationalMode::ReadWrite if health == HealthStatus::Degraded => {
71                Self::degraded(true)
72            }
73            RuntimeOperationalMode::ReadWrite => Self::ready(true),
74        }
75    }
76
77    fn ready(writes: bool) -> Self {
78        Self {
79            state: RuntimeAvailabilityState::Ready,
80            liveness: true,
81            local_readiness: true,
82            distributed_readiness: true,
83            write_readiness: writes,
84        }
85    }
86
87    fn degraded(local_ready: bool) -> Self {
88        Self {
89            state: RuntimeAvailabilityState::Degraded,
90            liveness: true,
91            local_readiness: local_ready,
92            distributed_readiness: false,
93            write_readiness: false,
94        }
95    }
96
97    fn restricted() -> Self {
98        Self {
99            state: RuntimeAvailabilityState::Restricted,
100            liveness: true,
101            local_readiness: false,
102            distributed_readiness: false,
103            write_readiness: false,
104        }
105    }
106
107    fn stopped() -> Self {
108        Self {
109            state: RuntimeAvailabilityState::Stopped,
110            liveness: false,
111            local_readiness: false,
112            distributed_readiness: false,
113            write_readiness: false,
114        }
115    }
116}
117
118#[cfg(test)]
119mod tests {
120    use super::*;
121
122    #[test]
123    fn read_write_health_is_fully_ready() {
124        let report = RuntimeAvailabilityReport::evaluate(
125            HealthStatus::Healthy,
126            RuntimeOperationalMode::ReadWrite,
127        );
128        assert_eq!(report.state, RuntimeAvailabilityState::Ready);
129        assert!(report.liveness);
130        assert!(report.local_readiness);
131        assert!(report.distributed_readiness);
132        assert!(report.write_readiness);
133    }
134
135    #[test]
136    fn isolated_runtime_keeps_only_local_reads_ready() {
137        let report = RuntimeAvailabilityReport::evaluate(
138            HealthStatus::Healthy,
139            RuntimeOperationalMode::Isolated,
140        );
141        assert_eq!(report.state, RuntimeAvailabilityState::Isolated);
142        assert!(report.liveness);
143        assert!(report.local_readiness);
144        assert!(!report.distributed_readiness);
145        assert!(!report.write_readiness);
146    }
147
148    #[test]
149    fn restricted_and_stopped_states_fail_readiness() {
150        let restricted = RuntimeAvailabilityReport::evaluate(
151            HealthStatus::Restricted,
152            RuntimeOperationalMode::ReadWrite,
153        );
154        let stopped = RuntimeAvailabilityReport::evaluate(
155            HealthStatus::Stopped,
156            RuntimeOperationalMode::ReadWrite,
157        );
158        assert!(restricted.liveness);
159        assert!(!restricted.local_readiness);
160        assert!(!stopped.liveness);
161    }
162}