Skip to main content

canic_backup/plan/
types.rs

1//! Module: plan::types
2//!
3//! Responsibility: define serialized backup plan and preflight contracts.
4//! Does not own: registry discovery, validation, execution, or persistence.
5//! Boundary: data shapes shared by backup planners, runners, and preflights.
6
7use crate::manifest::IdentityMode;
8
9use serde::{Deserialize, Serialize};
10
11///
12/// BackupPlan
13///
14/// Executable backup plan derived from a selected deployment scope.
15/// Owned by backup planning and consumed by execution journals and runners.
16///
17
18#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
19#[serde(deny_unknown_fields)]
20pub struct BackupPlan {
21    pub plan_version: u16,
22    pub plan_id: String,
23    pub run_id: String,
24    pub fleet: String,
25    pub environment: String,
26    pub root_canister_id: String,
27    pub selected_subtree_root: Option<String>,
28    pub selected_scope_kind: BackupScopeKind,
29    pub include_descendants: bool,
30    pub root_included: bool,
31    pub requires_root_controller: bool,
32    pub snapshot_read_authority: SnapshotReadAuthority,
33    pub quiescence_policy: QuiescencePolicy,
34    pub topology_hash_before_quiesce: String,
35    pub targets: Vec<BackupTarget>,
36    pub phases: Vec<BackupOperation>,
37}
38
39///
40/// BackupScopeKind
41///
42/// Backup selection mode used to derive target and operation scope.
43/// Owned by backup planning and serialized into plan and preflight contracts.
44///
45
46#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
47#[serde(rename_all = "kebab-case")]
48pub enum BackupScopeKind {
49    Member,
50    Subtree,
51    NonRootDeployment,
52    MaintenanceRoot,
53}
54
55///
56/// BackupTarget
57///
58/// One canister selected for backup with authority and restore policy.
59/// Owned by backup planning and used by preflight and execution builders.
60///
61
62#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
63#[serde(deny_unknown_fields)]
64pub struct BackupTarget {
65    pub canister_id: String,
66    pub role: Option<String>,
67    pub parent_canister_id: Option<String>,
68    pub depth: u32,
69    pub control_authority: ControlAuthority,
70    pub snapshot_read_authority: SnapshotReadAuthority,
71    pub identity_mode: IdentityMode,
72    pub expected_module_hash: Option<String>,
73}
74
75///
76/// AuthorityEvidence
77///
78/// Confidence level for an authority decision embedded in a backup plan.
79/// Owned by backup planning and refined by execution preflight receipts.
80///
81
82#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
83#[serde(rename_all = "kebab-case")]
84pub enum AuthorityEvidence {
85    Proven,
86    Declared,
87    Unknown,
88}
89
90///
91/// ControlAuthority
92///
93/// Control authority decision for one selected backup target.
94/// Owned by backup planning and validated before mutation execution.
95///
96
97#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
98#[serde(deny_unknown_fields)]
99pub struct ControlAuthority {
100    pub source: ControlAuthoritySource,
101    pub evidence: AuthorityEvidence,
102}
103
104impl ControlAuthority {
105    #[must_use]
106    pub const fn unknown() -> Self {
107        Self {
108            source: ControlAuthoritySource::Unknown,
109            evidence: AuthorityEvidence::Unknown,
110        }
111    }
112
113    #[must_use]
114    pub const fn root_controller(evidence: AuthorityEvidence) -> Self {
115        Self {
116            source: ControlAuthoritySource::RootController,
117            evidence,
118        }
119    }
120
121    #[must_use]
122    pub const fn operator_controller(evidence: AuthorityEvidence) -> Self {
123        Self {
124            source: ControlAuthoritySource::OperatorController,
125            evidence,
126        }
127    }
128
129    #[must_use]
130    pub fn is_proven(&self) -> bool {
131        self.evidence == AuthorityEvidence::Proven && self.source != ControlAuthoritySource::Unknown
132    }
133
134    #[must_use]
135    pub fn is_proven_root_controller(&self) -> bool {
136        self.evidence == AuthorityEvidence::Proven
137            && self.source == ControlAuthoritySource::RootController
138    }
139}
140
141///
142/// ControlAuthoritySource
143///
144/// Source of a control authority decision for one backup target.
145/// Owned by backup planning and interpreted by preflight validation.
146///
147
148#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
149#[serde(rename_all = "kebab-case", tag = "kind")]
150pub enum ControlAuthoritySource {
151    Unknown,
152    RootController,
153    OperatorController,
154}
155
156///
157/// SnapshotReadAuthority
158///
159/// Snapshot read authority decision for one selected backup target.
160/// Owned by backup planning and validated before snapshot download.
161///
162
163#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
164#[serde(deny_unknown_fields)]
165pub struct SnapshotReadAuthority {
166    pub source: SnapshotReadAuthoritySource,
167    pub evidence: AuthorityEvidence,
168}
169
170impl SnapshotReadAuthority {
171    #[must_use]
172    pub const fn unknown() -> Self {
173        Self {
174            source: SnapshotReadAuthoritySource::Unknown,
175            evidence: AuthorityEvidence::Unknown,
176        }
177    }
178
179    #[must_use]
180    pub const fn operator_controller(evidence: AuthorityEvidence) -> Self {
181        Self {
182            source: SnapshotReadAuthoritySource::OperatorController,
183            evidence,
184        }
185    }
186
187    #[must_use]
188    pub const fn root_configured_read(evidence: AuthorityEvidence) -> Self {
189        Self {
190            source: SnapshotReadAuthoritySource::RootConfiguredRead,
191            evidence,
192        }
193    }
194
195    #[must_use]
196    pub fn is_proven(&self) -> bool {
197        self.evidence == AuthorityEvidence::Proven
198            && self.source != SnapshotReadAuthoritySource::Unknown
199    }
200}
201
202///
203/// SnapshotReadAuthoritySource
204///
205/// Source of a snapshot-read authority decision for one backup target.
206/// Owned by backup planning and interpreted by preflight validation.
207///
208
209#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
210#[serde(rename_all = "kebab-case")]
211pub enum SnapshotReadAuthoritySource {
212    Unknown,
213    OperatorController,
214    RootConfiguredRead,
215}
216
217///
218/// QuiescencePolicy
219///
220/// Consistency policy requested before backup mutation begins.
221/// Owned by backup planning and checked by execution preflight receipts.
222///
223
224#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
225#[serde(rename_all = "kebab-case")]
226pub enum QuiescencePolicy {
227    CrashConsistent,
228    RootCoordinated,
229}
230
231///
232/// BackupOperation
233///
234/// Ordered operation generated for one backup plan.
235/// Owned by backup planning and converted into execution journal operations.
236///
237
238#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
239#[serde(deny_unknown_fields)]
240pub struct BackupOperation {
241    pub operation_id: String,
242    pub order: u32,
243    pub kind: BackupOperationKind,
244    pub target_canister_id: Option<String>,
245}
246
247///
248/// BackupOperationKind
249///
250/// Operation class used by backup execution and preflight validation.
251/// Owned by backup planning and interpreted by execution journals.
252///
253
254#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
255#[serde(rename_all = "kebab-case")]
256pub enum BackupOperationKind {
257    ValidateTopology,
258    ValidateControlAuthority,
259    ValidateSnapshotReadAuthority,
260    ValidateQuiescencePolicy,
261    Stop,
262    CreateSnapshot,
263    Start,
264    DownloadSnapshot,
265    VerifyArtifact,
266    FinalizeManifest,
267}
268
269///
270/// BackupExecutionPreflightReceipts
271///
272/// Complete preflight receipt bundle required before backup mutation.
273/// Owned by backup planning and consumed by execution journals.
274///
275
276#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
277#[serde(deny_unknown_fields)]
278pub struct BackupExecutionPreflightReceipts {
279    pub plan_id: String,
280    pub preflight_id: String,
281    pub validated_at: String,
282    pub expires_at: String,
283    pub topology: TopologyPreflightReceipt,
284    pub control_authority: Vec<ControlAuthorityReceipt>,
285    pub snapshot_read_authority: Vec<SnapshotReadAuthorityReceipt>,
286    pub quiescence: QuiescencePreflightReceipt,
287}
288
289///
290/// ControlAuthorityReceipt
291///
292/// Control authority preflight result for one selected backup target.
293/// Owned by backup planning and applied before execution can mutate targets.
294///
295
296#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
297#[serde(deny_unknown_fields)]
298pub struct ControlAuthorityReceipt {
299    pub plan_id: String,
300    pub preflight_id: String,
301    pub target_canister_id: String,
302    pub authority: ControlAuthority,
303    pub proof_source: AuthorityProofSource,
304    pub validated_at: String,
305    pub expires_at: String,
306    pub message: Option<String>,
307}
308
309///
310/// SnapshotReadAuthorityReceipt
311///
312/// Snapshot-read authority preflight result for one selected backup target.
313/// Owned by backup planning and applied before snapshot download can run.
314///
315
316#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
317#[serde(deny_unknown_fields)]
318pub struct SnapshotReadAuthorityReceipt {
319    pub plan_id: String,
320    pub preflight_id: String,
321    pub target_canister_id: String,
322    pub authority: SnapshotReadAuthority,
323    pub proof_source: AuthorityProofSource,
324    pub validated_at: String,
325    pub expires_at: String,
326    pub message: Option<String>,
327}
328
329///
330/// AuthorityProofSource
331///
332/// Evidence source used by an authority preflight receipt.
333/// Owned by backup planning and serialized with authority receipt contracts.
334///
335
336#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
337#[serde(rename_all = "kebab-case")]
338pub enum AuthorityProofSource {
339    ManagementStatus,
340}
341
342///
343/// ControlAuthorityPreflightRequest
344///
345/// Request shape for proving control authority over selected targets.
346/// Owned by backup planning and sent to execution preflight providers.
347///
348
349#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
350#[serde(deny_unknown_fields)]
351pub struct ControlAuthorityPreflightRequest {
352    pub plan_id: String,
353    pub run_id: String,
354    pub fleet: String,
355    pub environment: String,
356    pub root_canister_id: String,
357    pub requires_root_controller: bool,
358    pub targets: Vec<ControlAuthorityPreflightTarget>,
359}
360
361///
362/// ControlAuthorityPreflightTarget
363///
364/// Target row included in a control-authority preflight request.
365/// Owned by backup planning and projected from selected backup targets.
366///
367
368#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
369#[serde(deny_unknown_fields)]
370pub struct ControlAuthorityPreflightTarget {
371    pub canister_id: String,
372    pub role: Option<String>,
373    pub parent_canister_id: Option<String>,
374    pub declared_authority: ControlAuthority,
375}
376
377impl From<&BackupTarget> for ControlAuthorityPreflightTarget {
378    fn from(target: &BackupTarget) -> Self {
379        Self {
380            canister_id: target.canister_id.clone(),
381            role: target.role.clone(),
382            parent_canister_id: target.parent_canister_id.clone(),
383            declared_authority: target.control_authority.clone(),
384        }
385    }
386}
387
388///
389/// SnapshotReadAuthorityPreflightRequest
390///
391/// Request shape for proving snapshot read authority over selected targets.
392/// Owned by backup planning and sent to execution preflight providers.
393///
394
395#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
396#[serde(deny_unknown_fields)]
397pub struct SnapshotReadAuthorityPreflightRequest {
398    pub plan_id: String,
399    pub run_id: String,
400    pub fleet: String,
401    pub environment: String,
402    pub root_canister_id: String,
403    pub targets: Vec<SnapshotReadAuthorityPreflightTarget>,
404}
405
406///
407/// SnapshotReadAuthorityPreflightTarget
408///
409/// Target row included in a snapshot-read preflight request.
410/// Owned by backup planning and projected from selected backup targets.
411///
412
413#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
414#[serde(deny_unknown_fields)]
415pub struct SnapshotReadAuthorityPreflightTarget {
416    pub canister_id: String,
417    pub role: Option<String>,
418    pub parent_canister_id: Option<String>,
419    pub declared_authority: SnapshotReadAuthority,
420}
421
422impl From<&BackupTarget> for SnapshotReadAuthorityPreflightTarget {
423    fn from(target: &BackupTarget) -> Self {
424        Self {
425            canister_id: target.canister_id.clone(),
426            role: target.role.clone(),
427            parent_canister_id: target.parent_canister_id.clone(),
428            declared_authority: target.snapshot_read_authority.clone(),
429        }
430    }
431}
432
433///
434/// TopologyPreflightRequest
435///
436/// Request shape for confirming selected topology before mutation.
437/// Owned by backup planning and sent to execution preflight providers.
438///
439
440#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
441#[serde(deny_unknown_fields)]
442pub struct TopologyPreflightRequest {
443    pub plan_id: String,
444    pub run_id: String,
445    pub fleet: String,
446    pub environment: String,
447    pub root_canister_id: String,
448    pub selected_subtree_root: Option<String>,
449    pub selected_scope_kind: BackupScopeKind,
450    pub topology_hash_before_quiesce: String,
451    pub targets: Vec<TopologyPreflightTarget>,
452}
453
454///
455/// TopologyPreflightTarget
456///
457/// Target row included in a topology preflight request and receipt.
458/// Owned by backup planning and projected from selected backup targets.
459///
460
461#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
462#[serde(deny_unknown_fields)]
463pub struct TopologyPreflightTarget {
464    pub canister_id: String,
465    pub parent_canister_id: Option<String>,
466    pub depth: u32,
467}
468
469impl From<&BackupTarget> for TopologyPreflightTarget {
470    fn from(target: &BackupTarget) -> Self {
471        Self {
472            canister_id: target.canister_id.clone(),
473            parent_canister_id: target.parent_canister_id.clone(),
474            depth: target.depth,
475        }
476    }
477}
478
479///
480/// TopologyPreflightReceipt
481///
482/// Topology preflight result accepted before backup mutation begins.
483/// Owned by backup planning and checked against the selected plan.
484///
485
486#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
487#[serde(deny_unknown_fields)]
488pub struct TopologyPreflightReceipt {
489    pub plan_id: String,
490    pub preflight_id: String,
491    pub topology_hash_before_quiesce: String,
492    pub topology_hash_at_preflight: String,
493    pub targets: Vec<TopologyPreflightTarget>,
494    pub validated_at: String,
495    pub expires_at: String,
496    pub message: Option<String>,
497}
498
499///
500/// QuiescencePreflightRequest
501///
502/// Request shape for confirming the selected quiescence policy.
503/// Owned by backup planning and sent to execution preflight providers.
504///
505
506#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
507#[serde(deny_unknown_fields)]
508pub struct QuiescencePreflightRequest {
509    pub plan_id: String,
510    pub run_id: String,
511    pub fleet: String,
512    pub environment: String,
513    pub root_canister_id: String,
514    pub selected_subtree_root: Option<String>,
515    pub quiescence_policy: QuiescencePolicy,
516    pub targets: Vec<QuiescencePreflightTarget>,
517}
518
519///
520/// QuiescencePreflightTarget
521///
522/// Target row included in a quiescence preflight request and receipt.
523/// Owned by backup planning and projected from selected backup targets.
524///
525
526#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
527#[serde(deny_unknown_fields)]
528pub struct QuiescencePreflightTarget {
529    pub canister_id: String,
530    pub role: Option<String>,
531    pub parent_canister_id: Option<String>,
532}
533
534impl From<&BackupTarget> for QuiescencePreflightTarget {
535    fn from(target: &BackupTarget) -> Self {
536        Self {
537            canister_id: target.canister_id.clone(),
538            role: target.role.clone(),
539            parent_canister_id: target.parent_canister_id.clone(),
540        }
541    }
542}
543
544///
545/// QuiescencePreflightReceipt
546///
547/// Quiescence preflight result accepted before backup mutation begins.
548/// Owned by backup planning and checked against the selected plan.
549///
550
551#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
552#[serde(deny_unknown_fields)]
553pub struct QuiescencePreflightReceipt {
554    pub plan_id: String,
555    pub preflight_id: String,
556    pub quiescence_policy: QuiescencePolicy,
557    pub accepted: bool,
558    pub targets: Vec<QuiescencePreflightTarget>,
559    pub validated_at: String,
560    pub expires_at: String,
561    pub message: Option<String>,
562}