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