// file: crates/ksp-worker-api/src/lifecycle.rs // version: 2 /// Current lifecycle state of one continuous Worker. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] #[non_exhaustive] pub enum WorkerState { /// The Worker exists but has not started initialization. Created, /// The Worker is initializing resources before entering steady service. Starting, /// The Worker is actively providing its continuous service. Running, /// The Worker observed stop intent and is draining or releasing resources. Stopping, /// The Worker reached its normal terminal stopped state. Stopped, /// The Worker reached a terminal fault classified by one stable KSP error code. Faulted(crate::ErrorCode), } impl crate::WorkerState { /// Returns the stable safe lifecycle code without rendering Worker data. #[must_use] pub const fn code(&self) -> &'static str { return match self { Self::Created => "created", Self::Starting => "starting", Self::Running => "running", Self::Stopping => "stopping", Self::Stopped => "stopped", Self::Faulted(_) => "faulted", }; } /// Returns the terminal fault code when the state is [`Self::Faulted`]. #[must_use] pub const fn fault_code(&self) -> std::option::Option { return match self { Self::Faulted(code) => std::option::Option::Some(*code), _ => std::option::Option::None, }; } /// Reports whether no later lifecycle transition is permitted. #[must_use] pub const fn is_terminal(&self) -> bool { return matches!(self, Self::Stopped | Self::Faulted(_)); } } /// Operational health classification independent from Worker lifecycle phase. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] #[non_exhaustive] pub enum WorkerHealth { /// Health has not yet been established or cannot currently be classified. Unknown, /// The Worker is operating within its expected healthy envelope. Healthy, /// The Worker is operating with a known degradation while service remains available. Degraded, /// The Worker is currently unable to satisfy its expected service health contract. Unhealthy, } impl crate::WorkerHealth { /// Returns the stable safe code for this health classification. #[must_use] pub const fn code(&self) -> &'static str { return match self { Self::Unknown => "unknown", Self::Healthy => "healthy", Self::Degraded => "degraded", Self::Unhealthy => "unhealthy", }; } } /// Minimal generic activity classification for a continuous Worker. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] #[non_exhaustive] pub enum WorkerActivity { /// Activity has not yet been established or cannot currently be classified. Unknown, /// The Worker is alive but not currently processing concrete work. Idle, /// The Worker is currently processing concrete work. Active, } impl crate::WorkerActivity { /// Returns the stable safe code for this activity classification. #[must_use] pub const fn code(&self) -> &'static str { return match self { Self::Unknown => "unknown", Self::Idle => "idle", Self::Active => "active", }; } } /// Passive owner of one Worker identity and its validated lifecycle state. #[derive(Eq, PartialEq)] pub struct WorkerLifecycle { id: crate::WorkerId, kind: crate::WorkerKindCode, state: crate::WorkerState, } impl crate::WorkerLifecycle { /// Creates one lifecycle in [`WorkerState::Created`] state. #[must_use] pub const fn new(id: crate::WorkerId, kind: crate::WorkerKindCode) -> Self { return Self { id, kind, state: crate::WorkerState::Created }; } /// Returns the logical Worker identity. #[must_use] pub const fn id(&self) -> &crate::WorkerId { return &self.id; } /// Returns the stable Worker family code. #[must_use] pub const fn kind(&self) -> &crate::WorkerKindCode { return &self.kind; } /// Returns the current lifecycle state. #[must_use] pub const fn state(&self) -> crate::WorkerState { return self.state; } /// Begins initialization of a newly created Worker. pub fn start(&mut self) -> crate::Result<()> { return self.transition(crate::WorkerState::Starting); } /// Records that initialization completed and steady service is running. pub fn mark_running(&mut self) -> crate::Result<()> { return self.transition(crate::WorkerState::Running); } /// Records that a starting or running Worker observed cooperative stop intent. pub fn mark_stopping(&mut self) -> crate::Result<()> { return self.transition(crate::WorkerState::Stopping); } /// Records normal terminal stop after draining, or before initialization began. pub fn mark_stopped(&mut self) -> crate::Result<()> { return self.transition(crate::WorkerState::Stopped); } /// Records a terminal fault using only a stable KSP error code. pub fn fault(&mut self, code: crate::ErrorCode) -> crate::Result<()> { return self.transition(crate::WorkerState::Faulted(code)); } fn transition(&mut self, target: crate::WorkerState) -> crate::Result<()> { if !allowed_transition(self.state, target) { return std::result::Result::Err(transition_error(self.state, target)); } self.state = target; return std::result::Result::Ok(()); } } impl std::fmt::Debug for crate::WorkerLifecycle { fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { return formatter.debug_struct("WorkerLifecycle").field("id", &self.id).field("kind", &self.kind).field("state", &self.state).finish(); } } fn allowed_transition(source: crate::WorkerState, target: crate::WorkerState) -> bool { return matches!( (source, target), (crate::WorkerState::Created, crate::WorkerState::Starting) | (crate::WorkerState::Created, crate::WorkerState::Stopped) | (crate::WorkerState::Starting, crate::WorkerState::Running) | (crate::WorkerState::Starting, crate::WorkerState::Stopping) | (crate::WorkerState::Starting, crate::WorkerState::Faulted(_)) | (crate::WorkerState::Running, crate::WorkerState::Stopping) | (crate::WorkerState::Running, crate::WorkerState::Faulted(_)) | (crate::WorkerState::Stopping, crate::WorkerState::Stopped) | (crate::WorkerState::Stopping, crate::WorkerState::Faulted(_)) ); } fn transition_error(source: crate::WorkerState, target: crate::WorkerState) -> crate::Error { return crate::Error::new(crate::ERROR_CODE_WORKER_TRANSITION_INVALID, "invalid Worker lifecycle transition") .with_context("source_state", source.code()) .with_context("target_state", target.code()); } #[cfg(test)] #[path = "../unit_tests/lifecycle.rs"] mod tests;