ek-station 0.2.4

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
//! # factory —— 编译期插件工厂表(DV-2:v1 不做 dylib 动态加载)。
//!
//! 每个登记项 = 注册名 + 文档串 + `fn() -> Arc<dyn Plugin>` 零参构造。
//! 构造固定用生态缺省配置(`DmConfig::minute("dm")`/
//! `OmsPlugin::builder().risk_gates(ek_risk::standard_gates(..))`(0.1.7 装配
//! 口径)/`CtpPlugin::new(CtpConfig::default()` → 注册名 "ctp",native 特性关 =
//! 纯逻辑驱动);参数化配置进 S3 配置面(届时 build 签名升级为
//! `fn(&SectionMap) -> Result<...>`,本表只换实现不改面)。
//!
//! 0.1.12(BD-UI-38 用户裁定):纸面网关 "sim" 退场(纸面账户功能整体撤除,
//! ctp/SimNow 两套前置满足需求);历史见 CHANGELOG 0.1.8/0.1.9。

use ek_core::plugin::Plugin;
use ek_ctp::plugin::{CtpConfig, CtpPlugin};
use ek_dm::plugin::{DmConfig, DmPlugin};
use ek_oms::plugin::OmsPlugin;

/// 零参构造器(缺省路径:注册名 = 工厂名)。测试探针需要捕获共享槽,故用
/// `Arc<dyn Fn>`;零捕获闭包同样适配。
pub type Builder = std::sync::Arc<dyn Fn() -> std::sync::Arc<dyn Plugin> + Send + Sync>;

/// 命名构造器(多实例面:按调用方给的注册名造一个实例)。
pub type NamedBuilder = std::sync::Arc<dyn Fn(&str) -> std::sync::Arc<dyn Plugin> + Send + Sync>;

/// 一个可装配的插件种类。
#[derive(Clone)]
pub struct PlugKind {
    /// 工厂注册名(svctl.start 的 `factory` 参数;**不必然**等于插件注册名,
    /// 如多网关实例同工厂 —— 命名实例走 `build_named`)。
    pub name: &'static str,
    /// 一行用途说明(观测页提示用)。
    pub doc: &'static str,
    /// 零参构造。用于 `StartReq.name` 为空的缺省路径(单实例,注册名 = 工厂名)。
    pub build: Builder,
    /// 命名构造(多实例面:`StartReq.name` 非空时走这条)。
    ///
    /// `None` = 该工厂不支持命名实例(supervisor 直接回 `Rejected`,不静默退回
    /// 零参构造 —— 否则调用方以为建出了第二个实例,实际撞名)。
    pub build_named: Option<NamedBuilder>,
}

/// 缺省工厂表:ek 生态当前已发布插件的全部登记。
#[must_use]
pub fn default_kinds() -> Vec<PlugKind> {
    vec![
        PlugKind {
            name: "dm",
            doc: "数据管理器(KlineManager;eTick.→eBar. 单源批)",
            build: std::sync::Arc::new(|| {
                std::sync::Arc::new(DmPlugin::new(DmConfig::minute("dm")))
            }),
            build_named: None,
        },
        PlugKind {
            name: "oms",
            doc: "OMS 中枢(入账 + 查询 + 五段管线;地板三件 + ek-risk 四闸装配:合约预检 / 单笔手数 / 品种持仓限 / 价带)",
            build: std::sync::Arc::new(|| {
                // 0.1.7 装配口径:缺省限额(DESIGN ek-risk §6「不阻塞但保守」)。
                std::sync::Arc::new(
                    OmsPlugin::builder()
                        .risk_gates(ek_risk::standard_gates(&ek_risk::RiskLimits::default()))
                        .build(),
                )
            }),
            build_named: None,
        },
        PlugKind {
            name: "ctp",
            doc: "CTP 网关(native 关 = 纯逻辑驱动;账号经 {gw}.configure 手工注入,插件不读配置文件)",
            build: std::sync::Arc::new(|| build_ctp("")),
            // 多账号面:同一工厂按不同注册名建多个实例(名字由调用方派生)。
            build_named: Some(std::sync::Arc::new(build_ctp)),
        },
    ]
}

/// 以指定注册名建一个 ctp 实例(空名 = 缺省 `"ctp"`,单账号旧行为)。
///
/// 账号/前置一律留空:`connect` 空地址守卫跳过,等 `{gw}.configure` 手工注入
/// (0.3.0 D-CONFIGURE 定盘)。**凭据不进 start 通道**(密码只走 configure)。
fn build_ctp(name: &str) -> std::sync::Arc<dyn Plugin> {
    let cfg = CtpConfig {
        gateway_name: name.to_string(),
        ..CtpConfig::default()
    };
    // ctp-native 特性开 → 随包 SDK 传输 + 流目录 target/ctp-flow(按 gw 分子目录);
    // 关 → 纯逻辑驱动(现行为)。
    #[cfg(feature = "ctp-native")]
    {
        CtpPlugin::with_native(cfg, "target/ctp-flow")
    }
    #[cfg(not(feature = "ctp-native"))]
    {
        std::sync::Arc::new(CtpPlugin::new(cfg))
    }
}

/// 委托式包装:把异构的 `Arc<dyn Plugin>` 抬成 `add_plugin` 泛型点可用的具体类型
/// (ek-core 0.3.0 的 `impl Plugin` 是 `Sized` 泛型面 —— BD-U6:ek-core 已发布不可改)。
///
/// 纪律钉:`get_typed` 的 downcast 不穿透本包装(nt 语义拿内层裸 Arc),
/// 类型面一律走各插件自报的 ask 契约(生态铁律:插件不直调)。
pub struct DynPlugin {
    inner: std::sync::Arc<dyn Plugin>,
}

impl DynPlugin {
    /// 包装已建好的插件对象(直接给出 `Arc<Self>`,调用点即 add_plugin 形参)。
    pub fn wrap(inner: std::sync::Arc<dyn Plugin>) -> std::sync::Arc<Self> {
        std::sync::Arc::new(Self { inner })
    }
}

impl Plugin for DynPlugin {
    fn name(&self) -> &str {
        self.inner.name()
    }

    fn deps(&self) -> &[&str] {
        self.inner.deps()
    }

    fn routes(&self) -> Vec<std::borrow::Cow<'static, str>> {
        self.inner.routes()
    }

    fn mailbox_cap(&self) -> usize {
        self.inner.mailbox_cap()
    }

    fn version(&self) -> std::borrow::Cow<'static, str> {
        self.inner.version()
    }

    fn services(&self) -> Vec<ek_core::service::ServiceDesc> {
        self.inner.services()
    }

    fn on_init(&self, ctx: &ek_core::context::PluginContext) {
        self.inner.on_init(ctx);
    }

    fn on_start(&self) {
        self.inner.on_start();
    }

    fn on_stop(&self) {
        self.inner.on_stop();
    }

    fn on_mail(&self, event: &ek_event::Event) {
        self.inner.on_mail(event);
    }
}