ek-station 0.4.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
//! # cfgload —— 配置托底的载入单点(BD-CFG-1/6/16/20;2026-10-25)。
//!
//! boot(`boot_with_cfg`)与 svctl.start(`supervisor::start_by`)**共用**本模块
//! 的 [`apply_persisted`] —— 同一探查/载入/回灌/警告面,两处调用字面同一道。
//!
//! 语义(钉齐):
//! - 探查 [`Plugin::as_configurable`]:`None` = 无配置面,零开销略过;
//! - `Some`:`Store::load(ns)` → `Fresh` = 冷启动零动;`Loaded` → 警告 stderr
//!   显形(宿主 0.3.0 零总线日志纪律;载入是管理线程一次性动作)→
//!   `apply_config` 直调回灌(**在 on_start 之前**;解析/回灌失败 = Err 上抛,
//!   对齐 BD-U4「格式错即时报」的单一硬报错点);
//! - **秘面只回灌引用**(`apply_config` 禁取秘,BD-CFG-16);
//! - 双文件缺(cuurrent+.bak)才会 Fresh,残档回落自带 warning 明细(BD-CFG-4)。

use ek_cfg::Store;
use ek_core::plugin::Plugin;
use std::sync::Arc;

/// 探查-载入-回灌(单点;失败即报,不静默)。
///
/// # Errors
/// - [`crate::station::StationError::Cfg`]:载入 IO/格式(无 `.bak` 兜底)错误;
/// - [`crate::station::StationError::ApplyRejected`]:插件 `apply_config` 拒收该段。
pub fn apply_persisted(
    plugin: &dyn Plugin,
    store: Option<&Store>,
) -> Result<(), crate::station::StationError> {
    let Some(cfg) = plugin.as_configurable() else {
        return Ok(()); // 无配置面:零负担
    };
    let Some(store) = store else {
        return Ok(()); // 调用方未启用配置回灌(boot() 纯装配语义)
    };
    let ns = cfg.config_ns();
    let outcome = store
        .load(ns)
        .map_err(|e| crate::station::StationError::Cfg(e.to_string()))?;
    let (map, warnings) = match outcome {
        ek_cfg::LoadOutcome::Fresh => return Ok(()), // 冷启动 = 构造缺省
        ek_cfg::LoadOutcome::Loaded { map, warnings, .. } => (map, warnings),
    };
    for w in &warnings {
        eprintln!("[ek-cfg:{ns}] {w}");
    }
    let rep = cfg
        .apply_config(&map)
        .map_err(|e| crate::station::StationError::ApplyRejected {
            ns: ns.to_owned(),
            reason: e,
        })?;
    if !rep.deferred.is_empty() {
        eprintln!(
            "[ek-cfg:{ns}] 同步采纳 {},重启插件后生效 {}(BD-CFG-6)",
            fmt_keys(&rep.adopted),
            fmt_keys(&rep.deferred)
        );
    }
    Ok(())
}

/// ns 撞名检查(boot 的全表预筛;命名实例形态 `<工厂>.<注册名>` 与单段
/// 缺省名同一位空间 —— BD-CFG 段唯一性)。
///
/// # Errors
/// [`crate::StationError::NsDuplicate`]:两插件同 ns。
pub fn ns_probe_all(built: &[Arc<dyn Plugin>]) -> Result<(), crate::station::StationError> {
    let mut seen = std::collections::BTreeSet::new();
    for p in built {
        if let Some(cfg) = p.as_configurable() {
            let ns = cfg.config_ns();
            if !seen.insert(ns.to_owned()) {
                return Err(crate::station::StationError::NsDuplicate(ns.to_owned()));
            }
        }
    }
    Ok(())
}

fn fmt_keys(keys: &[String]) -> String {
    if keys.is_empty() {
        "(无)".into()
    } else {
        keys.join(",")
    }
}