ek-station 0.2.1

Station for the ek ecosystem: headless plugin host bootstrap - assembles ek-core MainEngine from a compile-time plugin factory table, runs the svctl supervisor service (list/start/remove over bus ask) and a headless main (readiness marker + graceful close).
Documentation
//! # station —— 装配器:`StationConfig` → Supervisor + 插件 → 待 starter。
//!
//! boot 只做装配(**不调 start**):调用方在这窗口可挂探针/额外插件,
//! 然后自己 `MainEngine::start()`(宿主按 deps 拓扑序装配并广播 `ePlugUp`)。
//! headless main 与整套测试都走这一条路径 —— 装配时序在一条代码路径上,
//! "launcher 勾选启动 = headless 同一逻辑"是字面成立的(方案 DV-5)。

use std::sync::Arc;

use ek_core::error::PluginError;
use ek_core::main_engine::MainEngine;

use crate::factory::PlugKind;
use crate::supervisor::Supervisor;

/// 站配置(纯清单,定盘口径:宿主零配置 —— 不读配置文件,需配置的插件其
/// 配置面归插件自身,见 ek-ui DESIGN BD-UI-19)。
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct StationConfig {
    /// 启用的工厂名清单(svctl 自身不在此列,由 boot 自动装配)。
    pub plugins: Vec<String>,
}

impl StationConfig {
    /// 演示清单:dm + oms(纯逻辑,不连任何真实通道)。
    #[must_use]
    pub fn demo() -> Self {
        Self {
            plugins: vec!["dm".to_string(), "oms".to_string()],
        }
    }
}

/// 装配期错误(硬报错给 main;tests 逐类断言)。
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum StationError {
    /// 请求的工厂不在表内(DV-2:编译期表,拼写错启动即红)。
    UnknownKind(String),
    /// 宿主拒绝(重名 `Duplicate`/名字非法 `IllegalName`/已闭停 `Stopped`)。
    Core(PluginError),
}

impl std::fmt::Display for StationError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            StationError::UnknownKind(k) => write!(f, "unknown factory kind: {k}"),
            StationError::Core(e) => write!(f, "host rejected: {e:?}"),
        }
    }
}

impl std::error::Error for StationError {}

impl From<PluginError> for StationError {
    fn from(e: PluginError) -> Self {
        StationError::Core(e)
    }
}

/// 装配监督者(`svctl`)与启用清单;调用方随后 `engine.start()`。
///
/// # Errors
/// 未知工厂名([`StationError::UnknownKind`]);宿主拒绝(重名等,透传)。
pub fn boot(
    engine: &Arc<MainEngine>,
    kinds: &[PlugKind],
    cfg: &StationConfig,
) -> Result<(), StationError> {
    let sup = Arc::new(Supervisor::new(Arc::clone(engine), kinds.to_vec()));
    engine.add_plugin(sup)?;
    for name in &cfg.plugins {
        let kind = kinds
            .iter()
            .find(|k| k.name == *name)
            .ok_or_else(|| StationError::UnknownKind(name.clone()))?;
        engine.add_plugin(crate::factory::DynPlugin::wrap((kind.build)()))?;
    }
    Ok(())
}