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 alternate_controller(
130        controller: impl Into<String>,
131        reason: impl Into<String>,
132        evidence: AuthorityEvidence,
133    ) -> Self {
134        Self {
135            source: ControlAuthoritySource::AlternateController {
136                controller: controller.into(),
137                reason: reason.into(),
138            },
139            evidence,
140        }
141    }
142
143    #[must_use]
144    pub fn is_proven(&self) -> bool {
145        self.evidence == AuthorityEvidence::Proven && self.source != ControlAuthoritySource::Unknown
146    }
147
148    #[must_use]
149    pub fn is_proven_root_controller(&self) -> bool {
150        self.evidence == AuthorityEvidence::Proven
151            && self.source == ControlAuthoritySource::RootController
152    }
153}
154
155///
156/// ControlAuthoritySource
157///
158/// Source of a control authority decision for one backup target.
159/// Owned by backup planning and interpreted by preflight validation.
160///
161
162#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
163#[serde(rename_all = "kebab-case", tag = "kind")]
164pub enum ControlAuthoritySource {
165    Unknown,
166    RootController,
167    OperatorController,
168    AlternateController { controller: String, reason: String },
169}
170
171///
172/// SnapshotReadAuthority
173///
174/// Snapshot read authority decision for one selected backup target.
175/// Owned by backup planning and validated before snapshot download.
176///
177
178#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
179#[serde(deny_unknown_fields)]
180pub struct SnapshotReadAuthority {
181    pub source: SnapshotReadAuthoritySource,
182    pub evidence: AuthorityEvidence,
183}
184
185impl SnapshotReadAuthority {
186    #[must_use]
187    pub const fn unknown() -> Self {
188        Self {
189            source: SnapshotReadAuthoritySource::Unknown,
190            evidence: AuthorityEvidence::Unknown,
191        }
192    }
193
194    #[must_use]
195    pub const fn operator_controller(evidence: AuthorityEvidence) -> Self {
196        Self {
197            source: SnapshotReadAuthoritySource::OperatorController,
198            evidence,
199        }
200    }
201
202    #[must_use]
203    pub const fn snapshot_visibility(evidence: AuthorityEvidence) -> Self {
204        Self {
205            source: SnapshotReadAuthoritySource::SnapshotVisibility,
206            evidence,
207        }
208    }
209
210    #[must_use]
211    pub const fn root_configured_read(evidence: AuthorityEvidence) -> Self {
212        Self {
213            source: SnapshotReadAuthoritySource::RootConfiguredRead,
214            evidence,
215        }
216    }
217
218    #[must_use]
219    pub const fn root_mediated_transfer(evidence: AuthorityEvidence) -> Self {
220        Self {
221            source: SnapshotReadAuthoritySource::RootMediatedTransfer,
222            evidence,
223        }
224    }
225
226    #[must_use]
227    pub fn is_proven(&self) -> bool {
228        self.evidence == AuthorityEvidence::Proven
229            && self.source != SnapshotReadAuthoritySource::Unknown
230    }
231}
232
233///
234/// SnapshotReadAuthoritySource
235///
236/// Source of a snapshot-read authority decision for one backup target.
237/// Owned by backup planning and interpreted by preflight validation.
238///
239
240#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
241#[serde(rename_all = "kebab-case")]
242pub enum SnapshotReadAuthoritySource {
243    Unknown,
244    OperatorController,
245    SnapshotVisibility,
246    RootConfiguredRead,
247    RootMediatedTransfer,
248}
249
250///
251/// QuiescencePolicy
252///
253/// Consistency policy requested before backup mutation begins.
254/// Owned by backup planning and checked by execution preflight receipts.
255///
256
257#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
258#[serde(rename_all = "kebab-case")]
259pub enum QuiescencePolicy {
260    CrashConsistent,
261    RootCoordinated,
262    AppQuiesced,
263}
264
265///
266/// BackupOperation
267///
268/// Ordered operation generated for one backup plan.
269/// Owned by backup planning and converted into execution journal operations.
270///
271
272#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
273#[serde(deny_unknown_fields)]
274pub struct BackupOperation {
275    pub operation_id: String,
276    pub order: u32,
277    pub kind: BackupOperationKind,
278    pub target_canister_id: Option<String>,
279}
280
281///
282/// BackupOperationKind
283///
284/// Operation class used by backup execution and preflight validation.
285/// Owned by backup planning and interpreted by execution journals.
286///
287
288#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
289#[serde(rename_all = "kebab-case")]
290pub enum BackupOperationKind {
291    ValidateTopology,
292    ValidateControlAuthority,
293    ValidateSnapshotReadAuthority,
294    ValidateQuiescencePolicy,
295    Stop,
296    CreateSnapshot,
297    Start,
298    DownloadSnapshot,
299    VerifyArtifact,
300    FinalizeManifest,
301}
302
303///
304/// BackupExecutionPreflightReceipts
305///
306/// Complete preflight receipt bundle required before backup mutation.
307/// Owned by backup planning and consumed by execution journals.
308///
309
310#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
311#[serde(deny_unknown_fields)]
312pub struct BackupExecutionPreflightReceipts {
313    pub plan_id: String,
314    pub preflight_id: String,
315    pub validated_at: String,
316    pub expires_at: String,
317    pub topology: TopologyPreflightReceipt,
318    pub control_authority: Vec<ControlAuthorityReceipt>,
319    pub snapshot_read_authority: Vec<SnapshotReadAuthorityReceipt>,
320    pub quiescence: QuiescencePreflightReceipt,
321}
322
323///
324/// ControlAuthorityReceipt
325///
326/// Control authority preflight result for one selected backup target.
327/// Owned by backup planning and applied before execution can mutate targets.
328///
329
330#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
331#[serde(deny_unknown_fields)]
332pub struct ControlAuthorityReceipt {
333    pub plan_id: String,
334    pub preflight_id: String,
335    pub target_canister_id: String,
336    pub authority: ControlAuthority,
337    pub proof_source: AuthorityProofSource,
338    pub validated_at: String,
339    pub expires_at: String,
340    pub message: Option<String>,
341}
342
343///
344/// SnapshotReadAuthorityReceipt
345///
346/// Snapshot-read authority preflight result for one selected backup target.
347/// Owned by backup planning and applied before snapshot download can run.
348///
349
350#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
351#[serde(deny_unknown_fields)]
352pub struct SnapshotReadAuthorityReceipt {
353    pub plan_id: String,
354    pub preflight_id: String,
355    pub target_canister_id: String,
356    pub authority: SnapshotReadAuthority,
357    pub proof_source: AuthorityProofSource,
358    pub validated_at: String,
359    pub expires_at: String,
360    pub message: Option<String>,
361}
362
363///
364/// AuthorityProofSource
365///
366/// Evidence source used by an authority preflight receipt.
367/// Owned by backup planning and serialized with authority receipt contracts.
368///
369
370#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
371#[serde(rename_all = "kebab-case")]
372pub enum AuthorityProofSource {
373    RootCoordination,
374    ManagementStatus,
375    SnapshotReadCheck,
376    Declaration,
377    Unknown,
378}
379
380///
381/// ControlAuthorityPreflightRequest
382///
383/// Request shape for proving control authority over selected targets.
384/// Owned by backup planning and sent to execution preflight providers.
385///
386
387#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
388#[serde(deny_unknown_fields)]
389pub struct ControlAuthorityPreflightRequest {
390    pub plan_id: String,
391    pub run_id: String,
392    pub fleet: String,
393    pub network: String,
394    pub root_canister_id: String,
395    pub requires_root_controller: bool,
396    pub targets: Vec<ControlAuthorityPreflightTarget>,
397}
398
399///
400/// ControlAuthorityPreflightTarget
401///
402/// Target row included in a control-authority preflight request.
403/// Owned by backup planning and projected from selected backup targets.
404///
405
406#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
407#[serde(deny_unknown_fields)]
408pub struct ControlAuthorityPreflightTarget {
409    pub canister_id: String,
410    pub role: Option<String>,
411    pub parent_canister_id: Option<String>,
412    pub declared_authority: ControlAuthority,
413}
414
415impl From<&BackupTarget> for ControlAuthorityPreflightTarget {
416    fn from(target: &BackupTarget) -> Self {
417        Self {
418            canister_id: target.canister_id.clone(),
419            role: target.role.clone(),
420            parent_canister_id: target.parent_canister_id.clone(),
421            declared_authority: target.control_authority.clone(),
422        }
423    }
424}
425
426///
427/// SnapshotReadAuthorityPreflightRequest
428///
429/// Request shape for proving snapshot read authority over selected targets.
430/// Owned by backup planning and sent to execution preflight providers.
431///
432
433#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
434#[serde(deny_unknown_fields)]
435pub struct SnapshotReadAuthorityPreflightRequest {
436    pub plan_id: String,
437    pub run_id: String,
438    pub fleet: String,
439    pub network: String,
440    pub root_canister_id: String,
441    pub targets: Vec<SnapshotReadAuthorityPreflightTarget>,
442}
443
444///
445/// SnapshotReadAuthorityPreflightTarget
446///
447/// Target row included in a snapshot-read preflight request.
448/// Owned by backup planning and projected from selected backup targets.
449///
450
451#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
452#[serde(deny_unknown_fields)]
453pub struct SnapshotReadAuthorityPreflightTarget {
454    pub canister_id: String,
455    pub role: Option<String>,
456    pub parent_canister_id: Option<String>,
457    pub declared_authority: SnapshotReadAuthority,
458}
459
460impl From<&BackupTarget> for SnapshotReadAuthorityPreflightTarget {
461    fn from(target: &BackupTarget) -> Self {
462        Self {
463            canister_id: target.canister_id.clone(),
464            role: target.role.clone(),
465            parent_canister_id: target.parent_canister_id.clone(),
466            declared_authority: target.snapshot_read_authority.clone(),
467        }
468    }
469}
470
471///
472/// TopologyPreflightRequest
473///
474/// Request shape for confirming selected topology before mutation.
475/// Owned by backup planning and sent to execution preflight providers.
476///
477
478#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
479#[serde(deny_unknown_fields)]
480pub struct TopologyPreflightRequest {
481    pub plan_id: String,
482    pub run_id: String,
483    pub fleet: String,
484    pub network: String,
485    pub root_canister_id: String,
486    pub selected_subtree_root: Option<String>,
487    pub selected_scope_kind: BackupScopeKind,
488    pub topology_hash_before_quiesce: String,
489    pub targets: Vec<TopologyPreflightTarget>,
490}
491
492///
493/// TopologyPreflightTarget
494///
495/// Target row included in a topology preflight request and receipt.
496/// Owned by backup planning and projected from selected backup targets.
497///
498
499#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
500#[serde(deny_unknown_fields)]
501pub struct TopologyPreflightTarget {
502    pub canister_id: String,
503    pub parent_canister_id: Option<String>,
504    pub depth: u32,
505}
506
507impl From<&BackupTarget> for TopologyPreflightTarget {
508    fn from(target: &BackupTarget) -> Self {
509        Self {
510            canister_id: target.canister_id.clone(),
511            parent_canister_id: target.parent_canister_id.clone(),
512            depth: target.depth,
513        }
514    }
515}
516
517///
518/// TopologyPreflightReceipt
519///
520/// Topology preflight result accepted before backup mutation begins.
521/// Owned by backup planning and checked against the selected plan.
522///
523
524#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
525#[serde(deny_unknown_fields)]
526pub struct TopologyPreflightReceipt {
527    pub plan_id: String,
528    pub preflight_id: String,
529    pub topology_hash_before_quiesce: String,
530    pub topology_hash_at_preflight: String,
531    pub targets: Vec<TopologyPreflightTarget>,
532    pub validated_at: String,
533    pub expires_at: String,
534    pub message: Option<String>,
535}
536
537///
538/// QuiescencePreflightRequest
539///
540/// Request shape for confirming the selected quiescence policy.
541/// Owned by backup planning and sent to execution preflight providers.
542///
543
544#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
545#[serde(deny_unknown_fields)]
546pub struct QuiescencePreflightRequest {
547    pub plan_id: String,
548    pub run_id: String,
549    pub fleet: String,
550    pub network: String,
551    pub root_canister_id: String,
552    pub selected_subtree_root: Option<String>,
553    pub quiescence_policy: QuiescencePolicy,
554    pub targets: Vec<QuiescencePreflightTarget>,
555}
556
557///
558/// QuiescencePreflightTarget
559///
560/// Target row included in a quiescence preflight request and receipt.
561/// Owned by backup planning and projected from selected backup targets.
562///
563
564#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
565#[serde(deny_unknown_fields)]
566pub struct QuiescencePreflightTarget {
567    pub canister_id: String,
568    pub role: Option<String>,
569    pub parent_canister_id: Option<String>,
570}
571
572impl From<&BackupTarget> for QuiescencePreflightTarget {
573    fn from(target: &BackupTarget) -> Self {
574        Self {
575            canister_id: target.canister_id.clone(),
576            role: target.role.clone(),
577            parent_canister_id: target.parent_canister_id.clone(),
578        }
579    }
580}
581
582///
583/// QuiescencePreflightReceipt
584///
585/// Quiescence preflight result accepted before backup mutation begins.
586/// Owned by backup planning and checked against the selected plan.
587///
588
589#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
590#[serde(deny_unknown_fields)]
591pub struct QuiescencePreflightReceipt {
592    pub plan_id: String,
593    pub preflight_id: String,
594    pub quiescence_policy: QuiescencePolicy,
595    pub accepted: bool,
596    pub targets: Vec<QuiescencePreflightTarget>,
597    pub validated_at: String,
598    pub expires_at: String,
599    pub message: Option<String>,
600}