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