ic-backup 0.63.1

Host-side snapshot backup and same-release recovery for Internet Computer canisters
Documentation
//! Original retained plan/journal admission for local progress and guarded reservations.

use super::{AttemptJournalError, BackupLayoutGuard, OperationPlanPersistenceError};
use crate::{
    model::{
        artifacts::ArtifactChecksumRecord, attempt_journal::AttemptJournalRecord,
        operation_plan::OperationPlanError,
    },
    policy::execution_progress::{ExecutionProgressError, ExecutionProgressView, admit_progress},
};
use thiserror::Error;

/// Read complete original retained execution evidence without remote effects or artifact reads.
///
/// Requires the exact persisted plan and every original attempt journal. Missing
/// journals never supply zero consumption. Journal locks are taken sequentially;
/// hold no attempt guards when calling this function. The layout retains cooperating
/// exclusion, while noncooperating byte custody remains integration-owned.
/// This grants no fresh permission, executable call, authenticated receipt or release.
/// # Errors
/// Rejects missing/unsafe/changed originals, contention and invalid retained causality.
pub fn read_execution_progress(
    layout: &BackupLayoutGuard,
    expected_plan: &ArtifactChecksumRecord,
) -> Result<ExecutionProgressView, ExecutionProgressPersistenceError> {
    let (plan, journals) = super::attempt_journal::read_original_plan_journals::<
        ExecutionProgressPersistenceError,
    >(layout, expected_plan, None)?;
    Ok(admit_progress(&plan, journals.iter())?.project()?)
}

pub(super) fn admit_with_current(
    layout: &BackupLayoutGuard,
    expected_plan: &ArtifactChecksumRecord,
    current: &AttemptJournalRecord,
    require_dependencies: bool,
) -> Result<(), ExecutionProgressPersistenceError> {
    let (plan, journals) = super::attempt_journal::read_original_plan_journals::<
        ExecutionProgressPersistenceError,
    >(layout, expected_plan, Some(current))?;
    let admitted = admit_progress(&plan, journals.iter())?;
    let sequence = current.authority().binding().operation_sequence();
    if require_dependencies
        && !admitted.dependencies_applied(plan.operation(sequence)?.depends_on())
    {
        return Err(ExecutionProgressPersistenceError::DependenciesUnapplied(
            sequence,
        ));
    }
    Ok(())
}

/// Typed local evidence or reservation rejection; no failure resets original spending.
#[derive(Debug, Error)]
pub enum ExecutionProgressPersistenceError {
    /// Exact original persisted plan cannot be admitted.
    #[error(transparent)]
    Plan(#[from] OperationPlanPersistenceError),
    /// Original bulk authority derivation failed.
    #[error(transparent)]
    Authority(#[from] OperationPlanError),
    /// Original journal admission or durable reservation failed.
    #[error(transparent)]
    Journal(#[from] AttemptJournalError),
    /// Complete retained accounting/causality cannot be projected.
    #[error(transparent)]
    Policy(#[from] ExecutionProgressError),
    /// The selected operation has a prerequisite without retained Applied evidence.
    #[error("operation {0} has unapplied prerequisites")]
    DependenciesUnapplied(u64),
}

#[cfg(all(test, unix))]
mod tests;