Skip to main content

canic_backup/journal/
types.rs

1//! Module: journal::types
2//!
3//! Responsibility: define durable artifact journal state and transitions.
4//! Does not own: persistence, resume reporting, or full journal validation.
5//! Boundary: exposes typed journal records consumed by backup workflows.
6
7use crate::journal::JournalValidationError;
8
9use serde::{Deserialize, Serialize};
10
11///
12/// DownloadJournal
13///
14/// Durable artifact download journal for one backup run.
15/// Owned by backup journaling and consumed by resume and integrity checks.
16///
17
18#[derive(Clone, Debug, Deserialize, Serialize)]
19pub struct DownloadJournal {
20    pub journal_version: u16,
21    pub backup_id: String,
22    #[serde(default)]
23    pub discovery_topology_hash: Option<String>,
24    #[serde(default)]
25    pub pre_snapshot_topology_hash: Option<String>,
26    #[serde(default)]
27    pub operation_metrics: DownloadOperationMetrics,
28    pub artifacts: Vec<ArtifactJournalEntry>,
29}
30
31///
32/// DownloadOperationMetrics
33///
34/// Counters for artifact download lifecycle progress.
35/// Owned by backup journaling and reported in resume summaries.
36///
37
38#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
39pub struct DownloadOperationMetrics {
40    pub target_count: usize,
41    pub snapshot_create_started: usize,
42    pub snapshot_create_completed: usize,
43    pub snapshot_download_started: usize,
44    pub snapshot_download_completed: usize,
45    pub checksum_verify_started: usize,
46    pub checksum_verify_completed: usize,
47    pub artifact_finalize_started: usize,
48    pub artifact_finalize_completed: usize,
49}
50
51///
52/// ArtifactJournalEntry
53///
54/// Durable journal entry for one snapshot artifact.
55/// Owned by backup journaling and advanced by snapshot download workflows.
56///
57
58#[derive(Clone, Debug, Deserialize, Serialize)]
59pub struct ArtifactJournalEntry {
60    pub canister_id: String,
61    pub snapshot_id: String,
62    pub state: ArtifactState,
63    pub temp_path: Option<String>,
64    pub artifact_path: String,
65    pub checksum_algorithm: String,
66    pub checksum: Option<String>,
67    pub updated_at: String,
68}
69
70impl ArtifactJournalEntry {
71    /// Return the idempotent action needed to resume this artifact.
72    #[must_use]
73    pub const fn resume_action(&self) -> ResumeAction {
74        match self.state {
75            ArtifactState::Created => ResumeAction::Download,
76            ArtifactState::Downloaded => ResumeAction::VerifyChecksum,
77            ArtifactState::ChecksumVerified => ResumeAction::Finalize,
78            ArtifactState::Durable => ResumeAction::Skip,
79        }
80    }
81
82    /// Advance this artifact to a later journal state.
83    pub fn advance_to(
84        &mut self,
85        next_state: ArtifactState,
86        updated_at: String,
87    ) -> Result<(), JournalValidationError> {
88        if !self.state.can_advance_to(next_state) {
89            return Err(JournalValidationError::InvalidStateTransition {
90                from: self.state,
91                to: next_state,
92            });
93        }
94
95        self.state = next_state;
96        self.updated_at = updated_at;
97        Ok(())
98    }
99}
100
101///
102/// ArtifactState
103///
104/// Monotonic durable state for one snapshot artifact.
105/// Owned by backup journaling and used to derive idempotent resume actions.
106///
107
108#[derive(Clone, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
109#[serde(rename_all = "PascalCase")]
110pub enum ArtifactState {
111    Created,
112    Downloaded,
113    ChecksumVerified,
114    Durable,
115}
116
117impl ArtifactState {
118    /// Return whether this state can advance monotonically to `next`.
119    #[must_use]
120    pub const fn can_advance_to(self, next: Self) -> bool {
121        self.as_order() <= next.as_order()
122    }
123
124    /// Return the stable ordering used by the journal state machine.
125    const fn as_order(self) -> u8 {
126        match self {
127            Self::Created => 0,
128            Self::Downloaded => 1,
129            Self::ChecksumVerified => 2,
130            Self::Durable => 3,
131        }
132    }
133}
134
135///
136/// ResumeAction
137///
138/// Next idempotent action needed to resume an artifact download.
139/// Owned by backup journaling and derived from artifact state.
140///
141
142#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
143#[serde(rename_all = "PascalCase")]
144pub enum ResumeAction {
145    Download,
146    VerifyChecksum,
147    Finalize,
148    Skip,
149}