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