1use crate::plan::BackupOperationKind;
8
9use serde::{Deserialize, Serialize};
10use thiserror::Error as ThisError;
11
12#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
20#[serde(deny_unknown_fields)]
21pub struct BackupExecutionJournal {
22 pub journal_version: u16,
23 pub plan_id: String,
24 pub run_id: String,
25 #[serde(deserialize_with = "crate::serialization::required_option")]
26 pub preflight_id: Option<String>,
27 pub preflight_accepted: bool,
28 pub restart_required: bool,
29 pub operations: Vec<BackupExecutionJournalOperation>,
30 pub operation_receipts: Vec<BackupExecutionOperationReceipt>,
31}
32
33#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
41#[serde(deny_unknown_fields)]
42pub struct BackupExecutionJournalOperation {
43 pub sequence: usize,
44 pub operation_id: String,
45 pub kind: BackupOperationKind,
46 #[serde(deserialize_with = "crate::serialization::required_option")]
47 pub target_canister_id: Option<String>,
48 pub state: BackupExecutionOperationState,
49 #[serde(deserialize_with = "crate::serialization::required_option")]
50 pub state_updated_at: Option<String>,
51 #[serde(deserialize_with = "crate::serialization::required_option")]
52 pub snapshot_ids_before: Option<Vec<String>>,
53 pub blocking_reasons: Vec<String>,
54}
55
56#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
64#[serde(rename_all = "kebab-case")]
65pub enum BackupExecutionOperationState {
66 Ready,
67 Pending,
68 Blocked,
69 Completed,
70 Failed,
71 Skipped,
72}
73
74#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
82#[serde(deny_unknown_fields)]
83pub struct BackupExecutionOperationReceipt {
84 pub plan_id: String,
85 pub run_id: String,
86 #[serde(deserialize_with = "crate::serialization::required_option")]
87 pub preflight_id: Option<String>,
88 pub sequence: usize,
89 pub operation_id: String,
90 pub kind: BackupOperationKind,
91 #[serde(deserialize_with = "crate::serialization::required_option")]
92 pub target_canister_id: Option<String>,
93 pub outcome: BackupExecutionOperationReceiptOutcome,
94 #[serde(deserialize_with = "crate::serialization::required_option")]
95 pub updated_at: Option<String>,
96 #[serde(deserialize_with = "crate::serialization::required_option")]
97 pub snapshot_id: Option<String>,
98 #[serde(deserialize_with = "crate::serialization::required_option")]
99 pub snapshot_taken_at_timestamp: Option<u64>,
100 #[serde(deserialize_with = "crate::serialization::required_option")]
101 pub snapshot_total_size_bytes: Option<u64>,
102 #[serde(deserialize_with = "crate::serialization::required_option")]
103 pub artifact_path: Option<String>,
104 #[serde(deserialize_with = "crate::serialization::required_option")]
105 pub checksum: Option<String>,
106 #[serde(deserialize_with = "crate::serialization::required_option")]
107 pub failure_reason: Option<String>,
108}
109
110#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
118#[serde(rename_all = "kebab-case")]
119pub enum BackupExecutionOperationReceiptOutcome {
120 Completed,
121 Failed,
122 Skipped,
123}
124
125#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
133#[serde(deny_unknown_fields)]
134pub struct BackupExecutionResumeSummary {
135 pub plan_id: String,
136 pub run_id: String,
137 pub preflight_id: Option<String>,
138 pub preflight_accepted: bool,
139 pub restart_required: bool,
140 pub total_operations: usize,
141 pub ready_operations: usize,
142 pub pending_operations: usize,
143 pub blocked_operations: usize,
144 pub completed_operations: usize,
145 pub failed_operations: usize,
146 pub skipped_operations: usize,
147 pub next_operation: Option<BackupExecutionJournalOperation>,
148}
149
150#[derive(Debug, ThisError)]
158pub enum BackupExecutionJournalError {
159 #[error("invalid backup plan for execution journal: {0}")]
160 InvalidPlan(String),
161
162 #[error("unsupported backup execution journal version {0}")]
163 UnsupportedVersion(u16),
164
165 #[error("backup execution journal field {0} is required")]
166 MissingField(&'static str),
167
168 #[error("backup execution journal has duplicate operation sequence {0}")]
169 DuplicateSequence(usize),
170
171 #[error("backup execution journal is missing operation sequence {0}")]
172 MissingSequence(usize),
173
174 #[error("accepted preflight is missing preflight_id")]
175 AcceptedPreflightMissingId,
176
177 #[error("restart_required does not match execution operation state")]
178 RestartRequiredMismatch,
179
180 #[error("preflight already accepted as {existing}, cannot accept {attempted}")]
181 PreflightAlreadyAccepted { existing: String, attempted: String },
182
183 #[error("preflight receipt plan id {actual} does not match execution journal plan {expected}")]
184 PreflightPlanMismatch { expected: String, actual: String },
185
186 #[error("mutating operation {sequence} is ready before preflight acceptance")]
187 MutationReadyBeforePreflight { sequence: usize },
188
189 #[error("mutating operation {sequence} cannot run before preflight acceptance")]
190 MutationBeforePreflightAccepted { sequence: usize },
191
192 #[error("operation {0} is missing a blocking or failure reason")]
193 OperationMissingReason(usize),
194
195 #[error("operation {0} cannot have blocking reasons in its current state")]
196 UnblockedOperationHasReasons(usize),
197
198 #[error("operation {0} cannot carry snapshot inventory evidence")]
199 UnexpectedSnapshotInventory(usize),
200
201 #[error("operation {sequence} snapshot inventory repeats identity {snapshot_id}")]
202 DuplicateSnapshotIdentity {
203 sequence: usize,
204 snapshot_id: String,
205 },
206
207 #[error("operation {0} was not found")]
208 OperationNotFound(usize),
209
210 #[error("operation {sequence} cannot transition from {from:?} to {to:?}")]
211 InvalidOperationTransition {
212 sequence: usize,
213 from: BackupExecutionOperationState,
214 to: BackupExecutionOperationState,
215 },
216
217 #[error("operation {requested} cannot advance before operation {next}")]
218 OutOfOrderOperationTransition { requested: usize, next: usize },
219
220 #[error("no operation can be advanced")]
221 NoTransitionableOperation,
222
223 #[error("operation {0} is not failed")]
224 OperationNotFailed(usize),
225
226 #[error("operation {0} is not an eligible failure-containment start")]
227 OperationNotFailureContainmentStart(usize),
228
229 #[error("operation receipt references missing operation {0}")]
230 ReceiptOperationNotFound(usize),
231
232 #[error("operation receipt does not match operation {sequence}")]
233 ReceiptOperationMismatch { sequence: usize },
234
235 #[error("operation receipt does not match journal {sequence}")]
236 ReceiptJournalMismatch { sequence: usize },
237
238 #[error("operation receipt does not match accepted preflight {sequence}")]
239 ReceiptPreflightMismatch { sequence: usize },
240
241 #[error("operation receipt {sequence} has no pending operation")]
242 ReceiptWithoutPendingOperation { sequence: usize },
243}