ek_station/wire.rs
1//! svctl —— 宿主管控服务的线上契约(唯一提供方 `crate::supervisor::Supervisor`)。
2//!
3//! 契约形状(与 ek-dm/ek-ctp 的 ask 同构,三口都走 `ask_service`):
4//! - [`SVC_LIST`]:[`ListReq`] → `Vec<[`PlugRow`]>`(全插件快照一行一个)。
5//! - [`SVC_START`]:[`StartReq`] → [`StartRes`](`Started`/`Rejected` 两种业务回执,
6//! 不用 `ReplyErr` 承载业务错 —— 回执枚举在总线上可读可断言)。
7//! - [`SVC_REMOVE`]:[`RemoveReq`] → [`RemoveRes`](`Removed`/`NoSuchName`/`Rejected`
8//! 三种业务回执;`name` 栏一律保真,错因只在 `Rejected.reason`)。
9//!
10//! 语义钉(ek-core 0.3.0 能力面自然修正):宿主没有"停而不摘表"的导出,
11//! 唯一的运行期下线动作是 `remove_plugin`(五步闭停 + 摘表 + `ePlugDown` 广播),
12//! 故 stop 语义并入 [`SVC_REMOVE`];再次启用走 [`SVC_START`] 动态重新注入
13//! (宿主 add_plugin 在 start 后直接 `launch_one` 上线并广播 `ePlugUp`)。
14
15use ek_core::service::ServiceDesc;
16
17// ================ 服务 id(服务目录键,UI 侧 ask_service 用) ================
18
19/// 列出全部插件快照。
20pub const SVC_LIST: &str = "svctl.list";
21/// 按工厂名动态注入一个新插件(start 后直接上线)。
22pub const SVC_START: &str = "svctl.start";
23/// 摘除一个运行中/未装配插件(stop 语义并入;ek-core 0.3.0 无"停而不摘表"导出)。
24pub const SVC_REMOVE: &str = "svctl.remove";
25
26// ================ list ================
27
28/// 一行插件快照(观测页逐行渲染)。
29#[derive(Debug, Clone, PartialEq)]
30pub struct PlugRow {
31 /// 注册名。
32 pub name: String,
33 /// 申报版本(插件自填;空串 = 未声明)。
34 pub version: String,
35 /// 生命周期状态(Debug 形状字符串:Ready/Running/Stopping/Exited/Quarantined)。
36 pub state: String,
37 /// 显式依赖的插件名。
38 pub deps: Vec<String>,
39 /// 软顶触发的丢弃累计(0.5.0 口径:默认无界不丢,只有触到软顶才丢)。
40 pub spilled: u64,
41 /// panic 累计(连续超限 → Quarantined)。
42 pub panics: u64,
43 /// **当前邮箱深度**(0.5.0:积压读数;消费侧显示"刷新滞后"的原料)。
44 pub pending: u64,
45 /// 历史峰值(只涨不跌:这段最堵的时候留档)。
46 pub peak: u64,
47 /// 该插件自己的软顶(0.5.0:溢出判据必须吃**它自己的线**,消费侧不再自备容量表)。
48 pub soft_cap: u64,
49}
50
51/// 快照请求(无参)。
52#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
53pub struct ListReq;
54
55/// 快照回执(注册序)。
56pub type ListRes = Vec<PlugRow>;
57
58// ================ start ================
59
60/// 按工厂名注入请求。
61#[derive(Debug, Clone, PartialEq, Eq)]
62pub struct StartReq {
63 /// 工厂名(= [`crate::factory::PlugKind`] 的 name)。
64 pub factory: String,
65 /// 实例注册名(多账号/多实例面)。
66 ///
67 /// 空 = 沿用旧行为(工厂名即注册名,单实例场景零回归)。
68 /// 非空 = 要求工厂以这个名字建实例(工厂须登记 `build_named`);
69 /// 名字合法性仍由宿主 `valid_plugin_name` 把关。
70 pub name: String,
71}
72
73/// 注入回执:业务结果自带(成功给出实际注册名;失败给原因)。
74#[derive(Debug, Clone, PartialEq, Eq)]
75pub enum StartRes {
76 /// 已上线(宿主 add_plugin 成功,start 状态下立即装配)。
77 Started {
78 /// 插件注册名(= 插件自报的 name)。
79 name: String,
80 },
81 /// 拒绝(未知工厂/重名/名字非法 —— fix 后在 UI 侧红字呈现)。
82 Rejected {
83 /// 原因文本(Debug 形状,不进日志)。
84 reason: String,
85 },
86}
87
88// ================ remove ================
89
90/// 摘除请求(=ek-core `remove_plugin`;被依赖时宿主 stderr 警告,回执仍成功)。
91#[derive(Debug, Clone, PartialEq, Eq)]
92pub struct RemoveReq {
93 /// 注册名(非工厂名)。
94 pub name: String,
95}
96
97/// 摘除回执(三种业务回执,与 [`StartRes`] 同形;不用 `ReplyErr` 承载业务错)。
98#[derive(Debug, Clone, PartialEq, Eq)]
99pub enum RemoveRes {
100 /// 已摘(五步闭停 + 摘表 + 广播 `ePlugDown`)。
101 Removed {
102 /// 被摘注册名。
103 name: String,
104 },
105 /// 表内无此名(宿主 `PluginError::UnknownName`)。
106 NoSuchName {
107 /// 原请求名(照请求回填,这一栏**不放错误文本**)。
108 name: String,
109 },
110 /// 名字在场但不可摘,或宿主另有错(0.2.11 新增,BD-UI-64)。
111 ///
112 /// 为什么要有这一档:0.2.10 之前非 `UnknownName` 的宿主错被塞进
113 /// [`NoSuchName`](RemoveRes::NoSuchName),而那一栏的 `name` 文档写明是
114 /// 「原请求名」—— 把 `format!("{e:?}")` 塞进去就是契约违约(消费侧
115 /// 会把错误文本当名字打日志,原请求名丢失)。
116 /// 现:名字一律保真,错误进 `reason`。
117 Rejected {
118 /// 原请求名(与请求一致)。
119 name: String,
120 /// 原因文本(Debug 形状或管制面自报的口径句)。
121 reason: String,
122 },
123}
124
125// ================ 服务目录自报(supervisor.rs 用) ================
126
127/// Supervisor 的三口目录(与 `crate::supervisor::Supervisor::services` 同源)。
128pub(crate) fn svctl_services() -> Vec<ServiceDesc> {
129 vec![
130 ServiceDesc::describe::<ListReq, ListRes>(SVC_LIST, "station: list plugin rows"),
131 ServiceDesc::describe::<StartReq, StartRes>(SVC_START, "station: inject plugin by factory"),
132 ServiceDesc::describe::<RemoveReq, RemoveRes>(SVC_REMOVE, "station: remove plugin by name"),
133 ]
134}