Skip to main content

ic_backup/policy/local_restore_source/
mod.rs

1//! Exact original local source manifest admission; no current restore/effect authority.
2
3use crate::{
4    model::{
5        download_journal::DownloadJournalRecord,
6        operation_plan::OperationPlanRecord,
7        restore_safety::{RestoreSafetyRequirementError, RestoreSafetyRequirementRecord},
8    },
9    policy::download_integrity::{
10        self, DownloadIntegrityPolicyError, DurableDownloadArtifactView, DurableDownloadView,
11    },
12};
13use thiserror::Error;
14
15/// Read-only original source declaration and exact same-ID restore selection.
16///
17/// Pure admission proves no fresh bytes, transfer completeness, authenticated snapshot,
18/// application subset safety, control/lifecycle authority, upload/load or terminal proof.
19#[derive(Debug)]
20pub struct LocalRestoreSourceView<'a> {
21    restore: &'a OperationPlanRecord,
22    requirement: &'a RestoreSafetyRequirementRecord,
23    source: DurableDownloadView<'a>,
24}
25
26impl<'a> LocalRestoreSourceView<'a> {
27    /// Read the exact original restore plan and its unchanged attempt allowances.
28    #[must_use]
29    pub const fn restore(&self) -> &'a OperationPlanRecord {
30        self.restore
31    }
32    /// Read original source, application safety lane and fence obligations.
33    #[must_use]
34    pub const fn requirement(&self) -> &'a RestoreSafetyRequirementRecord {
35        self.requirement
36    }
37    /// Read the complete original source's durable declarations, including unselected artifacts.
38    #[must_use]
39    pub const fn source(&self) -> &DurableDownloadView<'a> {
40        &self.source
41    }
42    /// Borrow exact selected artifacts in canonical principal order, without identity rebinding.
43    pub fn selected_artifacts(&self) -> impl Iterator<Item = &DurableDownloadArtifactView<'a>> {
44        self.source.artifacts().iter().filter(|artifact| {
45            self.restore
46                .selected_targets()
47                .binary_search_by(|target| target.as_str().cmp(artifact.artifact().canister_id()))
48                .is_ok()
49        })
50    }
51}
52
53/// Admit exact original plans and the local manifest digest retained in their requirement.
54///
55/// This opt-in local manifest binding does not reinterpret generic opaque integration
56/// artifact digests. The existing requirement owner checks same network/release/IDs;
57/// the durable download owner checks full original source intent and complete coverage.
58/// A restore subset needs separate application qualification. No IO, transitions,
59/// serialization, remote observations or new spending occur here.
60/// # Errors
61/// Rejects changed originals, another artifact binding or incomplete/non-durable source evidence.
62pub fn validate<'a>(
63    restore: &'a OperationPlanRecord,
64    source: &'a OperationPlanRecord,
65    requirement: &'a RestoreSafetyRequirementRecord,
66    manifest: &'a DownloadJournalRecord,
67) -> Result<LocalRestoreSourceView<'a>, LocalRestoreSourcePolicyError> {
68    requirement.validate_plans(restore, source)?;
69    if &manifest.digest() != requirement.source_artifacts() {
70        return Err(LocalRestoreSourcePolicyError::ManifestMismatch);
71    }
72    let source = download_integrity::validate(source, manifest)?;
73    Ok(LocalRestoreSourceView {
74        restore,
75        requirement,
76        source,
77    })
78}
79
80/// Typed original local source binding denial; all spending/obligations remain retained.
81#[derive(Debug, Error)]
82pub enum LocalRestoreSourcePolicyError {
83    /// Exact original plans or same-network/release/ID source admission failed.
84    #[error(transparent)]
85    Requirement(#[from] RestoreSafetyRequirementError),
86    /// The exact local manifest digest differs from original source artifact retention.
87    #[error("local restore source manifest mismatch")]
88    ManifestMismatch,
89    /// Full original source intent, selected coverage or durable evidence differs.
90    #[error(transparent)]
91    Download(#[from] DownloadIntegrityPolicyError),
92}
93
94#[cfg(test)]
95mod tests;