frame-host 0.4.0

Frame host server and embedding seam — boots an application's frame-core component tree with an embedded liminal bus, announces the host's real application events on the bus, and serves the built frame page
Documentation
//! The real [`NodeControl`]: the barrier's steps executed against the
//! live frame-core registry and runtime.
//!
//! On the SUCCESS path the reload is remove-free (constraint 5): the same
//! registration is stopped, re-staged through the registry's purge-aware
//! seam, and restarted — identity, metadata, grants, and the durable
//! branch are the same objects across incarnations. The ONE exceptional
//! path is rollback out of `Failed`: the staging seam legally refuses a
//! Failed component, so recovery goes remove → re-register with the SAME
//! identity and metadata — the branch and the per-identity incarnation
//! history both survive remove by design (F-5a), so even this path keeps
//! the fence and the freshness witnesses intact.

use std::sync::Arc;

use frame_core::component::{ComponentId, ComponentMeta};
use frame_core::error::RegistryError;
use frame_core::event::LifecycleState;
use frame_core::registry::ComponentRegistry;
use frame_core::runtime::{SupportModuleRef, SupportSwapper};

use super::{CandidateBytes, GenerationReport, NodeControl, StartFailure};

/// One liveness witness against the live registry — the application
/// supplies its own (the generated host's mailbox probe and entity
/// round-trip), frame-host never invents one.
pub type Witness = Box<dyn FnMut(&ComponentRegistry) -> Result<(), String> + Send>;

/// The barrier's node seam over the real registry and runtime.
pub struct DevNodeControl {
    registry: Arc<ComponentRegistry>,
    swapper: SupportSwapper,
    component: ComponentId,
    meta: ComponentMeta,
    support: SupportModuleRef,
    witness_mailbox: Witness,
    witness_content: Witness,
}

impl DevNodeControl {
    /// A control seam for one component on one node.
    #[must_use]
    pub fn new(
        registry: Arc<ComponentRegistry>,
        swapper: SupportSwapper,
        meta: ComponentMeta,
        support: SupportModuleRef,
        witness_mailbox: Witness,
        witness_content: Witness,
    ) -> Self {
        Self {
            registry,
            swapper,
            component: meta.id,
            meta,
            support,
            witness_mailbox,
            witness_content,
        }
    }

    fn state(&self) -> Result<LifecycleState, String> {
        self.registry
            .status(self.component)
            .map_err(|error| error.to_string())?
            .map(|status| status.state)
            .ok_or_else(|| "component is not registered".to_owned())
    }
}

impl NodeControl for DevNodeControl {
    fn stop_component(&mut self) -> Result<(), String> {
        self.registry
            .stop(self.component)
            .map(|_| ())
            .map_err(|error| error.to_string())
    }

    fn stage(&mut self, candidate: &CandidateBytes) -> Result<(), String> {
        match self.state()? {
            // Rollback out of Failed: the staging seam refuses Failed by
            // law, so recovery re-registers the SAME identity and meta.
            // Branch and incarnation history survive remove (F-5a).
            LifecycleState::Failed => {
                self.registry
                    .remove(self.component)
                    .map_err(|error| format!("recovering failed registration: {error}"))?;
                self.registry
                    .register(self.meta.clone(), candidate.component_beam.clone())
                    .map_err(|error| format!("re-registering after failure: {error}"))?;
            }
            _ => {
                self.registry
                    .upgrade_bytecode(self.component, candidate.component_beam.clone())
                    .map_err(|error| error.to_string())?;
            }
        }
        self.support = self
            .swapper
            .swap(&self.support, &candidate.ffi_beam)
            .map_err(|error| format!("support module swap: {error}"))?;
        Ok(())
    }

    fn start_component(&mut self) -> Result<(), StartFailure> {
        self.registry
            .start(self.component)
            .map_err(|error| match error {
                RegistryError::ModuleLoad { .. } | RegistryError::DeferredBifImports { .. } => {
                    StartFailure::Load(error.to_string())
                }
                other => StartFailure::Tree(other.to_string()),
            })
    }

    fn witness_mailbox(&mut self) -> Result<(), String> {
        (self.witness_mailbox)(&self.registry)
    }

    fn witness_content(&mut self) -> Result<(), String> {
        (self.witness_content)(&self.registry)
    }

    fn report(
        &mut self,
        build_generation: u64,
        content_digest: [u8; 32],
    ) -> Result<GenerationReport, String> {
        Ok(GenerationReport {
            build_generation,
            content_digest,
            component_incarnation: self
                .registry
                .component_incarnation(self.component)
                .map_err(|error| format!("incarnation witness unreadable: {error}"))?,
            module_generation: self
                .registry
                .component_module_generation(self.component)
                .map_err(|error| format!("module generation witness unreadable: {error}"))?,
        })
    }
}