Skip to main content

ic_backup/ops/persistence/execution_progress/
mod.rs

1//! Original retained plan/journal admission for local progress and guarded reservations.
2
3use super::{AttemptJournalError, BackupLayoutGuard, OperationPlanPersistenceError};
4use crate::{
5    model::{
6        artifacts::ArtifactChecksumRecord, attempt_journal::AttemptJournalRecord,
7        operation_plan::OperationPlanError,
8    },
9    policy::execution_progress::{ExecutionProgressError, ExecutionProgressView, admit_progress},
10};
11use thiserror::Error;
12
13/// Read complete original retained execution evidence without remote effects or artifact reads.
14///
15/// Requires the exact persisted plan and every original attempt journal. Missing
16/// journals never supply zero consumption. Journal locks are taken sequentially;
17/// hold no attempt guards when calling this function. The layout retains cooperating
18/// exclusion, while noncooperating byte custody remains integration-owned.
19/// This grants no fresh permission, executable call, authenticated receipt or release.
20/// # Errors
21/// Rejects missing/unsafe/changed originals, contention and invalid retained causality.
22pub fn read_execution_progress(
23    layout: &BackupLayoutGuard,
24    expected_plan: &ArtifactChecksumRecord,
25) -> Result<ExecutionProgressView, ExecutionProgressPersistenceError> {
26    let (plan, journals) = super::attempt_journal::read_original_plan_journals::<
27        ExecutionProgressPersistenceError,
28    >(layout, expected_plan, None)?;
29    Ok(admit_progress(&plan, journals.iter())?.project(&plan)?)
30}
31
32pub(super) fn admit_with_current(
33    layout: &BackupLayoutGuard,
34    expected_plan: &ArtifactChecksumRecord,
35    current: &AttemptJournalRecord,
36    require_dependencies: bool,
37) -> Result<(), ExecutionProgressPersistenceError> {
38    let (plan, journals) = super::attempt_journal::read_original_plan_journals::<
39        ExecutionProgressPersistenceError,
40    >(layout, expected_plan, Some(current))?;
41    let admitted = admit_progress(&plan, journals.iter())?;
42    let sequence = current.authority().binding().operation_sequence();
43    if require_dependencies
44        && plan.ordered_operations().any(|node| {
45            node.operation_sequence() == sequence
46                && !admitted.dependencies_applied(node.depends_on())
47        })
48    {
49        return Err(ExecutionProgressPersistenceError::DependenciesUnapplied(
50            sequence,
51        ));
52    }
53    Ok(())
54}
55
56/// Typed local evidence or reservation rejection; no failure resets original spending.
57#[derive(Debug, Error)]
58pub enum ExecutionProgressPersistenceError {
59    /// Exact original persisted plan cannot be admitted.
60    #[error(transparent)]
61    Plan(#[from] OperationPlanPersistenceError),
62    /// Original bulk authority derivation failed.
63    #[error(transparent)]
64    Authority(#[from] OperationPlanError),
65    /// Original journal admission or durable reservation failed.
66    #[error(transparent)]
67    Journal(#[from] AttemptJournalError),
68    /// Complete retained accounting/causality cannot be projected.
69    #[error(transparent)]
70    Policy(#[from] ExecutionProgressError),
71    /// The selected operation has a prerequisite without retained Applied evidence.
72    #[error("operation {0} has unapplied prerequisites")]
73    DependenciesUnapplied(u64),
74}
75
76#[cfg(all(test, unix))]
77mod tests;