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