microsandbox_protocol/control/types.rs
1//! Shared control records. Their field spellings also preserve legacy JSON.
2
3use serde::{Deserialize, Serialize};
4use std::path::PathBuf;
5
6/// Aggregate disk-compaction result shared with SDK-facing types.
7pub type DiskCompactionResult = microsandbox_types::DiskCompactionResult;
8
9//--------------------------------------------------------------------------------------------------
10// Types
11//--------------------------------------------------------------------------------------------------
12
13/// Empty map payload for state and capability queries.
14#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
15pub struct Empty {}
16
17/// Facilities available for this runtime and VM configuration.
18#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
19pub struct Capabilities {
20 /// Runtime supports online root-disk growth over its extended control API.
21 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
22 pub root_disk_grow: bool,
23 /// Live CPU target changes are available.
24 pub cpu_resize: bool,
25 /// Live memory target changes are available.
26 pub memory_resize: bool,
27 /// Host secret changes are available.
28 pub secrets_update: bool,
29}
30
31/// Complete generation-two runtime facility inventory.
32///
33/// [`Capabilities`] remains the released generation-one shape so Rust callers that construct it
34/// with a struct literal keep compiling. This additive record is returned to generation-two peers.
35#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
36pub struct RuntimeCapabilities {
37 /// Runtime supports online root-disk growth over its extended control API.
38 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
39 pub root_disk_grow: bool,
40 /// Explicit guest writeback policy is accepted by capture and pause operations.
41 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
42 pub guest_flush_policy: bool,
43 /// Capture accepts an explicit disk-integrity policy.
44 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
45 pub optional_disk_integrity: bool,
46 /// Direct local branch capture is available.
47 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
48 pub branch_create: bool,
49 /// Linux descriptor-backed branching is available through the legacy transport exception.
50 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
51 pub branch_memfd: bool,
52 /// Resident pause and resume are available.
53 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
54 pub pause_resume: bool,
55 /// Explicit disk compaction is available.
56 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
57 pub disk_compact: bool,
58 /// Owned-disk compaction selectors are available.
59 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
60 pub disk_compact_owned: bool,
61 /// Live CPU target changes are available.
62 pub cpu_resize: bool,
63 /// Live memory target changes are available.
64 pub memory_resize: bool,
65 /// Host secret changes are available.
66 pub secrets_update: bool,
67 /// Same-epoch full checkpoint capture is available.
68 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
69 pub checkpoint_create: bool,
70 /// Live disk-only capture is available.
71 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
72 pub disk_checkpoint_create: bool,
73}
74
75/// Purpose of a full checkpoint capture.
76#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
77#[serde(rename_all = "snake_case")]
78pub enum CheckpointCaptureIntent {
79 /// User-requested full snapshot.
80 FullSnapshot,
81 /// Local idle or park continuation.
82 Park,
83 /// Transparent continuity operation.
84 TransparentTransfer,
85}
86
87/// Create one same-epoch full checkpoint.
88#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
89pub struct CheckpointCreate {
90 /// Optional writeback policy; omission preserves released behavior.
91 #[serde(default, skip_serializing_if = "Option::is_none")]
92 pub guest_flush: Option<microsandbox_types::GuestFlush>,
93 /// Whether disk content hashes are recorded.
94 #[serde(default)]
95 pub record_integrity: bool,
96 /// Caller-selected capture identity.
97 pub checkpoint_id: String,
98 /// Capture purpose.
99 pub intent: CheckpointCaptureIntent,
100}
101
102/// Published full-checkpoint state.
103#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
104pub struct CheckpointState {
105 /// Stable checkpoint identity.
106 pub checkpoint_id: String,
107 /// Content-addressed composite root.
108 pub checkpoint_root: String,
109 /// Runtime-local installed closure path.
110 pub path: PathBuf,
111 /// `full` or `incremental` physical-memory mode.
112 pub memory_mode: String,
113 /// Logical memory bytes represented by the capture.
114 pub memory_logical_bytes: u64,
115 /// Non-zero memory bytes emitted by the capture.
116 pub memory_emitted_bytes: u64,
117}
118
119/// Full-checkpoint completion, including a published result whose source recovery failed.
120#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
121pub struct CheckpointResult {
122 /// Published checkpoint when publication completed.
123 #[serde(default, skip_serializing_if = "Option::is_none")]
124 pub checkpoint: Option<CheckpointState>,
125 /// Source recovery diagnostic after successful publication.
126 #[serde(default, skip_serializing_if = "Option::is_none")]
127 pub recovery_error: Option<String>,
128}
129
130/// Seal the owned root disk without capturing RAM or execution state.
131#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
132pub struct DiskCheckpointCreate {
133 /// Optional writeback policy; omission preserves released behavior.
134 #[serde(default, skip_serializing_if = "Option::is_none")]
135 pub guest_flush: Option<microsandbox_types::GuestFlush>,
136 /// Caller-selected capture identity.
137 pub checkpoint_id: String,
138}
139
140/// Complete disk-only capture result.
141#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
142pub struct DiskCheckpointState {
143 /// Capture identity echoed from the request.
144 pub checkpoint_id: String,
145 /// Runtime-owned closure containing the sealed manifests and layers.
146 pub path: PathBuf,
147 /// Complete root generation.
148 pub disk: microsandbox_types::snapshot::disk::DiskGenerationManifest,
149 /// Complete owned-volume inventory.
150 #[serde(default, skip_serializing_if = "Vec::is_empty")]
151 pub owned_volumes: Vec<microsandbox_types::snapshot::OwnedVolumeCapture>,
152}
153
154/// Capture directly into a reserved child-owned local handoff directory.
155#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
156pub struct BranchCreate {
157 /// Optional writeback policy.
158 #[serde(default, skip_serializing_if = "Option::is_none")]
159 pub guest_flush: Option<microsandbox_types::GuestFlush>,
160 /// Whether disk content hashes are recorded.
161 #[serde(default)]
162 pub record_integrity: bool,
163 /// Unique capture identity.
164 pub branch_id: String,
165 /// Reserved child sandbox name.
166 pub child_name: String,
167 /// Cache containing the existing handoff reservation.
168 pub memory_cache_dir: PathBuf,
169}
170
171/// Completed direct local branch handoff.
172#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
173pub struct BranchResult {
174 /// Runtime-owned handoff closure path.
175 pub path: PathBuf,
176}
177
178/// Pause the runtime, optionally requiring guest writeback first.
179#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
180pub struct Pause {
181 /// Optional policy; omission preserves the released unit-shaped pause request.
182 #[serde(default, skip_serializing_if = "Option::is_none")]
183 pub guest_flush: Option<microsandbox_types::GuestFlush>,
184}
185
186/// Host-confirmed resident suspension state.
187#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
188pub struct PauseState {
189 /// Whether a user pause is currently held.
190 pub paused: bool,
191 /// Whether recovery owns a suspension that ordinary resume must not release.
192 pub recovery_required: bool,
193 /// Why full capture cannot use this pause.
194 #[serde(default, skip_serializing_if = "Option::is_none")]
195 pub capture_unavailable: Option<String>,
196}
197
198/// Grow the owned root disk and its mounted filesystem.
199#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
200pub struct RootDiskGrow {
201 /// Target capacity in bytes.
202 pub size_bytes: u64,
203}
204
205/// Verified root-disk growth and measured phases.
206#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
207pub struct RootDiskState {
208 /// Committed ext4 capacity in bytes.
209 pub filesystem_bytes: u64,
210 /// Guest-observed virtio-block capacity in bytes.
211 pub device_bytes: u64,
212 /// Total operation time in microseconds.
213 pub total_us: u64,
214 /// VM pause through resume in microseconds.
215 pub pause_us: u64,
216 /// Guest expansion and verification time in microseconds.
217 pub guest_us: u64,
218}
219
220/// Compact selected sandbox-owned disk chains.
221#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
222pub struct DiskCompact {
223 /// Disks selected for maintenance.
224 #[serde(default)]
225 pub target: microsandbox_types::DiskCompactionTarget,
226 /// Maximum oldest sealed layers to compact per selected disk.
227 #[serde(default, skip_serializing_if = "Option::is_none")]
228 pub layers: Option<u64>,
229 /// Resolve without applying the plan.
230 #[serde(default)]
231 pub dry_run: bool,
232}
233
234/// Accepted and observed memory quantities, all in MiB.
235#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
236pub struct MemoryState {
237 /// Boot allocation.
238 pub boot_mib: u64,
239 /// Accepted target, which need not have converged yet.
240 pub target_mib: u64,
241 /// Current guest observation.
242 pub current_mib: u64,
243 /// Boot-time capacity ceiling.
244 pub max_mib: u64,
245}
246
247/// CPU capacity, accepted target, observation, and enforcement.
248#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
249pub struct CpuState {
250 /// CPUs possible for this VM boot.
251 pub possible: u32,
252 /// Accepted online target.
253 pub requested_online: u32,
254 /// Guest-reported online CPUs.
255 pub actual_online: u32,
256 /// Host-enforced online CPUs.
257 pub enforced: u32,
258}
259
260/// Native memory-target payload, without SDK convergence policy.
261#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
262pub struct MemoryTarget {
263 /// Requested total memory in MiB.
264 pub total_mib: u64,
265}
266
267/// Native CPU-target payload.
268#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
269pub struct CpuTarget {
270 /// Requested online CPUs.
271 pub online: u32,
272}
273
274/// Secret material that is redacted in diagnostics and cleared on drop.
275#[derive(Clone, Serialize, Deserialize, zeroize::Zeroize, zeroize::ZeroizeOnDrop)]
276#[serde(transparent)]
277pub struct SecretValue(pub String);
278
279/// One ordered host secret modification, preserving the JSON operation tags.
280#[derive(Debug, Clone, Serialize, Deserialize)]
281#[serde(tag = "change", rename_all = "snake_case")]
282pub enum SecretChange {
283 /// Replace an existing secret's value.
284 Rotate {
285 /// Secret identity.
286 name: String,
287 /// New secret material.
288 value: SecretValue,
289 },
290 /// Remove a secret; absence is a successful no-op.
291 Remove {
292 /// Secret identity.
293 name: String,
294 },
295 /// Replace an existing secret's allowed hosts.
296 SetAllowedHosts {
297 /// Secret identity.
298 name: String,
299 /// Replacement host patterns, in caller order.
300 hosts: Vec<String>,
301 },
302}
303
304/// Sequential, non-transactional secret modifications.
305#[derive(Debug, Clone, Serialize, Deserialize)]
306pub struct SecretsUpdate {
307 /// Apply in order and stop at the first operation failure.
308 pub changes: Vec<SecretChange>,
309}
310
311/// State mutation certainty reported by an operation error.
312#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
313#[serde(rename_all = "snake_case")]
314pub enum ErrorEffect {
315 /// This operation (or failed batch entry) did not change state.
316 None,
317 /// A change cannot be ruled out.
318 Unknown,
319}
320
321/// A recoverable peer error. Codes stay strings for future interoperability.
322#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
323pub struct ControlError {
324 /// Stable machine-readable code; preserve unknown future codes.
325 pub code: String,
326 /// Safe diagnostic text, never a request body or secret value.
327 pub message: String,
328 /// Certainty for this operation, not a retry instruction.
329 pub effect: ErrorEffect,
330}
331
332/// Completion of a sequential secret batch.
333#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
334#[serde(tag = "outcome", rename_all = "snake_case")]
335pub enum SecretsResult {
336 /// Every entry completed, including successful no-ops.
337 Complete {
338 /// Number of completed entries.
339 applied_count: u32,
340 },
341 /// Earlier entries completed; remaining entries were not attempted.
342 Failed {
343 /// Number of completed entries.
344 applied_count: u32,
345 /// Zero-based failed entry, equal to `applied_count`.
346 failed_index: u32,
347 /// Certainty here applies to the failed entry only.
348 error: ControlError,
349 },
350}
351
352/// Legacy JSON request, also used as a checked dispatch representation.
353#[derive(Debug, Clone, Serialize, Deserialize)]
354#[serde(tag = "op", rename_all = "snake_case")]
355pub enum ControlRequest {
356 /// Query available host operations.
357 Capabilities,
358 /// Set the memory target.
359 MemoryTarget {
360 /// Requested total memory in MiB.
361 total_mib: u64,
362 },
363 /// Observe memory state.
364 MemoryState,
365 /// Set the CPU target.
366 CpuTarget {
367 /// Requested online CPUs.
368 online: u32,
369 },
370 /// Observe CPU state.
371 CpuState,
372 /// Apply ordered secret modifications.
373 SecretsUpdate {
374 /// Caller-ordered changes.
375 changes: Vec<SecretChange>,
376 },
377}
378
379/// Existing JSON response with an additive discovery advertisement.
380///
381/// Omission of `control_protocols` identifies an ordinary legacy response.
382/// Raw JSON consumers must retain the original bytes to preserve unknown fields.
383#[derive(Debug, Clone, Default, Serialize, Deserialize)]
384pub struct JsonControlResponse {
385 /// Whether the operation succeeded.
386 pub ok: bool,
387 /// Legacy diagnostic; batch progress cannot be inferred from it.
388 #[serde(default, skip_serializing_if = "Option::is_none")]
389 pub error: Option<String>,
390 /// Present for memory operations.
391 #[serde(default, skip_serializing_if = "Option::is_none")]
392 pub memory: Option<MemoryState>,
393 /// Present for CPU operations.
394 #[serde(default, skip_serializing_if = "Option::is_none")]
395 pub cpu: Option<CpuState>,
396 /// Present for capability discovery.
397 #[serde(default, skip_serializing_if = "Option::is_none")]
398 pub capabilities: Option<Capabilities>,
399 /// Explicit operation formats, emitted only during discovery.
400 #[serde(default, skip_serializing_if = "Option::is_none")]
401 pub control_protocols: Option<Vec<String>>,
402}
403
404//--------------------------------------------------------------------------------------------------
405// Methods
406//--------------------------------------------------------------------------------------------------
407
408impl RuntimeCapabilities {
409 /// Project the extended inventory onto the frozen generation-one record.
410 pub fn generation_one(self) -> Capabilities {
411 Capabilities {
412 root_disk_grow: self.root_disk_grow,
413 cpu_resize: self.cpu_resize,
414 memory_resize: self.memory_resize,
415 secrets_update: self.secrets_update,
416 }
417 }
418}
419
420impl ControlError {
421 /// Construct an error known to precede mutation.
422 pub fn rejected(code: impl Into<String>, message: impl Into<String>) -> Self {
423 Self {
424 code: code.into(),
425 message: message.into(),
426 effect: ErrorEffect::None,
427 }
428 }
429}
430
431//--------------------------------------------------------------------------------------------------
432// Trait Implementations
433//--------------------------------------------------------------------------------------------------
434
435impl std::fmt::Debug for SecretValue {
436 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
437 f.write_str("[redacted]")
438 }
439}