Skip to main content

ic_backup/ops/persistence/download_journal/integrity/
mod.rs

1//! Explicit fresh local artifact checks, borrowing journal and layout exclusion.
2
3use super::super::json::check_json_size;
4use super::{DownloadJournalError, DownloadJournalGuard};
5use crate::{
6    model::{
7        artifacts::ChecksumError,
8        download_journal::{DownloadJournalRecord, MAX_DOWNLOAD_JOURNAL_BYTES},
9        operation_plan::OperationPlanRecord,
10    },
11    ops::{
12        artifacts::{ArtifactError, checksum_directory},
13        persistence::{
14            OperationPlanPersistenceError, PersistenceError, read_json, read_operation_plan,
15        },
16    },
17    policy::download_integrity::{DownloadIntegrityPolicyError, DurableDownloadView, validate},
18};
19use thiserror::Error;
20
21impl DownloadJournalGuard<'_> {
22    /// Explicitly reverify every published artifact under the retained original plan.
23    ///
24    /// Requires the exact persisted plan and unchanged held journal before and after
25    /// no-follow checksumming. The returned view borrows journal/layout custody.
26    /// This reads local bytes; ordinary journal reopen/resume remains effect-free
27    /// and does not trigger verification. Nothing is written, pruned or released.
28    ///
29    /// File checks are sequential observations, not an atomic filesystem snapshot.
30    /// Integrations retain stable byte custody and qualify complete backend transfer,
31    /// authentic snapshot/receipt identity and terminal/reference-release evidence.
32    ///
33    /// # Errors
34    /// Rejects unusable/replaced custody, missing/changed plans or journals, incomplete
35    /// exact selected coverage, non-durable entries, unsafe/missing trees and changed bytes.
36    pub fn verify_durable_artifacts<'a>(
37        &'a self,
38        plan: &'a OperationPlanRecord,
39    ) -> Result<DurableDownloadView<'a>, DownloadIntegrityError> {
40        self.check_usable()?;
41        read_operation_plan(self.layout, &plan.digest())?;
42        self.require_unchanged_integrity_journal()?;
43        let view = validate(plan, &self.record)?;
44        self.check_artifact_parent()?;
45        for artifact in view.artifacts() {
46            let path = self.layout.root().join(artifact.artifact().artifact_path());
47            checksum_directory(&path)?.verify(artifact.checksum().hash())?;
48        }
49        // Detect changed retained declarations or a replaced root during traversal.
50        // This does not turn individual byte reads into an atomic whole-set snapshot.
51        self.check_usable()?;
52        read_operation_plan(self.layout, &plan.digest())?;
53        self.require_unchanged_integrity_journal()?;
54        Ok(view)
55    }
56
57    pub(super) fn require_unchanged_integrity_journal(&self) -> Result<(), DownloadIntegrityError> {
58        let retained: DownloadJournalRecord = read_json(&self.path(), MAX_DOWNLOAD_JOURNAL_BYTES)?;
59        check_json_size(&retained, MAX_DOWNLOAD_JOURNAL_BYTES)?;
60        if retained != self.record {
61            return Err(DownloadIntegrityError::JournalChanged);
62        }
63        Ok(())
64    }
65}
66
67/// Typed fresh local verification failure; original evidence remains retained.
68#[derive(Debug, Error)]
69pub enum DownloadIntegrityError {
70    /// Retained journal differs from the exact declaration held by its guard.
71    #[error("retained download journal changed during integrity verification")]
72    JournalChanged,
73    /// Guard/layout custody is unusable, replaced or unsafe.
74    #[error(transparent)]
75    Journal(#[from] DownloadJournalError),
76    /// Retained original plan cannot be admitted under its exact expected digest.
77    #[error(transparent)]
78    Plan(#[from] OperationPlanPersistenceError),
79    /// Original-plan selected-set or durable-checksum declaration mismatch.
80    #[error(transparent)]
81    Policy(#[from] DownloadIntegrityPolicyError),
82    /// Bounded retained record admission failed.
83    #[error(transparent)]
84    Persistence(#[from] PersistenceError),
85    /// No-follow directory traversal or streaming failed.
86    #[error(transparent)]
87    Artifact(#[from] ArtifactError),
88    /// Current local bytes differ from the exact retained checksum.
89    #[error(transparent)]
90    Checksum(#[from] ChecksumError),
91}
92
93#[cfg(all(test, unix))]
94mod tests;