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