Skip to main content

ic_backup/policy/execution_settlement/
mod.rs

1//! Exact complete Applied local journal admission; no terminal or release authority.
2
3use crate::{
4    model::{
5        attempt_journal::AttemptJournalRecord, execution_settlement::ExecutionSettlementRecord,
6        operation_plan::OperationPlanRecord,
7    },
8    policy::execution_progress::{
9        self, AdmittedProgress, ExecutionProgressError, ExecutionProgressView,
10        OperationProgressState,
11    },
12};
13use thiserror::Error;
14
15/// Admit the original complete causal journal set and match every retained history.
16///
17/// The canonical progress owner checks exact coverage/context/operation/limits and
18/// retained prerequisite evidence; no missing input means unused allowance. Every
19/// operation must be Applied, even when remaining allowances are nonzero. Exact
20/// full history includes negative/uncertain receipts and consumed attempts, not
21/// just the final progress shape. This performs no IO/serialization/transitions or
22/// remote observations. Receipt authentication, dispatch chronology, artifacts,
23/// actual application safety and command quiescence remain separately qualified.
24/// # Errors
25/// Rejects changed plan, incomplete/invalid/unsettled journals or different exact histories.
26pub fn validate(
27    plan: &OperationPlanRecord,
28    journals: &[&AttemptJournalRecord],
29    settlement: &ExecutionSettlementRecord,
30) -> Result<ExecutionProgressView, ExecutionSettlementPolicyError> {
31    validate_journals(plan, journals.iter().copied(), settlement)
32}
33
34pub(crate) fn validate_journals<'a>(
35    plan: &OperationPlanRecord,
36    journals: impl ExactSizeIterator<Item = &'a AttemptJournalRecord>,
37    settlement: &ExecutionSettlementRecord,
38) -> Result<ExecutionProgressView, ExecutionSettlementPolicyError> {
39    if &plan.digest() != settlement.plan_intent() {
40        return Err(ExecutionSettlementPolicyError::IntentMismatch);
41    }
42    let AdmittedProgress {
43        view,
44        journals: actual,
45    } = execution_progress::progress_with_journals(plan, journals)?;
46    if let Some(operation) = view
47        .operations
48        .iter()
49        .find(|operation| operation.state != OperationProgressState::Applied)
50    {
51        return Err(ExecutionSettlementPolicyError::UnsettledOperation(
52            operation.operation_sequence,
53        ));
54    }
55    if settlement.journals().len() != plan.operations().len() {
56        return Err(ExecutionSettlementPolicyError::JournalSetMismatch);
57    }
58    for (row, operation) in settlement.journals().iter().zip(plan.operations()) {
59        if row.operation_sequence() != operation.operation_sequence() {
60            return Err(ExecutionSettlementPolicyError::JournalSetMismatch);
61        }
62        let journal = actual
63            .get(&row.operation_sequence())
64            .ok_or(ExecutionSettlementPolicyError::JournalSetMismatch)?;
65        if &journal.digest() != row.history() {
66            return Err(ExecutionSettlementPolicyError::HistoryMismatch(
67                row.operation_sequence(),
68            ));
69        }
70    }
71    Ok(view)
72}
73/// Typed local settlement denial; no error dispatches, refunds or releases evidence.
74#[derive(Debug, Eq, Error, PartialEq)]
75pub enum ExecutionSettlementPolicyError {
76    /// Checkpoint names another full original plan.
77    #[error("execution settlement original plan mismatch")]
78    IntentMismatch,
79    /// Original complete evidence/causality failed canonical admission.
80    #[error(transparent)]
81    Progress(#[from] ExecutionProgressError),
82    /// Operation has no retained Applied outcome.
83    #[error("execution settlement operation {0} is not applied")]
84    UnsettledOperation(u64),
85    /// Checkpoint rows do not cover exactly the original operation set.
86    #[error("execution settlement journal set mismatch")]
87    JournalSetMismatch,
88    /// Same final progress hides changed exact reservations/receipt evidence.
89    #[error("execution settlement journal {0} history mismatch")]
90    HistoryMismatch(u64),
91}
92#[cfg(test)]
93mod tests;