frame-core 0.2.0

Component model, lifecycle, process isolation, and WASM module host
Documentation
//! Typed registration and lifecycle failures.

use beamr::scheduler::MailboxSendError;
use serde::{Deserialize, Serialize};
use thiserror::Error;

use crate::capability::{CapabilityRequest, ScopeValidationError};
use crate::component::ComponentId;
use crate::event::LifecycleState;

/// Stable classification of a beamr process tombstone.
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub enum TombstoneKind {
    /// Process completed normally.
    Normal,
    /// Host requested an untrappable kill.
    Kill,
    /// Process observed a kill and terminated.
    Killed,
    /// Runtime execution failed.
    Error,
    /// A linked distributed node disconnected.
    NoConnection,
    /// A link targeted a process that was already absent.
    NoProcess,
}

/// Observable reason a component entered [`LifecycleState::Failed`].
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct FailureReason {
    /// Child associated with the failure, if a child had been identified.
    pub child: Option<String>,
    /// Tombstone observed for the failing process, when one existed.
    pub tombstone: Option<TombstoneKind>,
    /// Runtime detail; rich exception information is intentionally optional.
    pub detail: String,
}

/// A typed refusal or runtime failure from the component registry.
#[derive(Debug, Error)]
pub enum RegistryError {
    /// Registration attempted to reuse a stable identity.
    #[error("component {id} is already registered")]
    DuplicateComponent {
        /// Conflicting stable identity.
        id: ComponentId,
    },
    /// A declared dependency was not in the registration table.
    #[error("component {component} requires missing component {required}")]
    MissingDependency {
        /// Component being registered.
        component: ComponentId,
        /// Missing edge target.
        required: ComponentId,
    },
    /// A dependency edge would make the graph cyclic.
    #[error("dependency edge {component} -> {required} creates a cycle")]
    CyclicDependency {
        /// Source of the cyclic edge.
        component: ComponentId,
        /// Target of the cyclic edge.
        required: ComponentId,
    },
    /// Two registered components claimed the same capability identifier.
    #[error("capability {capability} is already provided by component {provider}")]
    CapabilityConflict {
        /// Colliding service capability identifier.
        capability: String,
        /// Existing provider.
        provider: ComponentId,
    },
    /// A host-authority declaration used a malformed typed scope.
    #[error("component {component} declares invalid capability need {request:?}: {reason}")]
    InvalidCapabilityScope {
        /// Component being registered.
        component: ComponentId,
        /// Offending declaration.
        request: CapabilityRequest,
        /// Exact scope validation refusal.
        reason: ScopeValidationError,
    },
    /// A manifest repeated an exact kind-and-scope pair.
    #[error("component {component} declares capability need {request:?} more than once")]
    DuplicateCapabilityNeed {
        /// Component being registered.
        component: ComponentId,
        /// Duplicate declaration.
        request: CapabilityRequest,
    },
    /// Registration omitted the mandatory restart intensity declaration.
    #[error("component {id} has no declared supervision intensity")]
    UndeclaredSupervision {
        /// Component missing the declaration.
        id: ComponentId,
    },
    /// A restart window of zero cannot define an intensity interval.
    #[error("component {id} declares a zero-length supervision window")]
    InvalidSupervisionWindow {
        /// Component with the invalid policy.
        id: ComponentId,
    },
    /// Child names are component-local unique keys.
    #[error("component {id} declares child name {child} more than once")]
    DuplicateChild {
        /// Component with the duplicate declaration.
        id: ComponentId,
        /// Duplicate child key.
        child: String,
    },
    /// The requested component is not registered.
    #[error("component {id} is not registered")]
    NotRegistered {
        /// Requested identity.
        id: ComponentId,
    },
    /// A start is already in flight for this identity.
    #[error("component {id} is already starting")]
    AlreadyStarting {
        /// Busy identity.
        id: ComponentId,
    },
    /// The component is already running.
    #[error("component {id} is already running")]
    AlreadyRunning {
        /// Running identity.
        id: ComponentId,
    },
    /// A stop is already in flight for this identity.
    #[error("component {id} is already stopping")]
    AlreadyStopping {
        /// Busy identity.
        id: ComponentId,
    },
    /// A remove is already in flight for this identity.
    #[error("component {id} is already being removed")]
    AlreadyRemoving {
        /// Busy identity.
        id: ComponentId,
    },
    /// A dependency exists but has not reached Running.
    #[error("component {component} requires {required} to be Running, found {state:?}")]
    DependencyNotRunning {
        /// Component whose start was refused.
        component: ComponentId,
        /// Required component.
        required: ComponentId,
        /// Observed dependency state.
        state: LifecycleState,
    },
    /// A running component still depends on the removal target.
    #[error("running component {dependent} depends on {required}")]
    RunningDependent {
        /// Running dependent preventing removal.
        dependent: ComponentId,
        /// Requested removal target.
        required: ComponentId,
    },
    /// Hot-loaded bytecode carries `erlang:*` imports the scheduler composition
    /// left Deferred — dispatch would kill the process at first use.
    #[error(
        "component {id} module {module} defers built-in imports [{imports}]: the scheduler was \
         composed without a populated BIF registry; compose through \
         frame_core::composition::compose_scheduler, never bare Scheduler::with_services"
    )]
    DeferredBifImports {
        /// Component whose start was refused.
        id: ComponentId,
        /// Module whose committed import table carries the deferrals.
        module: String,
        /// Deferred imports in `erlang:name/arity` form.
        imports: String,
    },
    /// beamr refused or could not parse component bytecode.
    #[error("failed to load bytecode for component {id}: {detail}")]
    ModuleLoad {
        /// Component being started.
        id: ComponentId,
        /// beamr loader detail.
        detail: String,
    },
    /// A native supervisor or linked child could not be spawned.
    #[error("failed to spawn {process} for component {id}: {detail}")]
    Spawn {
        /// Component being started or restarted.
        id: ComponentId,
        /// Supervisor or child name.
        process: String,
        /// beamr spawn detail.
        detail: String,
    },
    /// A child did not answer its declared mailbox probe in time.
    #[error("component {id} child {child} liveness probe timed out")]
    LivenessTimeout {
        /// Component being started or probed.
        id: ComponentId,
        /// Child that did not answer.
        child: String,
    },
    /// Start failed after entering Starting; status remains Failed.
    #[error("component {id} failed during start: {reason:?}")]
    StartFailed {
        /// Failed identity.
        id: ComponentId,
        /// Observable reason retained in status.
        reason: FailureReason,
    },
    /// Ordered stop observed a non-normal or missing tombstone.
    #[error("component {id} failed during ordered stop: {reason:?}")]
    StopFailed {
        /// Component being stopped.
        id: ComponentId,
        /// Exact observed failure.
        reason: FailureReason,
    },
    /// A retained old module generation could not be safely purged.
    #[error("failed to purge module {module} for component {id}: {detail}")]
    ModulePurge {
        /// Component being removed.
        id: ComponentId,
        /// Module display name.
        module: String,
        /// beamr purge detail.
        detail: String,
    },
    /// The current module generation was absent when removal expected it.
    #[error("module {module} for component {id} was not present during delete")]
    ModuleDelete {
        /// Component being removed.
        id: ComponentId,
        /// Module display name.
        module: String,
    },
    /// Lookup still found code after deletion.
    #[error("module {module} for component {id} remained visible after delete")]
    ModuleStillLoaded {
        /// Component being removed.
        id: ComponentId,
        /// Module display name.
        module: String,
    },
    /// beamr refused a typed host command at supervisor mailbox admission.
    #[error("component {id} supervisor {command} command delivery failed: {source}")]
    CommandDelivery {
        /// Affected component.
        id: ComponentId,
        /// Stable command kind whose delivery was attempted.
        command: &'static str,
        /// Exact beamr mailbox admission refusal.
        #[source]
        source: MailboxSendError,
    },
    /// Host/supervisor mailbox protocol was violated.
    #[error("component {id} supervisor protocol failed: {detail}")]
    SupervisorProtocol {
        /// Affected component.
        id: ComponentId,
        /// Protocol detail.
        detail: String,
    },
    /// Internal synchronization was poisoned by a panic.
    #[error("component registry synchronization is poisoned")]
    SynchronizationPoisoned,
    /// A component monitor thread terminated without returning its result.
    #[error("component {id} monitor thread terminated unexpectedly")]
    MonitorTerminated {
        /// Component whose monitor was lost.
        id: ComponentId,
    },
}