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/// TopologyPreflightRequest
351///
352/// Request shape for confirming selected topology before mutation.
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 TopologyPreflightRequest {
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    #[serde(deserialize_with = "crate::serialization::required_option")]
365    pub selected_subtree_root: Option<String>,
366    pub selected_scope_kind: BackupScopeKind,
367    pub topology_hash_before_quiesce: String,
368    pub targets: Vec<TopologyPreflightTarget>,
369}
370
371///
372/// TopologyPreflightTarget
373///
374/// Target row included in a topology preflight request and receipt.
375/// Owned by backup planning and projected from selected backup targets.
376///
377
378#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
379#[serde(deny_unknown_fields)]
380pub struct TopologyPreflightTarget {
381    pub canister_id: String,
382    #[serde(deserialize_with = "crate::serialization::required_option")]
383    pub parent_canister_id: Option<String>,
384    pub depth: u32,
385}
386
387impl From<&BackupTarget> for TopologyPreflightTarget {
388    fn from(target: &BackupTarget) -> Self {
389        Self {
390            canister_id: target.canister_id.clone(),
391            parent_canister_id: target.parent_canister_id.clone(),
392            depth: target.depth,
393        }
394    }
395}
396
397///
398/// TopologyPreflightReceipt
399///
400/// Topology preflight result accepted before backup mutation begins.
401/// Owned by backup planning and checked against the selected plan.
402///
403
404#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
405#[serde(deny_unknown_fields)]
406pub struct TopologyPreflightReceipt {
407    pub plan_id: String,
408    pub preflight_id: String,
409    pub topology_hash_before_quiesce: String,
410    pub topology_hash_at_preflight: String,
411    pub targets: Vec<TopologyPreflightTarget>,
412    pub validated_at: String,
413    pub expires_at: String,
414    #[serde(deserialize_with = "crate::serialization::required_option")]
415    pub message: Option<String>,
416}
417
418///
419/// QuiescencePreflightRequest
420///
421/// Request shape for confirming the selected quiescence policy.
422/// Owned by backup planning and sent to execution preflight providers.
423///
424
425#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
426#[serde(deny_unknown_fields)]
427pub struct QuiescencePreflightRequest {
428    pub plan_id: String,
429    pub run_id: String,
430    pub fleet: String,
431    pub environment: String,
432    pub root_canister_id: String,
433    #[serde(deserialize_with = "crate::serialization::required_option")]
434    pub selected_subtree_root: Option<String>,
435    pub quiescence_policy: QuiescencePolicy,
436    pub targets: Vec<QuiescencePreflightTarget>,
437}
438
439///
440/// QuiescencePreflightTarget
441///
442/// Target row included in a quiescence preflight request and receipt.
443/// Owned by backup planning and projected from selected backup targets.
444///
445
446#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
447#[serde(deny_unknown_fields)]
448pub struct QuiescencePreflightTarget {
449    pub canister_id: String,
450    #[serde(deserialize_with = "crate::serialization::required_option")]
451    pub role: Option<String>,
452    #[serde(deserialize_with = "crate::serialization::required_option")]
453    pub parent_canister_id: Option<String>,
454}
455
456impl From<&BackupTarget> for QuiescencePreflightTarget {
457    fn from(target: &BackupTarget) -> Self {
458        Self {
459            canister_id: target.canister_id.clone(),
460            role: target.role.clone(),
461            parent_canister_id: target.parent_canister_id.clone(),
462        }
463    }
464}
465
466///
467/// QuiescencePreflightReceipt
468///
469/// Quiescence preflight result accepted before backup mutation begins.
470/// Owned by backup planning and checked against the selected plan.
471///
472
473#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
474#[serde(deny_unknown_fields)]
475pub struct QuiescencePreflightReceipt {
476    pub plan_id: String,
477    pub preflight_id: String,
478    pub quiescence_policy: QuiescencePolicy,
479    pub accepted: bool,
480    pub targets: Vec<QuiescencePreflightTarget>,
481    pub validated_at: String,
482    pub expires_at: String,
483    #[serde(deserialize_with = "crate::serialization::required_option")]
484    pub message: Option<String>,
485}