ek-station 0.2.13

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
# ek-station DESIGN


设计与决策记录(docs/ek-ui-station-plan.md 的 S1a 落地面;扎钉后不放)。

## BD(边界钉)


- **BD-U1 svctl.stop ≅ svctl.remove**:ek-core 0.3.0 无"停而不摘表"导出
  (`close_one` 是私有方法,公开面只有 `close_all`/`remove_plugin`)。
  运行期下线 = remove(五步闭停 + 摘表 + `ePlugDown` 广播);重启用 start
  (宿主 `add_plugin` 在 start 后即时 `launch_one` 上线 + `ePlugUp` 广播)。
  测试 c0 全链钉死。
- **BD-U2 工厂 build 用 `Arc<dyn Fn>` 而非 `fn` 指针**:测试探针需要捕获
  共享槽;S3 参数化配置也吃这个签名。行动面不变(零参调用)。
- **BD-U3 headless 的 stdin 退出策略**:Windows 控制台无 std 信号面,
  `SetConsoleCtrlHandler` 需三方/unsafe → v1 记录:`q`/`quit` 行 = 优雅退出。
  ~~管道 EOF = 优雅退出~~ **0.1.4 文档纠错(T2 t2g 实测推翻)**:EOF 与 stdin
  不可用(detached)同归**常驻 keep-alive**——launcher 的 spawn 用 stdin=null,
  子进程起手即 EOF,EOF 若走优雅退则 launcher 协议即碎。五步闭停在 `q` 与
  窗口关闭(ek-ui 侧)路径生效;常驻进程由进程外 kill 终止。
  **0.2.12 补两件事(审核 P12 的出口,不改默认)**:① 这一档从此有测试钉 ——
  `headless_exit.rs` 的 t4 钉「EOF 之后进程必须还活着」、t5 钉「`--exit-on-eof`
  走优雅闭停」。此前四个测试全喂 `q\n`,EOF 分支从未被跑过:谁把它改成优雅退,
  四个测试照绿而 launcher 协议当场碎,0.1.4 那条实测纠正从此没有守卫。
  ② 新增 `--exit-on-eof` 开关(缺省**不开**,常驻仍是默认):手跑 `ek-station < NUL`
  或在管道里跑时,缺省留下的是持有 CTP 会话的孤儿进程,这条给一个优雅出口。
  常驻本身仍无超时位、无退出条件 —— 0.2.11 审核把它记为按裁定保留,不加 `--keep-alive`。
- **BD-U4 `--from` 清单延迟校验**:行格式错即时报(exit 2),未知工厂名
  延迟到 boot 单一硬报错点(exit 1)——避免两套重名/存在性校验的重复面。
- **BD-U5 构造期身份(多账号面已改写)**:原 v1 口径"工厂名 == 插件注册名
  (dm/oms/ctp 各一实例)"**已废**。`StartReq.name` 非空 = 以该注册名建实例
  (工厂须登记 `PlugKind.build_named`;ctp 已登记,dm/oms 未登记 → 显式回拒,
  不静默退回零参构造);空 = 旧行为(工厂名即注册名,单实例零回归)。
  `StartRes.Started.name` 一律回带**插件自报的 `name()`**,不再假设工厂名。
  网关注册名由 (broker, 账号) 派生(`ek_ctp::derived_gateway_name`),
  **不进配置文件**。

- **BD-U6 管制面不可自摘(0.2.11,应用级审核 P1)**:`svctl.remove(name = "svctl")`
  一律回 `RemoveRes::Rejected`。两条理由:① `Supervisor` 是全系统唯一持
  `Arc<MainEngine>` 的插件,而工厂表(`default_kinds`/ek-ui 的 `ui_kinds`)都不登记
  "svctl" 这一项 —— 摘掉之后 `svctl.start` 也只会回 `unknown factory: svctl`,
  启停面**没有恢复入口**(单向门);② 请求在本插件自己的 worker 里处理,摘自己
  = `close_one` 等自己排空,只能走到 ek-core 那 2 秒超时的 detach 分支,回包也发不出。
  钉在测试 c0 step5/6(拒 + 拒后 svctl 仍在场)。观测面 `ui` 的同一道闸长在 ek-ui 侧
  (0.4.3):那条指令正是 ui 自己的 worker 在执行,运维窗不给它画移除钮。
- **BD-U7 回执 `name` 栏只放原请求名(0.2.11,审核 P2)**:0.2.10 之前非
  `UnknownName` 的宿主错被塞进 `NoSuchName { name: format!("{e:?}") }`,而该栏文档
  写明是「原请求名」—— 消费侧(ek-ui 日志)会把错误文本当名字打,原请求名丢失。
  现:新增 `Rejected { name, reason }`,名字一律照请求回填,错因只进 `reason`。

## 契约面(wire.rs)


`PlugRow { name, version, state(Debug 形状), deps, spilled, panics }`;
`StartRes/RemoveRes` 用**业务回执枚举**(Started/Rejected、Removed/NoSuchName/Rejected)
而非 `ReplyErr` 承载业务错 —— 回执在总线/测试里可读可断言。