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