frame-host 0.1.0

Frame host server — boots the frame-core host authority with an embedded liminal component and serves the built frame page
Documentation
//! Typed failures for every host boot, serve, and shutdown path.

use std::net::SocketAddr;
use std::path::PathBuf;

use frame_core::capability::CapabilityMutationError;
use frame_core::component::ComponentId;
use frame_core::error::RegistryError;
use frame_core::event::LifecycleState;
use thiserror::Error;

/// A typed failure from the demo host.
#[derive(Debug, Error)]
pub enum HostError {
    /// Scheduler composition (BIF population + construction) failed.
    #[error("scheduler composition failed")]
    Composition {
        /// Exact typed composition refusal.
        #[source]
        source: frame_core::composition::SchedulerCompositionError,
    },
    /// The component registry refused or failed an operation.
    #[error("component registry operation failed")]
    Registry(#[from] RegistryError),
    /// The host capability facade refused an operation.
    #[error("host capability operation failed")]
    Capability(#[from] CapabilityMutationError),
    /// The demo component vanished from the registry between operations.
    #[error("component {id} has no status snapshot in the registry")]
    StatusMissing {
        /// Identity whose snapshot was absent.
        id: ComponentId,
    },
    /// The demo component did not reach Running after start.
    #[error("component {id} is {state:?} after start instead of Running")]
    NotRunning {
        /// Identity that failed to reach Running.
        id: ComponentId,
        /// Observed lifecycle state.
        state: LifecycleState,
    },
    /// The presence child answered its liveness probe with the wrong value.
    #[error("presence child answered liveness probe with {actual}, expected {expected}")]
    ProbeMismatch {
        /// Value the fresh incarnation must answer.
        expected: i64,
        /// Value actually observed.
        actual: i64,
    },
    /// The lifecycle event logger thread could not be spawned.
    #[error("failed to spawn lifecycle event logger thread: {source}")]
    EventLoggerSpawn {
        /// Underlying spawn failure.
        #[source]
        source: std::io::Error,
    },
    /// The lifecycle event logger thread panicked.
    #[error("lifecycle event logger thread panicked")]
    EventLoggerPanicked,
    /// The asset directory is unusable as a shell root.
    #[error("asset directory {path} is unusable: {detail}")]
    AssetRoot {
        /// Configured asset directory.
        path: PathBuf,
        /// Exact refusal detail.
        detail: String,
    },
    /// The served config would be refused by the shell's config contract.
    #[error("shell config contract violation: {detail}")]
    ConfigContract {
        /// Exact contract refusal and the flag that fixes it.
        detail: String,
    },
    /// The asset directory has no `index.html` shell entry point.
    #[error(
        "asset directory {path} has no index.html; refusing to serve a shell with no entry point"
    )]
    MissingIndex {
        /// Configured asset directory.
        path: PathBuf,
    },
    /// Binding the shell listener failed.
    #[error("failed to bind shell server on {addr}: {source}")]
    Bind {
        /// Requested socket address.
        addr: SocketAddr,
        /// Underlying bind failure.
        #[source]
        source: std::io::Error,
    },
    /// The shell server failed while serving.
    #[error("shell server failed: {source}")]
    Serve {
        /// Underlying accept/serve failure.
        #[source]
        source: std::io::Error,
    },
    /// The async runtime hosting the shell server could not be built.
    #[error("failed to build tokio runtime for the shell server: {source}")]
    AsyncRuntime {
        /// Underlying builder failure.
        #[source]
        source: std::io::Error,
    },
    /// The shutdown signal handler could not be installed or failed.
    #[error("shutdown signal handling failed: {source}")]
    ShutdownSignal {
        /// Underlying signal failure.
        #[source]
        source: std::io::Error,
    },
    /// Ordered shutdown left live processes on the scheduler.
    #[error("ordered stop leaked {count} live scheduler process(es)")]
    ProcessResidue {
        /// Processes still alive after the ordered drain.
        count: usize,
    },
    /// Host-internal synchronization was poisoned by a panic.
    #[error("host synchronization is poisoned")]
    SynchronizationPoisoned,
    /// The frame configuration file could not be read.
    #[error("failed to read frame config {path}: {source}")]
    ConfigRead {
        /// Configured frame.toml path.
        path: PathBuf,
        /// Underlying read failure.
        #[source]
        source: std::io::Error,
    },
    /// The frame configuration file could not be parsed as TOML into the
    /// `[frame]` + `[liminal]` schema.
    #[error("failed to parse frame config {path}: {detail}")]
    ConfigParse {
        /// Configured frame.toml path.
        path: PathBuf,
        /// Exact TOML/deserialization refusal.
        detail: String,
    },
    /// The embedded `[liminal]` config failed liminal's own validation.
    #[error("embedded liminal config is invalid: {source}")]
    LiminalConfig {
        /// Exact typed liminal validation failure.
        #[source]
        source: liminal_server::ServerError,
    },
    /// The `[liminal]` config selects a shape the embedded frame server does
    /// not faithfully orchestrate (cluster, worker-front-door, or an absent
    /// WebSocket transport). Loud refusal at startup, never a silent downgrade.
    #[error("embedded frame mode does not support this liminal shape: {detail}")]
    EmbeddedModeUnsupported {
        /// Which shape was refused and why.
        detail: String,
    },
    /// A liminal component failed to bind or boot. Frame-host exits nonzero
    /// with the component named — never a half-up stack.
    #[error("embedded liminal component '{component}' failed to boot: {source}")]
    LiminalComponent {
        /// Which liminal component failed (health endpoint, connection
        /// services, connection supervisor, TCP listener, WebSocket listener).
        component: &'static str,
        /// Exact typed liminal failure.
        #[source]
        source: liminal_server::ServerError,
    },
    /// Liminal's graceful shutdown sequence failed.
    #[error("embedded liminal graceful shutdown failed: {source}")]
    LiminalShutdown {
        /// Exact typed liminal shutdown failure.
        #[source]
        source: liminal_server::ServerError,
    },
    /// The embedded liminal component stopped answering at runtime while the
    /// host was still meant to be serving. A dead server behind a healthy host
    /// is forbidden: frame-host tears down and exits nonzero.
    #[error("embedded liminal component terminated unexpectedly at runtime: {detail}")]
    LiminalExited {
        /// Which liveness probe failed and against which address.
        detail: String,
    },
}