ek-station 0.2.2

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
//! svctl —— 宿主管控服务的线上契约(唯一提供方 `crate::supervisor::Supervisor`)。
//!
//! 契约形状(与 ek-dm/ek-ctp 的 ask 同构,三口都走 `ask_service`):
//! - [`SVC_LIST`]:[`ListReq`] → `Vec<[`PlugRow`]>`(全插件快照一行一个)。
//! - [`SVC_START`]:[`StartReq`] → [`StartRes`](`Started`/`Rejected` 两种业务回执,
//!   不用 `ReplyErr` 承载业务错 —— 回执枚举在总线上可读可断言)。
//! - [`SVC_REMOVE`]:[`RemoveReq`] → [`RemoveRes`]。
//!
//! 语义钉(ek-core 0.3.0 能力面自然修正):宿主没有"停而不摘表"的导出,
//! 唯一的运行期下线动作是 `remove_plugin`(五步闭停 + 摘表 + `ePlugDown` 广播),
//! 故 stop 语义并入 [`SVC_REMOVE`];再次启用走 [`SVC_START`] 动态重新注入
//! (宿主 add_plugin 在 start 后直接 `launch_one` 上线并广播 `ePlugUp`)。

use ek_core::service::ServiceDesc;

// ================ 服务 id(服务目录键,UI 侧 ask_service 用) ================

/// 列出全部插件快照。
pub const SVC_LIST: &str = "svctl.list";
/// 按工厂名动态注入一个新插件(start 后直接上线)。
pub const SVC_START: &str = "svctl.start";
/// 摘除一个运行中/未装配插件(stop 语义并入;ek-core 0.3.0 无"停而不摘表"导出)。
pub const SVC_REMOVE: &str = "svctl.remove";

// ================ list ================

/// 一行插件快照(观测页逐行渲染)。
#[derive(Debug, Clone, PartialEq)]
pub struct PlugRow {
    /// 注册名。
    pub name: String,
    /// 申报版本(插件自填;空串 = 未声明)。
    pub version: String,
    /// 生命周期状态(Debug 形状字符串:Ready/Running/Stopping/Exited/Quarantined)。
    pub state: String,
    /// 显式依赖的插件名。
    pub deps: Vec<String>,
    /// forwarder 溢出丢弃累计(邮箱满即丢,统计面显式可见)。
    pub spilled: u64,
    /// panic 累计(连续超限 → Quarantined)。
    pub panics: u64,
}

/// 快照请求(无参)。
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct ListReq;

/// 快照回执(注册序)。
pub type ListRes = Vec<PlugRow>;

// ================ start ================

/// 按工厂名注入请求。
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StartReq {
    /// 工厂名(= [`crate::factory::PlugKind`] 的 name)。
    pub factory: String,
    /// 实例注册名(多账号/多实例面)。
    ///
    /// 空 = 沿用旧行为(工厂名即注册名,单实例场景零回归)。
    /// 非空 = 要求工厂以这个名字建实例(工厂须登记 `build_named`);
    /// 名字合法性仍由宿主 `valid_plugin_name` 把关。
    pub name: String,
}

/// 注入回执:业务结果自带(成功给出实际注册名;失败给原因)。
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum StartRes {
    /// 已上线(宿主 add_plugin 成功,start 状态下立即装配)。
    Started {
        /// 插件注册名(= 插件自报的 name)。
        name: String,
    },
    /// 拒绝(未知工厂/重名/名字非法 —— fix 后在 UI 侧红字呈现)。
    Rejected {
        /// 原因文本(Debug 形状,不进日志)。
        reason: String,
    },
}

// ================ remove ================

/// 摘除请求(=ek-core `remove_plugin`;被依赖时宿主 stderr 警告,回执仍成功)。
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RemoveReq {
    /// 注册名(非工厂名)。
    pub name: String,
}

/// 摘除回执。
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RemoveRes {
    /// 已摘(五步闭停 + 摘表 + 广播 `ePlugDown`)。
    Removed {
        /// 被摘注册名。
        name: String,
    },
    /// 表内无此名。
    NoSuchName {
        /// 原请求名。
        name: String,
    },
}

// ================ 服务目录自报(supervisor.rs 用) ================

/// Supervisor 的三口目录(与 `crate::supervisor::Supervisor::services` 同源)。
pub(crate) fn svctl_services() -> Vec<ServiceDesc> {
    vec![
        ServiceDesc::describe::<ListReq, ListRes>(SVC_LIST, "station: list plugin rows"),
        ServiceDesc::describe::<StartReq, StartRes>(SVC_START, "station: inject plugin by factory"),
        ServiceDesc::describe::<RemoveReq, RemoveRes>(SVC_REMOVE, "station: remove plugin by name"),
    ]
}