Skip to main content

ic_backup/ops/persistence/execution_workflow/progress/
mod.rs

1//! Read-only original workflow inspection through existing plan and journal admission.
2
3use super::{
4    ArtifactChecksumRecord, BackupLayoutGuard, ExecutionProgressPersistenceError,
5    ExecutionWorkflowPersistenceError,
6    ancestry::{acquire_stage, read_admitted_binding, read_current_binding, validate_predecessors},
7    read_execution_progress, read_execution_stage_occupancy, read_execution_workflow,
8};
9use crate::view::execution_workflow::{
10    ExecutionStageProgressState, ExecutionStageProgressView, ExecutionWorkflowProgressView,
11};
12use thiserror::Error;
13
14/// Inspect all original stage journals without calling a provider or changing evidence.
15///
16/// Hold the workflow layout, but no stage/attempt guards. Each occupied stage must
17/// admit its exact binding, ancestors and complete original journal set. Missing
18/// journals or partial stage preparation are typed failures, never unused allowance.
19/// Pending mutation/observation identities remain visible. Applied rows are not
20/// checkpoint, transfer, application or terminal proof. No remote call, write,
21/// repair, retry, artifact read or learned-evidence inference occurs.
22/// # Errors
23/// Rejects unsafe/changed originals, missing journals, contention and invalid accounting.
24pub fn read_execution_workflow_progress(
25    layout: &BackupLayoutGuard,
26    expected: &ArtifactChecksumRecord,
27) -> Result<ExecutionWorkflowProgressView, ExecutionWorkflowProgressError> {
28    let workflow = read_execution_workflow(layout, expected)?;
29    let occupied = read_execution_stage_occupancy(layout, expected)?;
30    let mut stages = Vec::with_capacity(workflow.stages().len());
31    for node in workflow.allocation().graph().ordered_nodes() {
32        let sequence = node.operation_sequence();
33        let state = if occupied.contains(&sequence) {
34            let child = acquire_stage(layout, sequence)?;
35            let (binding, plan) = read_current_binding(&child, &workflow, sequence)?;
36            validate_predecessors(layout, &workflow, &binding)?;
37            let progress = read_execution_progress(&child, &plan.digest())?;
38            read_admitted_binding(layout, &child, &workflow, sequence, &binding.digest())?;
39            ExecutionStageProgressState::Prepared {
40                binding: binding.digest(),
41                progress: Box::new(progress),
42            }
43        } else {
44            ExecutionStageProgressState::Unprepared
45        };
46        stages.push(ExecutionStageProgressView {
47            stage_sequence: sequence,
48            state,
49        });
50    }
51    if read_execution_stage_occupancy(layout, expected)? != occupied {
52        return Err(ExecutionWorkflowPersistenceError::DigestMismatch.into());
53    }
54    Ok(ExecutionWorkflowProgressView {
55        workflow: expected.clone(),
56        stages,
57    })
58}
59
60/// Local workflow inspection refusal; no original evidence or spending is changed.
61#[derive(Debug, Error)]
62pub enum ExecutionWorkflowProgressError {
63    /// Original workflow, stage, ancestors or filesystem custody failed admission.
64    #[error(transparent)]
65    Stage(#[from] ExecutionWorkflowPersistenceError),
66    /// Complete original journals, accounting or causal progress failed admission.
67    #[error(transparent)]
68    Journals(#[from] ExecutionProgressPersistenceError),
69}