Skip to main content

vigil_ui_protocol/
command.rs

1//! `UiCommand`:typed 枚举协议(ADR 0008 §D3)。
2//!
3//! 框架无关:CLI(I08a)/ Tauri(I08b)/ 未来 Web / 测试 harness 都可复用同一个枚举。
4//!
5//! Capability 模型(ADR 0008 §D4 / §I-8.4):
6//! - 读命令 → `Capability::Read`
7//! - 写命令 → `Capability::Write`
8//! - 调用方必须先调 `required_capability()` 做静态配额检查
9
10use serde::{Deserialize, Serialize};
11use vigil_types::ApprovalScope;
12
13/// 每条命令所需的权限级别。
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
15#[serde(rename_all = "lowercase")]
16pub enum Capability {
17    /// 只读命令(查询 Ledger 不改状态)
18    Read,
19    /// 写命令(改 approval / drift / sandbox profile 绑定等)
20    Write,
21}
22
23/// UI 层的所有命令。每个变种带自己的 payload struct。
24#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
25#[serde(tag = "op", content = "args")]
26#[non_exhaustive]
27pub enum UiCommand {
28    // --- Activity / Audit ---
29    /// 列最近的 AuditEvents(可选 session / 类型过滤)
30    ListRecentEvents(ListRecentEventsReq),
31    /// 按 event_id 拿单条详细 payload
32    GetEventDetail(GetEventDetailReq),
33    /// FTS5 搜索 redacted_text
34    FtsSearch(FtsSearchReq),
35
36    // --- Approval Queue ---
37    /// 列 Pending 状态的 approvals
38    ListPendingApprovals(ListPendingApprovalsReq),
39    /// 拿某 approval 的完整细节(effect vector + decision)
40    GetApprovalDetail(GetApprovalDetailReq),
41    /// 批准 / 拒绝 / 取消
42    ResolveApproval(ResolveApprovalReq),
43
44    // --- Privacy Findings(ISS-017,Stage 3 wave-4)---
45    /// 全局 Privacy Findings 聚合视图(label × count + 最近 scans 列表)
46    ListPrivacyFindings(ListPrivacyFindingsReq),
47
48    // --- Session Replay ---
49    /// 列所有 sessions
50    ListSessions(ListSessionsReq),
51    /// 重放某 session(结构化事件 + 可选 verify_chain)
52    ReplaySession(ReplaySessionReq),
53    /// 单独触发 hash chain verify
54    VerifyChain,
55    /// ISS-018 — Safe Export:把 session replay 渲染为 MD / HTML 文本,
56    /// **payload 已在 events 入库时由 vigil-redaction 脱敏**,渲染层只读不改;
57    /// 输出 content 由 caller 触发浏览器 download。
58    ExportSessionReplay(ExportSessionReplayReq),
59
60    // --- Server Registry ---
61    /// 列已登记的 servers
62    ListServers,
63    /// 取某 server 的 onboarding 数据(transport / argv / env keys)
64    GetServerOnboarding(GetServerOnboardingReq),
65    /// 列首次待批准的 tool descriptor
66    ListPendingToolApprovals,
67    /// 列已 drift 的 tools
68    ListDriftedTools,
69    /// 列已 drift 的 servers
70    ListDriftedServers,
71    /// 首次批准 tool descriptor
72    ApproveTool(ApproveToolReq),
73    /// drift 后批准到新 hash
74    ApproveToolDrift(ApproveToolDriftReq),
75    /// drift reject(保留旧 hash)
76    RejectToolDrift(RejectToolDriftReq),
77    /// server command drift 批准
78    ApproveServerCommandDrift(ApproveServerCommandDriftReq),
79    /// server command drift 拒绝
80    RejectServerCommandDrift(RejectServerCommandDriftReq),
81
82    // --- SandboxProfile(I07 延后项) ---
83    /// 列所有 sandbox profiles
84    ListSandboxProfiles,
85    /// 按 id 取 profile
86    GetSandboxProfile(GetSandboxProfileReq),
87    /// 新建或覆盖 profile(写命令)
88    UpsertSandboxProfile(UpsertSandboxProfileReq),
89    /// 绑定 server → profile
90    BindServerSandboxProfile(BindServerSandboxProfileReq),
91}
92
93impl UiCommand {
94    /// 返回本命令要求的权限级别(ADR §I-8.4)。
95    pub fn required_capability(&self) -> Capability {
96        use UiCommand::*;
97        match self {
98            // Read
99            ListRecentEvents(_)
100            | GetEventDetail(_)
101            | FtsSearch(_)
102            | ListPendingApprovals(_)
103            | GetApprovalDetail(_)
104            | ListPrivacyFindings(_)
105            | ListSessions(_)
106            | ExportSessionReplay(_)
107            | ReplaySession(_)
108            | VerifyChain
109            | ListServers
110            | GetServerOnboarding(_)
111            | ListPendingToolApprovals
112            | ListDriftedTools
113            | ListDriftedServers
114            | ListSandboxProfiles
115            | GetSandboxProfile(_) => Capability::Read,
116            // Write
117            ResolveApproval(_)
118            | ApproveTool(_)
119            | ApproveToolDrift(_)
120            | RejectToolDrift(_)
121            | ApproveServerCommandDrift(_)
122            | RejectServerCommandDrift(_)
123            | UpsertSandboxProfile(_)
124            | BindServerSandboxProfile(_) => Capability::Write,
125        }
126    }
127}
128
129// ---------------- Payload structs ----------------
130
131/// ListRecentEvents 参数。
132#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
133pub struct ListRecentEventsReq {
134    /// 只看某个 session(None = 所有)
135    pub session_id: Option<String>,
136    /// 事件类型过滤(None = 全部)
137    pub event_type_filter: Option<Vec<String>>,
138    /// 返回上限
139    pub limit: u32,
140}
141
142/// GetEventDetail 参数。
143#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
144pub struct GetEventDetailReq {
145    /// events.event_id
146    pub event_id: i64,
147}
148
149/// FtsSearch 参数。
150#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
151pub struct FtsSearchReq {
152    /// FTS5 MATCH 语法
153    pub query: String,
154    /// 结果上限
155    pub limit: u32,
156}
157
158/// ListPendingApprovals 参数。
159#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
160pub struct ListPendingApprovalsReq {
161    /// 可选 session 过滤
162    pub session_id: Option<String>,
163}
164
165/// ISS-018 — Safe Export 输出格式。
166///
167/// MD / HTML 在本 phase 实装;PDF 留 phase 2(需 Rust PDF 库或浏览器侧 print)。
168#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
169#[serde(rename_all = "lowercase")]
170pub enum ExportFormat {
171    /// Markdown(`.md`)— 文本可读,易于审计员粘贴 / diff
172    Md,
173    /// HTML(`.html`)— 含 inline CSS,适合直接在浏览器打开预览
174    Html,
175}
176
177impl ExportFormat {
178    /// 返回该格式的 MIME type(浏览器 download 用)
179    pub fn mime(&self) -> &'static str {
180        match self {
181            ExportFormat::Md => "text/markdown; charset=utf-8",
182            ExportFormat::Html => "text/html; charset=utf-8",
183        }
184    }
185    /// 返回该格式的文件扩展名(不含 `.`)
186    pub fn extension(&self) -> &'static str {
187        match self {
188            ExportFormat::Md => "md",
189            ExportFormat::Html => "html",
190        }
191    }
192}
193
194/// ISS-018 — ExportSessionReplay 参数。
195#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
196pub struct ExportSessionReplayReq {
197    /// 要导出的 session id
198    pub session_id: String,
199    /// 输出格式
200    pub format: ExportFormat,
201}
202
203/// ListPrivacyFindings 参数(ISS-017)。
204#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
205pub struct ListPrivacyFindingsReq {
206    /// 最近 scans 的返回上限。0 → ledger 用 50 默认;最大被 ledger clamp 到 500。
207    /// caller 通常传 50-100 给 UI。
208    pub limit_recent_scans: u32,
209}
210
211/// GetApprovalDetail 参数。
212#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
213pub struct GetApprovalDetailReq {
214    /// approval id
215    pub approval_id: String,
216}
217
218/// ResolveApproval 参数。
219#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
220pub struct ResolveApprovalReq {
221    /// approval id
222    pub approval_id: String,
223    /// 动作
224    pub action: ApprovalAction,
225    /// 批准时的 scope(Once / ThisSession);Deny/Cancel 时忽略
226    pub scope: Option<ApprovalScope>,
227    /// 解析人(审计 payload)
228    pub resolved_by: String,
229    /// Deny 时的可选原因(纯文本,已脱敏)
230    pub reason: Option<String>,
231}
232
233/// Resolve approval 的动作。
234#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
235#[serde(rename_all = "lowercase")]
236pub enum ApprovalAction {
237    /// 批准
238    Approve,
239    /// 拒绝
240    Deny,
241    /// 取消(用户主动撤)
242    Cancel,
243}
244
245/// ListSessions 参数。
246#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
247pub struct ListSessionsReq {
248    /// source 过滤(mcp_hub / desktop / ...)
249    pub source: Option<String>,
250    /// 返回上限
251    pub limit: u32,
252}
253
254/// ReplaySession 参数。
255#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
256pub struct ReplaySessionReq {
257    /// session id
258    pub session_id: String,
259    /// 是否同时执行 verify_chain
260    pub verify: bool,
261}
262
263/// GetServerOnboarding 参数。
264#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
265pub struct GetServerOnboardingReq {
266    /// server id
267    pub server_id: String,
268}
269
270/// ApproveTool 参数(首次批准)。
271#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
272pub struct ApproveToolReq {
273    /// server id
274    pub server_id: String,
275    /// tool name(namespace 内的裸名,不含 `__`)
276    pub tool_name: String,
277}
278
279/// ApproveToolDrift 参数。
280#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
281pub struct ApproveToolDriftReq {
282    /// server id
283    pub server_id: String,
284    /// tool name
285    pub tool_name: String,
286    /// drift 后的新 hash(必须等于当前 pending_hash,否则 registry 层拒)
287    pub new_hash: String,
288}
289
290/// RejectToolDrift 参数。
291#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
292pub struct RejectToolDriftReq {
293    /// server id
294    pub server_id: String,
295    /// tool name
296    pub tool_name: String,
297}
298
299/// ApproveServerCommandDrift 参数。
300#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
301pub struct ApproveServerCommandDriftReq {
302    /// server id
303    pub server_id: String,
304}
305
306/// RejectServerCommandDrift 参数。
307#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
308pub struct RejectServerCommandDriftReq {
309    /// server id
310    pub server_id: String,
311}
312
313/// GetSandboxProfile 参数。
314#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
315pub struct GetSandboxProfileReq {
316    /// profile id
317    pub profile_id: String,
318}
319
320/// UpsertSandboxProfile 参数。
321#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
322pub struct UpsertSandboxProfileReq {
323    /// profile 完整 JSON(JCS 规范化前)—— Ledger 内部会 canonicalize + hash
324    pub profile: vigil_runner_types::SandboxProfile,
325}
326
327/// BindServerSandboxProfile 参数。
328#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
329pub struct BindServerSandboxProfileReq {
330    /// server id
331    pub server_id: String,
332    /// profile id;None = 解绑
333    pub profile_id: Option<String>,
334}