@celestia-island/plana-types 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/Cargo.toml +38 -0
  2. package/bindings/FileAnchor.ts +3 -0
  3. package/bindings/engine.ts +277 -0
  4. package/bindings/enums.ts +31 -0
  5. package/bindings/httpTypes.ts +197 -0
  6. package/bindings/index.ts +47 -0
  7. package/bindings/mcp/aporia.ts +61 -0
  8. package/bindings/mcp/eleos.ts +18 -0
  9. package/bindings/mcp/epieikeia.ts +48 -0
  10. package/bindings/mcp/haplotes.ts +47 -0
  11. package/bindings/mcp/hubris.ts +41 -0
  12. package/bindings/mcp/index.ts +13 -0
  13. package/bindings/mcp/kalos.ts +47 -0
  14. package/bindings/mcp/neikos.ts +76 -0
  15. package/bindings/mcp/orexis.ts +64 -0
  16. package/bindings/mcp/philia.ts +57 -0
  17. package/bindings/mcp/polemos.ts +55 -0
  18. package/bindings/mcp/skemma.ts +58 -0
  19. package/bindings/mcp/skopeo.ts +56 -0
  20. package/bindings/mcp/webAutomation.ts +31 -0
  21. package/bindings/model.ts +240 -0
  22. package/bindings/package.json +16 -0
  23. package/bindings/region.ts +3 -0
  24. package/bindings/serde_json/JsonValue.ts +3 -0
  25. package/bindings/ws/agentLifecycle.ts +41 -0
  26. package/bindings/ws/auth.ts +15 -0
  27. package/bindings/ws/baseMessages.ts +7 -0
  28. package/bindings/ws/bridgeNetwork.ts +71 -0
  29. package/bindings/ws/core.ts +51 -0
  30. package/bindings/ws/fileBrowsing.ts +57 -0
  31. package/bindings/ws/handshake.ts +23 -0
  32. package/bindings/ws/industrial.ts +72 -0
  33. package/bindings/ws/knowledgeBase.ts +10 -0
  34. package/bindings/ws/layer2.ts +25 -0
  35. package/bindings/ws/llmProvider.ts +74 -0
  36. package/bindings/ws/logs.ts +11 -0
  37. package/bindings/ws/malkuth.ts +62 -0
  38. package/bindings/ws/noa.ts +17 -0
  39. package/bindings/ws/stateSync.ts +19 -0
  40. package/bindings/ws/systemUi.ts +5 -0
  41. package/bindings/ws/tasks.ts +6 -0
  42. package/bindings/ws/views.ts +163 -0
  43. package/bindings/ws/workspace.ts +11 -0
  44. package/bindings/ws/yolo.ts +32 -0
  45. package/examples/schema_dump.rs +51 -0
  46. package/package.json +6 -0
  47. package/pnpm-workspace.yaml +2 -0
  48. package/src/engine.rs +602 -0
  49. package/src/enums.rs +334 -0
  50. package/src/external_mcp.rs +132 -0
  51. package/src/http.rs +1077 -0
  52. package/src/identity.rs +160 -0
  53. package/src/lib.rs +1215 -0
  54. package/src/malkuth.rs +145 -0
  55. package/src/mcp/aporia.rs +272 -0
  56. package/src/mcp/eleos.rs +210 -0
  57. package/src/mcp/epieikeia.rs +194 -0
  58. package/src/mcp/haplotes.rs +310 -0
  59. package/src/mcp/hubris.rs +377 -0
  60. package/src/mcp/kalos.rs +251 -0
  61. package/src/mcp/mod.rs +23 -0
  62. package/src/mcp/neikos.rs +533 -0
  63. package/src/mcp/orexis.rs +493 -0
  64. package/src/mcp/philia.rs +267 -0
  65. package/src/mcp/polemos.rs +241 -0
  66. package/src/mcp/skemma.rs +442 -0
  67. package/src/mcp/skopeo.rs +282 -0
  68. package/src/mcp/web_automation.rs +122 -0
  69. package/src/model.rs +421 -0
  70. package/src/protocol/base_messages.rs +125 -0
  71. package/src/protocol/handshake.rs +364 -0
  72. package/src/protocol/jsonrpc.rs +888 -0
  73. package/src/protocol/mod.rs +10 -0
  74. package/src/rbac.rs +786 -0
  75. package/src/region.rs +362 -0
  76. package/src/tracing_helpers.rs +9 -0
  77. package/src/ws/agent/agent_lifecycle.rs +221 -0
  78. package/src/ws/agent/layer2.rs +126 -0
  79. package/src/ws/agent/mod.rs +9 -0
  80. package/src/ws/agent/state_sync.rs +111 -0
  81. package/src/ws/agent/tasks.rs +43 -0
  82. package/src/ws/agent/yolo.rs +161 -0
  83. package/src/ws/mod.rs +10 -0
  84. package/src/ws/services/auth.rs +99 -0
  85. package/src/ws/services/industrial.rs +647 -0
  86. package/src/ws/services/knowledge_base.rs +59 -0
  87. package/src/ws/services/llm_provider.rs +371 -0
  88. package/src/ws/services/mod.rs +7 -0
  89. package/src/ws/ui/bridge_network.rs +96 -0
  90. package/src/ws/ui/file_browsing.rs +88 -0
  91. package/src/ws/ui/logs.rs +55 -0
  92. package/src/ws/ui/mod.rs +11 -0
  93. package/src/ws/ui/noa.rs +105 -0
  94. package/src/ws/ui/system_ui.rs +27 -0
  95. package/src/ws/ui/views.rs +159 -0
  96. package/src/ws/ui/workspace.rs +73 -0
@@ -0,0 +1,493 @@
1
+ use serde_json::Value;
2
+ use uuid::Uuid;
3
+
4
+ use crate::enums::ConsultationStatus;
5
+
6
+ #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ts_rs::TS)]
7
+ #[ts(export, export_to = "mcp/orexis.ts")]
8
+ pub struct CheckResultItem {
9
+ pub standard: String,
10
+ pub status: String,
11
+ pub message: String,
12
+ #[serde(default, skip_serializing_if = "Option::is_none")]
13
+ pub details: Option<String>,
14
+ }
15
+
16
+ #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ts_rs::TS)]
17
+ #[ts(export, export_to = "mcp/orexis.ts")]
18
+ pub struct SensitivityRule {
19
+ pub tool: String,
20
+ #[serde(default, skip_serializing_if = "Option::is_none")]
21
+ pub agent: Option<String>,
22
+ pub sensitivity: String,
23
+ #[serde(default, skip_serializing_if = "Option::is_none")]
24
+ pub reason: Option<String>,
25
+ }
26
+
27
+ #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ts_rs::TS)]
28
+ #[ts(export, export_to = "mcp/orexis.ts")]
29
+ pub struct AskResult {
30
+ pub consultation_id: Uuid,
31
+ pub question: String,
32
+ pub context: String,
33
+ pub options: Vec<String>,
34
+ pub recommended: String,
35
+ pub status: ConsultationStatus,
36
+ }
37
+
38
+ #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ts_rs::TS)]
39
+ #[ts(export, export_to = "mcp/orexis.ts")]
40
+ pub struct ReplyResult {
41
+ pub consultation_id: Uuid,
42
+ pub answer: String,
43
+ pub selected_options: Vec<String>,
44
+ pub status: ConsultationStatus,
45
+ }
46
+
47
+ #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ts_rs::TS)]
48
+ #[ts(export, export_to = "mcp/orexis.ts")]
49
+ pub struct ReportHumanResult {
50
+ pub report_id: Uuid,
51
+ pub report_type: String,
52
+ #[ts(type = "Record<string, unknown>")]
53
+ pub content: Value,
54
+ pub consultation_id: Option<Uuid>,
55
+ pub status: ConsultationStatus,
56
+ pub created_at: String,
57
+ }
58
+
59
+ #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ts_rs::TS)]
60
+ #[ts(export, export_to = "mcp/orexis.ts")]
61
+ pub struct AuditAlignmentResult {
62
+ pub audit_id: Uuid,
63
+ pub target: String,
64
+ pub total_rules: usize,
65
+ pub passed: usize,
66
+ pub failed: usize,
67
+ pub findings: Vec<AuditFinding>,
68
+ }
69
+
70
+ #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ts_rs::TS)]
71
+ #[ts(export, export_to = "mcp/orexis.ts")]
72
+ pub struct AuditLegalityResult {
73
+ pub audit_id: Uuid,
74
+ pub target: String,
75
+ pub jurisdiction: String,
76
+ pub total_requirements: usize,
77
+ pub compliant: usize,
78
+ pub non_compliant: usize,
79
+ pub findings: Vec<AuditFinding>,
80
+ }
81
+
82
+ #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ts_rs::TS)]
83
+ #[ts(export, export_to = "mcp/orexis.ts")]
84
+ pub struct AuditFinding {
85
+ pub rule_id: String,
86
+ pub severity: String,
87
+ pub description: String,
88
+ pub evidence: String,
89
+ pub recommendation: String,
90
+ }
91
+
92
+ // ── Tool parameter structs (for .d.ts API signature generation) ──
93
+
94
+ #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ts_rs::TS)]
95
+ #[ts(export, export_to = "mcp/orexis.ts")]
96
+ pub struct ReportHumanParams {
97
+ pub summary: String,
98
+ pub body: Option<String>,
99
+ pub text: Option<String>,
100
+ pub mode: Option<String>,
101
+ }
102
+
103
+ #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ts_rs::TS)]
104
+ #[ts(export, export_to = "mcp/orexis.ts")]
105
+ pub struct StandardCheckParams {
106
+ pub standard_id: Uuid,
107
+ #[serde(default, skip_serializing_if = "Option::is_none")]
108
+ pub device_data: Option<std::collections::HashMap<String, String>>,
109
+ }
110
+
111
+ #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ts_rs::TS)]
112
+ #[ts(export, export_to = "mcp/orexis.ts")]
113
+ pub struct ComplianceReportParams {
114
+ pub standard_id: Uuid,
115
+ pub check_results: Vec<CheckResultItem>,
116
+ pub device_id: Option<String>,
117
+ pub summary: Option<String>,
118
+ pub mode: Option<String>,
119
+ }
120
+
121
+ #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ts_rs::TS)]
122
+ #[ts(export, export_to = "mcp/orexis.ts")]
123
+ pub struct AuditAlignmentParams {
124
+ pub target: String,
125
+ #[serde(default, skip_serializing_if = "Option::is_none")]
126
+ pub device_data: Option<std::collections::HashMap<String, String>>,
127
+ }
128
+
129
+ #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ts_rs::TS)]
130
+ #[ts(export, export_to = "mcp/orexis.ts")]
131
+ pub struct AuditLegalityParams {
132
+ pub target: String,
133
+ pub jurisdiction: Option<String>,
134
+ #[serde(default, skip_serializing_if = "Option::is_none")]
135
+ pub device_data: Option<std::collections::HashMap<String, String>>,
136
+ }
137
+
138
+ #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ts_rs::TS)]
139
+ #[ts(export, export_to = "mcp/orexis.ts")]
140
+ pub struct AgentIntegrityParams {
141
+ pub verbose: Option<bool>,
142
+ }
143
+
144
+ #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ts_rs::TS)]
145
+ #[ts(export, export_to = "mcp/orexis.ts")]
146
+ pub struct SecurityAuditParams {
147
+ pub deep: Option<bool>,
148
+ }
149
+
150
+ #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ts_rs::TS)]
151
+ #[ts(export, export_to = "mcp/orexis.ts")]
152
+ pub struct BlockToolParams {
153
+ pub agent: String,
154
+ pub tool: String,
155
+ pub reason: Option<String>,
156
+ }
157
+
158
+ #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ts_rs::TS)]
159
+ #[ts(export, export_to = "mcp/orexis.ts")]
160
+ pub struct UnblockToolParams {
161
+ pub agent: String,
162
+ pub tool: String,
163
+ }
164
+
165
+ #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ts_rs::TS)]
166
+ #[ts(export, export_to = "mcp/orexis.ts")]
167
+ pub struct SetSecurityPolicyParams {
168
+ pub emergency_lockdown: Option<bool>,
169
+ pub audit_only: Option<bool>,
170
+ }
171
+
172
+ #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ts_rs::TS)]
173
+ #[ts(export, export_to = "mcp/orexis.ts")]
174
+ pub struct SetRiskThresholdParams {
175
+ pub level: String,
176
+ }
177
+
178
+ #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ts_rs::TS)]
179
+ #[ts(export, export_to = "mcp/orexis.ts")]
180
+ pub struct InspectToolCallParams {
181
+ pub agent: String,
182
+ pub tool: String,
183
+ pub session_id: Option<String>,
184
+ }
185
+
186
+ #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ts_rs::TS)]
187
+ #[ts(export, export_to = "mcp/orexis.ts")]
188
+ pub struct SecurityStatusParams {}
189
+
190
+ #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ts_rs::TS)]
191
+ #[ts(export, export_to = "mcp/orexis.ts")]
192
+ pub struct SetNetworkPolicyParams {
193
+ pub allow_hosts: Option<Vec<String>>,
194
+ pub allow_cidrs: Option<Vec<String>>,
195
+ pub block_private: Option<bool>,
196
+ pub block_metadata: Option<bool>,
197
+ }
198
+
199
+ #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ts_rs::TS)]
200
+ #[ts(export, export_to = "mcp/orexis.ts")]
201
+ pub struct SecuritySuggestionsParams {}
202
+
203
+ #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ts_rs::TS)]
204
+ #[ts(export, export_to = "mcp/orexis.ts")]
205
+ pub struct ManageSensitivityRulesParams {
206
+ pub action: String,
207
+ pub rules: Option<Vec<SensitivityRule>>,
208
+ }
209
+
210
+ // ── Tool result structs (compliance) ──
211
+
212
+ #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ts_rs::TS)]
213
+ #[ts(export, export_to = "mcp/orexis.ts")]
214
+ pub struct ComplianceRule {
215
+ pub id: String,
216
+ pub standard: String,
217
+ pub clause: String,
218
+ pub description: String,
219
+ pub check_type: String,
220
+ #[ts(type = "Record<string, unknown>")]
221
+ pub parameters: Value,
222
+ pub severity: String,
223
+ }
224
+
225
+ #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ts_rs::TS)]
226
+ #[ts(export, export_to = "mcp/orexis.ts")]
227
+ pub struct StandardRegisterResult {
228
+ pub standard_id: Uuid,
229
+ pub rules_registered: usize,
230
+ }
231
+
232
+ #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ts_rs::TS)]
233
+ #[ts(export, export_to = "mcp/orexis.ts")]
234
+ pub struct RuleCheckResult {
235
+ pub rule_id: String,
236
+ pub clause: String,
237
+ pub description: String,
238
+ pub status: String,
239
+ #[ts(type = "Record<string, unknown> | null")]
240
+ pub actual_value: Option<Value>,
241
+ #[ts(type = "Record<string, unknown> | null")]
242
+ pub expected: Option<Value>,
243
+ pub deviation: Option<f64>,
244
+ pub severity: String,
245
+ }
246
+
247
+ #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ts_rs::TS)]
248
+ #[ts(export, export_to = "mcp/orexis.ts")]
249
+ pub struct StandardCheckResult {
250
+ pub standard_id: Uuid,
251
+ pub total_rules: usize,
252
+ pub passed: usize,
253
+ pub failed: usize,
254
+ pub not_applicable: usize,
255
+ pub results: Vec<RuleCheckResult>,
256
+ pub overall_status: String,
257
+ }
258
+
259
+ #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ts_rs::TS)]
260
+ #[ts(export, export_to = "mcp/orexis.ts")]
261
+ pub struct ComplianceSummary {
262
+ pub total_rules: usize,
263
+ pub passed: usize,
264
+ pub failed: usize,
265
+ pub critical_failures: usize,
266
+ pub high_failures: usize,
267
+ pub medium_failures: usize,
268
+ }
269
+
270
+ #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ts_rs::TS)]
271
+ #[ts(export, export_to = "mcp/orexis.ts")]
272
+ pub struct ReportDetail {
273
+ pub rule_id: String,
274
+ pub clause: String,
275
+ pub description: String,
276
+ pub status: String,
277
+ pub severity: String,
278
+ pub deviation: Option<f64>,
279
+ pub recommendation: String,
280
+ }
281
+
282
+ #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ts_rs::TS)]
283
+ #[ts(export, export_to = "mcp/orexis.ts")]
284
+ pub struct ComplianceReportToolResult {
285
+ pub report_id: Uuid,
286
+ pub standard_id: Uuid,
287
+ pub device_id: Option<String>,
288
+ pub overall_status: String,
289
+ pub summary: ComplianceSummary,
290
+ pub details: Vec<ReportDetail>,
291
+ pub generated_at: String,
292
+ }
293
+
294
+ #[cfg(test)]
295
+ mod tests {
296
+ use super::*;
297
+ use crate::enums::ConsultationStatus;
298
+ use serde_json::json;
299
+
300
+ #[test]
301
+ fn check_result_item_round_trip() {
302
+ let item = CheckResultItem {
303
+ standard: "ISO-27001".into(),
304
+ status: "pass".into(),
305
+ message: "encryption enabled".into(),
306
+ details: None,
307
+ };
308
+ let v = serde_json::to_value(&item).unwrap();
309
+ assert_eq!(v["status"], "pass");
310
+ assert!(v.get("details").is_none(), "details skipped when None");
311
+ let back: CheckResultItem = serde_json::from_value(v).unwrap();
312
+ assert_eq!(back.standard, "ISO-27001");
313
+ }
314
+
315
+ #[test]
316
+ fn check_result_item_with_details() {
317
+ let item = CheckResultItem {
318
+ standard: "ISO-27001".into(),
319
+ status: "fail".into(),
320
+ message: "weak cipher".into(),
321
+ details: Some("TLS_RSA_WITH_3DES_EDE_CBC_SHA detected".into()),
322
+ };
323
+ let v = serde_json::to_value(&item).unwrap();
324
+ assert_eq!(v["details"], "TLS_RSA_WITH_3DES_EDE_CBC_SHA detected");
325
+ }
326
+
327
+ #[test]
328
+ fn sensitivity_rule_round_trip() {
329
+ let r = SensitivityRule {
330
+ tool: "neikos.container.exec".into(),
331
+ agent: Some("skeMma".into()),
332
+ sensitivity: "high".into(),
333
+ reason: None,
334
+ };
335
+ let v = serde_json::to_value(&r).unwrap();
336
+ assert_eq!(v["sensitivity"], "high");
337
+ assert_eq!(v["agent"], "skeMma");
338
+ assert!(v.get("reason").is_none());
339
+ }
340
+
341
+ #[test]
342
+ fn ask_result_round_trip() {
343
+ let id = Uuid::new_v4();
344
+ let r = AskResult {
345
+ consultation_id: id,
346
+ question: "Proceed with deletion?".into(),
347
+ context: "3 files selected".into(),
348
+ options: vec!["yes".into(), "no".into()],
349
+ recommended: "no".into(),
350
+ status: ConsultationStatus::WaitingHuman,
351
+ };
352
+ let v = serde_json::to_value(&r).unwrap();
353
+ assert_eq!(v["consultation_id"], id.to_string());
354
+ // ConsultationStatus serializes as PascalCase variant name.
355
+ assert_eq!(v["status"], "WaitingHuman");
356
+ assert_eq!(v["options"].as_array().unwrap().len(), 2);
357
+ let back: AskResult = serde_json::from_value(v).unwrap();
358
+ assert_eq!(back.status, ConsultationStatus::WaitingHuman);
359
+ }
360
+
361
+ #[test]
362
+ fn audit_alignment_result_round_trip() {
363
+ let id = Uuid::new_v4();
364
+ let r = AuditAlignmentResult {
365
+ audit_id: id,
366
+ target: "container-runtime".into(),
367
+ total_rules: 10,
368
+ passed: 8,
369
+ failed: 2,
370
+ findings: vec![AuditFinding {
371
+ rule_id: "R-001".into(),
372
+ severity: "high".into(),
373
+ description: "Missing seccomp profile".into(),
374
+ evidence: "no seccomp annotation".into(),
375
+ recommendation: "Apply default seccomp profile".into(),
376
+ }],
377
+ };
378
+ let v = serde_json::to_value(&r).unwrap();
379
+ assert_eq!(v["passed"], 8);
380
+ assert_eq!(v["failed"], 2);
381
+ assert_eq!(v["findings"][0]["severity"], "high");
382
+ let back: AuditAlignmentResult = serde_json::from_value(v).unwrap();
383
+ assert_eq!(back.findings.len(), 1);
384
+ }
385
+
386
+ #[test]
387
+ fn compliance_summary_round_trip() {
388
+ let s = ComplianceSummary {
389
+ total_rules: 50,
390
+ passed: 45,
391
+ failed: 5,
392
+ critical_failures: 1,
393
+ high_failures: 2,
394
+ medium_failures: 2,
395
+ };
396
+ let v = serde_json::to_value(&s).unwrap();
397
+ assert_eq!(v["critical_failures"], 1);
398
+ let back: ComplianceSummary = serde_json::from_value(v).unwrap();
399
+ assert_eq!(back.total_rules, 50);
400
+ }
401
+
402
+ #[test]
403
+ fn compliance_report_tool_result_round_trip() {
404
+ let report_id = Uuid::new_v4();
405
+ let standard_id = Uuid::new_v4();
406
+ let r = ComplianceReportToolResult {
407
+ report_id,
408
+ standard_id,
409
+ device_id: Some("dev-001".into()),
410
+ overall_status: "non_compliant".into(),
411
+ summary: ComplianceSummary {
412
+ total_rules: 10,
413
+ passed: 7,
414
+ failed: 3,
415
+ critical_failures: 1,
416
+ high_failures: 1,
417
+ medium_failures: 1,
418
+ },
419
+ details: vec![ReportDetail {
420
+ rule_id: "R-005".into(),
421
+ clause: "A.10".into(),
422
+ description: "Access control".into(),
423
+ status: "fail".into(),
424
+ severity: "critical".into(),
425
+ deviation: Some(0.85),
426
+ recommendation: "Enable MFA".into(),
427
+ }],
428
+ generated_at: "2026-07-07T00:00:00Z".into(),
429
+ };
430
+ let v = serde_json::to_value(&r).unwrap();
431
+ assert_eq!(v["overall_status"], "non_compliant");
432
+ assert_eq!(v["device_id"], "dev-001");
433
+ assert_eq!(v["details"][0]["deviation"], 0.85);
434
+ let back: ComplianceReportToolResult = serde_json::from_value(v).unwrap();
435
+ assert_eq!(back.details.len(), 1);
436
+ }
437
+
438
+ #[test]
439
+ fn compliance_report_tool_result_no_device() {
440
+ let r = ComplianceReportToolResult {
441
+ report_id: Uuid::new_v4(),
442
+ standard_id: Uuid::new_v4(),
443
+ device_id: None,
444
+ overall_status: "compliant".into(),
445
+ summary: ComplianceSummary {
446
+ total_rules: 1,
447
+ passed: 1,
448
+ failed: 0,
449
+ critical_failures: 0,
450
+ high_failures: 0,
451
+ medium_failures: 0,
452
+ },
453
+ details: vec![],
454
+ generated_at: "2026-01-01T00:00:00Z".into(),
455
+ };
456
+ let v = serde_json::to_value(&r).unwrap();
457
+ assert!(
458
+ v.get("device_id").is_some(),
459
+ "Option without skip → null on wire"
460
+ );
461
+ assert_eq!(v["device_id"], serde_json::Value::Null);
462
+ }
463
+
464
+ #[test]
465
+ fn rule_check_result_round_trip() {
466
+ let r = RuleCheckResult {
467
+ rule_id: "R-001".into(),
468
+ clause: "§5.2".into(),
469
+ description: "Check X".into(),
470
+ status: "fail".into(),
471
+ actual_value: Some(json!({"value": 80})),
472
+ expected: Some(json!({"min": 100})),
473
+ deviation: Some(0.2),
474
+ severity: "medium".into(),
475
+ };
476
+ let v = serde_json::to_value(&r).unwrap();
477
+ assert_eq!(v["actual_value"]["value"], 80);
478
+ assert_eq!(v["deviation"], 0.2);
479
+ let back: RuleCheckResult = serde_json::from_value(v).unwrap();
480
+ assert_eq!(back.deviation, Some(0.2));
481
+ }
482
+
483
+ #[test]
484
+ fn block_tool_params_round_trip() {
485
+ let p = BlockToolParams {
486
+ agent: "neikos".into(),
487
+ tool: "container.remove".into(),
488
+ reason: Some("destructive in production".into()),
489
+ };
490
+ let v = serde_json::to_value(&p).unwrap();
491
+ assert_eq!(v["tool"], "container.remove");
492
+ }
493
+ }