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