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