Files
brahman/crates/runtime/sandokan-lifecycle/src/state.rs
T
sergio 545dd59c72 feat(sandokan-lifecycle): A4 — primitivas de lifecycle agnósticas
Nuevo crate runtime/sandokan-lifecycle: lógica pura reutilizable por
cualquier supervisor de procesos (shuma, matilda Ghost, charka-shadow,
mirada). Sin syscalls, sin proceso, sin UI.

Módulos:
- backoff   — Backoff exponencial con tope
- ttl       — Ttl anclado a Instant
- quota     — ResourceQuota + check_quota + Breach + QuotaAction
- restart   — RestartPolicy + RestartTracker (conteo + backoff)
- state     — LifecycleState (Pending/Running/Exited/Failed/Killed)

15 tests verdes. cargo check --workspace verde.

Variante segura de A4: se crea la library limpia sin tocar shuma-core
(módulo maduro). La migración de WorkspaceManager a consumir estas
primitivas queda registrada como A4.2 (refactor diferido, no urgente).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 00:32:52 +00:00

66 lines
2.1 KiB
Rust

//! Máquina de estados del ciclo de vida de una entidad supervisada.
use serde::{Deserialize, Serialize};
/// Estado de una entidad supervisada (proceso, workspace, sandbox, ...).
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum LifecycleState {
/// Creada, aún no arrancó.
Pending,
/// En ejecución.
Running,
/// Salió por sí misma con un código de salida.
Exited { code: i32 },
/// Falló (no llegó a correr, o crasheó de forma no capturable).
Failed { reason: String },
/// Terminada por el supervisor (SIGKILL / quota / drain).
Killed,
}
impl LifecycleState {
/// `true` si el estado es terminal (no habrá más transiciones sin
/// un restart explícito).
pub fn is_terminal(&self) -> bool {
matches!(
self,
LifecycleState::Exited { .. }
| LifecycleState::Failed { .. }
| LifecycleState::Killed
)
}
/// `true` si el estado terminal cuenta como fallo (dispara restart
/// si la política lo permite). `Exited { code: 0 }` NO es fallo.
pub fn is_failure(&self) -> bool {
match self {
LifecycleState::Exited { code } => *code != 0,
LifecycleState::Failed { .. } => true,
LifecycleState::Killed => false, // kill deliberado, no fallo
_ => false,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn terminal_detection() {
assert!(!LifecycleState::Pending.is_terminal());
assert!(!LifecycleState::Running.is_terminal());
assert!(LifecycleState::Exited { code: 0 }.is_terminal());
assert!(LifecycleState::Killed.is_terminal());
assert!(LifecycleState::Failed { reason: "x".into() }.is_terminal());
}
#[test]
fn failure_semantics() {
assert!(!LifecycleState::Exited { code: 0 }.is_failure());
assert!(LifecycleState::Exited { code: 1 }.is_failure());
assert!(LifecycleState::Failed { reason: "x".into() }.is_failure());
assert!(!LifecycleState::Killed.is_failure());
assert!(!LifecycleState::Running.is_failure());
}
}