Skip to main content

appcore_supervisor/
service.rs

1// =============================================================================
2//        #######
3//     ###       ###     F: service.rs
4//    ##   ## ##   ##    P: AppCore-Runtime
5//         ## ##
6//                       C: 2026/07/24 11:51:10 by dnettoRaw
7//    ##   ## ##   ##    U: 2026/07/24 13:18:47 by dnettoRaw
8//      ###########      S: 1.0.1-rc.8
9// =============================================================================
10
11//! Managed-service contracts and descriptors.
12
13use crate::{
14    DependencyRequirement, RestartPolicy, ServiceActivationState, ServiceHealth,
15    ServiceRuntimeState, SupervisorResult,
16};
17use std::time::Duration;
18
19/// Runtime resource controlled by a managed service.
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum ManagedResource {
22    /// Runtime lifecycle coordination.
23    Runtime,
24    /// Security and credential boundary.
25    Security,
26    /// Scheduler coordinator and task workers.
27    Scheduler,
28    /// Peer RPC listener and workers.
29    PeerRpc,
30    /// Control-plane worker.
31    ControlPlane,
32    /// Durable job workers and queues.
33    Jobs,
34    /// Update polling and activation coordination.
35    Update,
36    /// Auth-server listener and request workers.
37    AuthServer,
38    /// Metrics collection.
39    Metrics,
40    /// Observation drain.
41    Observation,
42    /// Synchronization listener or worker.
43    Sync,
44    /// Runtime HTTP listener.
45    Http,
46    /// Multi-tenant Gateway listener and connection workers.
47    Gateway,
48    /// Generic bounded worker.
49    Worker,
50    /// Generic bounded queue.
51    Queue,
52}
53
54/// One dependency and the minimum health required from it.
55#[derive(Debug, Clone, PartialEq, Eq)]
56pub struct ServiceDependency {
57    service_id: String,
58    requirement: DependencyRequirement,
59}
60
61impl ServiceDependency {
62    /// Creates a validated dependency contract.
63    pub fn new(
64        service_id: impl Into<String>,
65        requirement: DependencyRequirement,
66    ) -> SupervisorResult<Self> {
67        let dependency = Self {
68            service_id: service_id.into(),
69            requirement,
70        };
71        validate_name(&dependency.service_id)?;
72        Ok(dependency)
73    }
74
75    /// Returns the stable dependency service identifier.
76    pub fn service_id(&self) -> &str {
77        &self.service_id
78    }
79
80    /// Returns the minimum accepted dependency health.
81    pub fn requirement(&self) -> DependencyRequirement {
82        self.requirement
83    }
84}
85
86/// Immutable definition of one managed service.
87#[derive(Debug, Clone, PartialEq, Eq)]
88pub struct ServiceDescriptor {
89    name: String,
90    resource: ManagedResource,
91    dependencies: Vec<ServiceDependency>,
92    restart_policy: RestartPolicy,
93    activation: ServiceActivationState,
94    critical: bool,
95}
96
97impl ServiceDescriptor {
98    /// Creates a service descriptor with no dependencies.
99    pub fn new(
100        name: impl Into<String>,
101        resource: ManagedResource,
102        restart_policy: RestartPolicy,
103    ) -> SupervisorResult<Self> {
104        let descriptor = Self {
105            name: name.into(),
106            resource,
107            dependencies: Vec::new(),
108            restart_policy,
109            activation: ServiceActivationState::Enabled,
110            critical: true,
111        };
112        descriptor.validate()?;
113        Ok(descriptor)
114    }
115
116    /// Adds one compatibility dependency that permits degraded operation.
117    pub fn with_dependency(self, dependency: impl Into<String>) -> SupervisorResult<Self> {
118        self.add_dependency(ServiceDependency::new(
119            dependency,
120            DependencyRequirement::DegradedAllowed,
121        )?)
122    }
123
124    /// Adds one dependency with an explicit minimum-health requirement.
125    pub fn with_dependency_requirement(
126        self,
127        dependency: impl Into<String>,
128        requirement: DependencyRequirement,
129    ) -> SupervisorResult<Self> {
130        self.add_dependency(ServiceDependency::new(dependency, requirement)?)
131    }
132
133    fn add_dependency(mut self, dependency: ServiceDependency) -> SupervisorResult<Self> {
134        if dependency.service_id == self.name {
135            return Err(crate::SupervisorError::InvalidConfiguration(
136                "a service cannot depend on itself".to_string(),
137            ));
138        }
139        if !self
140            .dependencies
141            .iter()
142            .any(|current| current.service_id == dependency.service_id)
143        {
144            self.dependencies.push(dependency);
145            self.dependencies
146                .sort_by(|left, right| left.service_id.cmp(&right.service_id));
147        }
148        Ok(self)
149    }
150
151    /// Replaces the installation activation state.
152    pub fn with_activation(mut self, activation: ServiceActivationState) -> Self {
153        self.activation = activation;
154        self
155    }
156
157    /// Marks whether failure affects overall Runtime health.
158    pub fn with_critical(mut self, critical: bool) -> Self {
159        self.critical = critical;
160        self
161    }
162
163    /// Returns the stable service name.
164    pub fn name(&self) -> &str {
165        &self.name
166    }
167
168    /// Returns the owned Runtime resource.
169    pub fn resource(&self) -> ManagedResource {
170        self.resource
171    }
172
173    /// Returns required service dependencies.
174    pub fn dependencies(&self) -> &[ServiceDependency] {
175        &self.dependencies
176    }
177
178    /// Returns restart and shutdown policy.
179    pub fn restart_policy(&self) -> RestartPolicy {
180        self.restart_policy
181    }
182
183    /// Returns the installation activation state.
184    pub fn activation(&self) -> ServiceActivationState {
185        self.activation
186    }
187
188    /// Reports whether failure affects overall Runtime health.
189    pub fn is_critical(&self) -> bool {
190        self.critical
191    }
192
193    /// Validates descriptor and policy invariants.
194    pub fn validate(&self) -> SupervisorResult<()> {
195        validate_name(&self.name)?;
196        self.restart_policy.validate()
197    }
198}
199
200/// Lifecycle boundary implemented by every supervised Runtime service.
201pub trait ManagedService: Send + Sync {
202    /// Returns immutable identity, dependencies, resource, and policy.
203    fn descriptor(&self) -> &ServiceDescriptor;
204    /// Starts the service and its bounded resources.
205    fn start(&self) -> SupervisorResult<()>;
206    /// Requests cooperative shutdown inside `timeout`.
207    fn stop(&self, timeout: Duration) -> SupervisorResult<()>;
208    /// Returns current service health.
209    fn health(&self) -> ServiceHealth;
210    /// Returns the concrete service execution state.
211    fn runtime_state(&self) -> ServiceRuntimeState {
212        match self.health() {
213            ServiceHealth::Starting => ServiceRuntimeState::Starting,
214            ServiceHealth::Ready | ServiceHealth::Healthy | ServiceHealth::Degraded => {
215                ServiceRuntimeState::Running
216            }
217            ServiceHealth::Stopping => ServiceRuntimeState::Stopping,
218            ServiceHealth::Failed => ServiceRuntimeState::Failed,
219            ServiceHealth::Unknown => ServiceRuntimeState::Stopped,
220        }
221    }
222}
223
224fn validate_name(name: &str) -> SupervisorResult<()> {
225    if name.is_empty()
226        || name.len() > 128
227        || !name
228            .bytes()
229            .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'.' | b'-' | b'_'))
230    {
231        return Err(crate::SupervisorError::InvalidConfiguration(
232            "service name is invalid".to_string(),
233        ));
234    }
235    Ok(())
236}