Skip to main content

vigil_ui_protocol/
response.rs

1//! `UiResponse` tagged union(ADR 0008 §D3)。
2//!
3//! 每个 UiCommand 映射到一个 UiResponse 变种(同名或类型化资源)。
4
5use serde::{Deserialize, Serialize};
6use serde_json::Value;
7
8use vigil_audit::{
9    EventHit, ReplayEvent, SecretRefEntry, ServerOnboardingData, StoredServerProfile,
10    ToolApprovalCard, ToolSecretBinding,
11};
12use vigil_runner_types::SandboxProfile;
13use vigil_types::{ApprovalRequest, ApprovalScope, ApprovalStatus};
14
15/// UI 响应的 tagged union。
16#[derive(Debug, Clone, Serialize, Deserialize)]
17#[serde(tag = "kind", content = "data")]
18#[non_exhaustive]
19pub enum UiResponse {
20    /// 无返回数据(写命令成功 / 空查询)
21    Ack,
22    /// 事件列表(Activity Feed)
23    EventList(Vec<EventSummary>),
24    /// 单条事件的完整 payload
25    EventDetail(EventDetail),
26    /// FTS 搜索命中
27    SearchHits(Vec<EventHit>),
28    /// Pending approval 概要
29    ApprovalList(Vec<ApprovalSummary>),
30    /// Approval 完整细节
31    ApprovalDetail(ApprovalDetailDto),
32    /// Session 列表
33    SessionList(Vec<SessionSummary>),
34    /// Session replay
35    ReplayDump(SessionReplay),
36    /// hash chain verify 结果
37    ChainVerification(ChainVerifyReport),
38    /// Server 列表
39    ServerList(Vec<StoredServerProfile>),
40    /// Server onboarding 数据
41    ServerOnboarding(ServerOnboardingData),
42    /// Tool approval cards(pending 或 drifted)
43    ToolApprovalList(Vec<ToolApprovalCard>),
44    /// Drifted servers
45    DriftedServerList(Vec<ServerOnboardingData>),
46    /// Sandbox profile 列表
47    SandboxProfileList(Vec<SandboxProfile>),
48    /// 单个 sandbox profile(或 None)
49    SandboxProfileOpt(Option<SandboxProfile>),
50    /// Approval resolve 后的状态
51    ApprovalResolution(ApprovalResolutionDto),
52    /// Sandbox profile upsert 后 id + hash
53    SandboxProfileUpserted(SandboxProfileUpsertDto),
54    /// Secret refs + bindings(辅助 onboarding)
55    SecretBinding(SecretBindingSummary),
56    /// ISS-017 — Privacy Findings 聚合视图(全局 label × count + 最近 scans)
57    PrivacyFindings(PrivacyFindingsDto),
58    /// ISS-018 — Safe Export 渲染结果(MD / HTML 字符串内容)
59    SessionExport(SessionExportDto),
60}
61
62// ---------------- DTO ----------------
63
64/// Activity Feed 单行摘要(不含完整 payload)。
65#[derive(Debug, Clone, Serialize, Deserialize)]
66pub struct EventSummary {
67    /// events.event_id
68    pub event_id: i64,
69    /// 所属 session
70    pub session_id: String,
71    /// 事件类型
72    pub event_type: String,
73    /// FTS redacted 摘要(可能为 None)
74    pub redacted_text: Option<String>,
75    /// Unix 秒
76    pub created_at: i64,
77}
78
79impl From<ReplayEvent> for EventSummary {
80    fn from(e: ReplayEvent) -> Self {
81        Self {
82            event_id: e.event_id,
83            session_id: e.session_id,
84            event_type: e.event_type,
85            redacted_text: e.redacted_text,
86            created_at: e.created_at,
87        }
88    }
89}
90
91/// 单条事件的完整 payload(从 events 表直读,payload 已 JCS 规范化 + 脱敏)。
92#[derive(Debug, Clone, Serialize, Deserialize)]
93pub struct EventDetail {
94    /// event_id
95    pub event_id: i64,
96    /// session
97    pub session_id: String,
98    /// type
99    pub event_type: String,
100    /// 完整 payload JSON(已脱敏)
101    pub payload: Value,
102    /// FTS 摘要
103    pub redacted_text: Option<String>,
104    /// hash chain:前 hash
105    pub prev_hash: String,
106    /// hash chain:本事件 hash
107    pub event_hash: String,
108    /// 创建时间
109    pub created_at: i64,
110}
111
112/// Approval 列表项(不含 effect vector,节省传输)。
113#[derive(Debug, Clone, Serialize, Deserialize)]
114pub struct ApprovalSummary {
115    /// approval id
116    pub approval_id: String,
117    /// 所属 session
118    pub session_id: String,
119    /// 标题
120    pub title: String,
121    /// 简述
122    pub summary: String,
123    /// 状态
124    pub status: ApprovalStatus,
125    /// 到期 Unix 秒
126    pub expires_at: i64,
127}
128
129impl From<&ApprovalRequest> for ApprovalSummary {
130    fn from(r: &ApprovalRequest) -> Self {
131        Self {
132            approval_id: r.approval_id.clone(),
133            session_id: r.session_id.clone(),
134            title: r.title.clone(),
135            summary: r.summary.clone(),
136            status: r.status,
137            expires_at: r.expires_at,
138        }
139    }
140}
141
142/// ISS-014 — Privacy Findings 区块单项(按 PrivacyLabel 聚合)。
143///
144/// **绝不展原文**:仅展示 `{label} × {count}` 元数据,与 `redaction_findings` 表
145/// "不存原文"纪律一致(ADR 0013 §I-9.1 + audit `test_schema_forbids_plaintext_columns`)。
146#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
147pub struct PrivacyFindingDto {
148    /// PrivacyLabel 字面量(`secret` / `email` / `private_person` / 等 8 类之一)
149    pub label: String,
150    /// 该 label 在本 approval 关联 session 内的 finding 命中次数(≥ 1)
151    pub count: i64,
152}
153
154/// ISS-018 — Safe Export 输出 DTO。
155///
156/// **不变量**:`content` 来自 `events.payload_json`(已由 `vigil-redaction::redact`
157/// 在 audit 入库时脱敏)+ `events.redacted_text`(FTS 摘要)+ 元数据(event_id、
158/// event_type、ts、hash 链);**绝不**接触从未脱敏的源。渲染层只组装,不引入新文本。
159///
160/// `content` 按 `ExportFormat` 编码:`Md` → Markdown 文本,`Html` → 完整 HTML 文档
161/// (含 `<!DOCTYPE>` + 最小 inline CSS)。前端用 Blob + `<a download>` 触发下载。
162#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
163pub struct SessionExportDto {
164    /// 被导出的 session id
165    pub session_id: String,
166    /// 输出格式(Md / Html)
167    pub format: crate::ExportFormat,
168    /// 渲染后的文本内容
169    pub content: String,
170    /// 内容字节长度(UI 显示用)
171    pub byte_len: usize,
172    /// 包含的事件总数
173    pub event_count: usize,
174    /// 渲染时戳(Unix epoch 秒)
175    pub generated_at: i64,
176}
177
178/// ISS-017 — Privacy Findings 面板单条 scan 摘要(不含原文)。
179///
180/// 仅展 metadata + 衍生 finding 数;`fingerprint` 已是 sha256 前 16 字节 hex
181/// (32 char),不可逆;`text_length_bucket` 是位宽粗化(MSB 1-based,U64 0-64)。
182#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
183pub struct RedactionScanSummaryDto {
184    /// scan_id(UUIDv4)
185    pub scan_id: String,
186    /// 关联的 session_id
187    pub session_id: String,
188    /// Unix epoch 秒(scan 入库时间)
189    pub ts: i64,
190    /// 来源:`paste` | `tool_arg` | `tool_output` | `export`
191    pub source: String,
192    /// 文本长度位宽粗化(MSB 1-based,0→0)— **不还原原文长度**
193    pub text_length_bucket: i64,
194    /// 文本 sha256 前 16 字节 hex-lower(32 字符)— 跨 scan 溯源用,不泄漏原文
195    pub fingerprint: String,
196    /// 该 scan 下 finding 总数(各 label 合计)
197    pub finding_count: i64,
198}
199
200/// ISS-017 — Privacy Findings 面板的聚合 payload。
201///
202/// **绝不展原文**:UI 用此 DTO 渲染时必须仅展示 label 字面量、计数、fingerprint
203/// 截断字符串;不得 join span 或还原文本(audit grep 守门 `test_schema_forbids_plaintext_columns`
204/// 的语义延伸到 UI 层)。
205#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
206pub struct PrivacyFindingsDto {
207    /// 全局 label 聚合(count DESC, label ASC)
208    pub by_label_total: Vec<PrivacyFindingDto>,
209    /// 最近 N 条 scans 的摘要(按 ts DESC, scan_id DESC)
210    pub recent_scans: Vec<RedactionScanSummaryDto>,
211}
212
213/// Approval 完整细节。
214#[derive(Debug, Clone, Serialize, Deserialize)]
215pub struct ApprovalDetailDto {
216    /// 原始 request
217    pub request: ApprovalRequest,
218    /// 关联的 invocation id
219    pub invocation_id: String,
220    /// 关联的 decision id
221    pub decision_id: String,
222    /// ISS-014 — 关联 session 的 Privacy Findings 聚合(label × count)
223    /// 空数组表示该 session 无 PII 命中(或 firewall preflight 未跑)。
224    /// **scope 折衷**:按 session_id 聚合而非 invocation_id(redaction_scans 暂无
225    /// invocation_id 字段),同 session 多 invocation 的 findings 会一起呈现。
226    /// ISS-014 phase 2 / ISS-021 后续可加 invocation 关联。
227    #[serde(default)]
228    pub privacy_findings: Vec<PrivacyFindingDto>,
229}
230
231/// Session 列表项。
232#[derive(Debug, Clone, Serialize, Deserialize)]
233pub struct SessionSummary {
234    /// session id
235    pub session_id: String,
236    /// source(mcp_hub / desktop / ...)
237    pub source: String,
238    /// 应用名(可选)
239    pub app_name: Option<String>,
240    /// 开始时间
241    pub started_at: i64,
242    /// 结束时间(未结束 = None)
243    pub ended_at: Option<i64>,
244    /// 风险分
245    pub risk_score: i64,
246}
247
248/// Session replay 结果。
249#[derive(Debug, Clone, Serialize, Deserialize)]
250pub struct SessionReplay {
251    /// session id
252    pub session_id: String,
253    /// 事件总数
254    pub event_count: usize,
255    /// 完整事件流(已脱敏)
256    pub events: Vec<EventDetail>,
257    /// 可选 verify_chain 结果
258    pub chain_verified: Option<ChainVerifyReport>,
259}
260
261/// hash chain verify 报告。
262#[derive(Debug, Clone, Serialize, Deserialize)]
263pub struct ChainVerifyReport {
264    /// 是否全部校验通过
265    pub ok: bool,
266    /// 若 broken,指向第一条断链的 event_id
267    pub broken_at_event_id: Option<i64>,
268    /// 错误文本(已脱敏)
269    pub message: Option<String>,
270}
271
272/// Approval resolve 结果。
273#[derive(Debug, Clone, Serialize, Deserialize)]
274pub struct ApprovalResolutionDto {
275    /// approval id
276    pub approval_id: String,
277    /// 最终状态
278    pub status: ApprovalStatus,
279    /// 生效 scope(approve 时有值)
280    pub scope: Option<ApprovalScope>,
281    /// 谁 resolve 的
282    pub resolved_by: Option<String>,
283}
284
285/// Sandbox profile upsert 结果。
286#[derive(Debug, Clone, Serialize, Deserialize)]
287pub struct SandboxProfileUpsertDto {
288    /// profile id
289    pub profile_id: String,
290    /// 新 hash(sha256 JCS)
291    pub profile_hash: String,
292    /// 是否是新插入(true = INSERT;false = UPDATE)
293    pub inserted: bool,
294}
295
296/// 某 server 的 secret binding 概览(辅助 onboarding)。
297#[derive(Debug, Clone, Serialize, Deserialize)]
298pub struct SecretBindingSummary {
299    /// 所属 server
300    pub server_id: String,
301    /// 已登记的 secret refs(仅 alias + metadata)
302    pub refs: Vec<SecretRefEntry>,
303    /// 该 server 的 ChildEnv 绑定
304    pub bindings: Vec<ToolSecretBinding>,
305}