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/// One ordered host secret modification, preserving the JSON operation tags.
275#[derive(Debug, Clone, Serialize, Deserialize)]
276#[serde(tag = "change", rename_all = "snake_case")]
277pub enum SecretChange {
278 /// Replace an existing secret's value.
279 Rotate {
280 /// Secret identity.
281 name: String,
282 /// New secret material.
283 value: SecretValue,
284 },
285 /// Remove a secret; absence is a successful no-op.
286 Remove {
287 /// Secret identity.
288 name: String,
289 },
290 /// Replace an existing secret's allowed hosts.
291 SetAllowedHosts {
292 /// Secret identity.
293 name: String,
294 /// Replacement host patterns, in caller order.
295 hosts: Vec<String>,
296 },
297}
298
299/// Sequential, non-transactional secret modifications.
300#[derive(Debug, Clone, Serialize, Deserialize)]
301pub struct SecretsUpdate {
302 /// Apply in order and stop at the first operation failure.
303 pub changes: Vec<SecretChange>,
304}
305
306/// State mutation certainty reported by an operation error.
307#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
308#[serde(rename_all = "snake_case")]
309pub enum ErrorEffect {
310 /// This operation (or failed batch entry) did not change state.
311 None,
312 /// A change cannot be ruled out.
313 Unknown,
314}
315
316/// A recoverable peer error. Codes stay strings for future interoperability.
317#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
318pub struct ControlError {
319 /// Stable machine-readable code; preserve unknown future codes.
320 pub code: String,
321 /// Safe diagnostic text, never a request body or secret value.
322 pub message: String,
323 /// Certainty for this operation, not a retry instruction.
324 pub effect: ErrorEffect,
325}
326
327/// Completion of a sequential secret batch.
328#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
329#[serde(tag = "outcome", rename_all = "snake_case")]
330pub enum SecretsResult {
331 /// Every entry completed, including successful no-ops.
332 Complete {
333 /// Number of completed entries.
334 applied_count: u32,
335 },
336 /// Earlier entries completed; remaining entries were not attempted.
337 Failed {
338 /// Number of completed entries.
339 applied_count: u32,
340 /// Zero-based failed entry, equal to `applied_count`.
341 failed_index: u32,
342 /// Certainty here applies to the failed entry only.
343 error: ControlError,
344 },
345}
346
347/// Legacy JSON request, also used as a checked dispatch representation.
348#[derive(Debug, Clone, Serialize, Deserialize)]
349#[serde(tag = "op", rename_all = "snake_case")]
350pub enum ControlRequest {
351 /// Query available host operations.
352 Capabilities,
353 /// Set the memory target.
354 MemoryTarget {
355 /// Requested total memory in MiB.
356 total_mib: u64,
357 },
358 /// Observe memory state.
359 MemoryState,
360 /// Set the CPU target.
361 CpuTarget {
362 /// Requested online CPUs.
363 online: u32,
364 },
365 /// Observe CPU state.
366 CpuState,
367 /// Apply ordered secret modifications.
368 SecretsUpdate {
369 /// Caller-ordered changes.
370 changes: Vec<SecretChange>,
371 },
372}
373
374/// Existing JSON response with an additive discovery advertisement.
375///
376/// Omission of `control_protocols` identifies an ordinary legacy response.
377/// Raw JSON consumers must retain the original bytes to preserve unknown fields.
378#[derive(Debug, Clone, Default, Serialize, Deserialize)]
379pub struct JsonControlResponse {
380 /// Whether the operation succeeded.
381 pub ok: bool,
382 /// Legacy diagnostic; batch progress cannot be inferred from it.
383 #[serde(default, skip_serializing_if = "Option::is_none")]
384 pub error: Option<String>,
385 /// Present for memory operations.
386 #[serde(default, skip_serializing_if = "Option::is_none")]
387 pub memory: Option<MemoryState>,
388 /// Present for CPU operations.
389 #[serde(default, skip_serializing_if = "Option::is_none")]
390 pub cpu: Option<CpuState>,
391 /// Present for capability discovery.
392 #[serde(default, skip_serializing_if = "Option::is_none")]
393 pub capabilities: Option<Capabilities>,
394 /// Explicit operation formats, emitted only during discovery.
395 #[serde(default, skip_serializing_if = "Option::is_none")]
396 pub control_protocols: Option<Vec<String>>,
397}
398
399//--------------------------------------------------------------------------------------------------
400// Methods
401//--------------------------------------------------------------------------------------------------
402
403impl RuntimeCapabilities {
404 /// Project the extended inventory onto the frozen generation-one record.
405 pub fn generation_one(self) -> Capabilities {
406 Capabilities {
407 root_disk_grow: self.root_disk_grow,
408 cpu_resize: self.cpu_resize,
409 memory_resize: self.memory_resize,
410 secrets_update: self.secrets_update,
411 }
412 }
413}
414
415impl ControlError {
416 /// Construct an error known to precede mutation.
417 pub fn rejected(code: impl Into<String>, message: impl Into<String>) -> Self {
418 Self {
419 code: code.into(),
420 message: message.into(),
421 effect: ErrorEffect::None,
422 }
423 }
424}
425
426//--------------------------------------------------------------------------------------------------
427// Re-Exports
428//--------------------------------------------------------------------------------------------------
429
430pub use microsandbox_types::SecretValue;