Skip to main content

ic_backup/ops/persistence/execution_settlement/
mod.rs

1//! Immutable original all-Applied journal checkpoint publication and local replay.
2
3use super::{
4    AttemptJournalError, AttemptJournalGuard, BackupLayoutGuard, JournalLock, JournalLockError,
5    OperationPlanPersistenceError, PersistenceError, create_json_durable, read_json,
6    read_operation_plan,
7};
8use crate::{
9    model::{
10        artifacts::ArtifactChecksumRecord,
11        execution_settlement::{ExecutionSettlementRecord, MAX_EXECUTION_SETTLEMENT_BYTES},
12    },
13    policy::execution_settlement::{ExecutionSettlementPolicyError, validate},
14};
15use std::path::Path;
16use thiserror::Error;
17
18/// Publish fixed `execution-settlement.json` under original retained plan/journal evidence.
19///
20/// The exclusive layout owns cooperating write exclusion. Journal locks are acquired
21/// sequentially in canonical sequence order, bounding descriptor use; every admitted
22/// Applied journal rejects further owner transitions. Hold no journal guards when
23/// invoking this operation. Noncooperating byte custody and authentic receipts remain
24/// integration-owned. This creates no journal, budget, product terminal or release permit.
25/// # Errors
26/// Rejects missing/unsafe/oversized originals, contention, unsettled/changed evidence or existing publication.
27pub fn create_execution_settlement(
28    layout: &BackupLayoutGuard,
29    record: &ExecutionSettlementRecord,
30) -> Result<(), ExecutionSettlementPersistenceError> {
31    create_with(layout, record, create_json_durable)
32}
33fn create_with(
34    layout: &BackupLayoutGuard,
35    record: &ExecutionSettlementRecord,
36    writer: impl FnOnce(&Path, &ExecutionSettlementRecord) -> Result<(), PersistenceError>,
37) -> Result<(), ExecutionSettlementPersistenceError> {
38    layout.check_root()?;
39    let path = layout.root().join("execution-settlement.json");
40    let _lock = JournalLock::acquire(&path)?;
41    check_size(record)?;
42    validate_retained(layout, record)?;
43    writer(&path, record)?;
44    Ok(())
45}
46/// Reopen exact checkpoint and validate complete retained original evidence using local IO only.
47///
48/// A lost publication reply can be reconciled without rewriting evidence or observing
49/// remote state. This is original journal settlement replay, not fresh verification
50/// of artifacts/application state, full run completion or release admission.
51/// # Errors
52/// Rejects changed expected identity, absent/invalid/unsafe evidence, contention or journal drift.
53pub fn read_execution_settlement(
54    layout: &BackupLayoutGuard,
55    expected_plan: &ArtifactChecksumRecord,
56    expected: &ArtifactChecksumRecord,
57) -> Result<ExecutionSettlementRecord, ExecutionSettlementPersistenceError> {
58    layout.check_root()?;
59    let path = layout.root().join("execution-settlement.json");
60    let _lock = JournalLock::acquire(&path)?;
61    let record: ExecutionSettlementRecord = read_json(&path, MAX_EXECUTION_SETTLEMENT_BYTES)?;
62    check_size(&record)?;
63    if record.plan_intent() != expected_plan || &record.digest() != expected {
64        return Err(ExecutionSettlementPersistenceError::DigestMismatch);
65    }
66    validate_retained(layout, &record)?;
67    Ok(record)
68}
69fn validate_retained(
70    layout: &BackupLayoutGuard,
71    record: &ExecutionSettlementRecord,
72) -> Result<(), ExecutionSettlementPersistenceError> {
73    let plan = read_operation_plan(layout, record.plan_intent())?;
74    let mut journals = Vec::with_capacity(plan.operations().len());
75    for authority in plan.attempt_authorities()? {
76        let guard = AttemptJournalGuard::open(layout, &authority)?;
77        journals.push(guard.record()?.clone());
78    }
79    let references: Vec<_> = journals.iter().collect();
80    validate(&plan, &references, record)?;
81    layout.check_root()?;
82    Ok(())
83}
84fn check_size(record: &ExecutionSettlementRecord) -> Result<(), PersistenceError> {
85    super::json::check_json_size(record, MAX_EXECUTION_SETTLEMENT_BYTES)
86}
87/// Typed immutable publication/replay failure, preserving all original journals and obligations.
88#[derive(Debug, Error)]
89pub enum ExecutionSettlementPersistenceError {
90    /// Expected original plan/checkpoint identity differs.
91    #[error("execution settlement digest mismatch")]
92    DigestMismatch,
93    /// Original retained plan cannot be admitted.
94    #[error(transparent)]
95    Plan(#[from] OperationPlanPersistenceError),
96    /// Original plan authority derivation failed.
97    #[error(transparent)]
98    Authority(#[from] crate::model::operation_plan::OperationPlanError),
99    /// An original journal is absent, unsafe, mismatched or held by another owner.
100    #[error(transparent)]
101    Journal(#[from] AttemptJournalError),
102    /// Complete original settlement evidence failed pure policy.
103    #[error(transparent)]
104    Policy(#[from] ExecutionSettlementPolicyError),
105    /// Publication/replay checkpoint lock failed.
106    #[error(transparent)]
107    Lock(#[from] JournalLockError),
108    /// Bounded local IO or canonical encoding failed.
109    #[error(transparent)]
110    Persistence(#[from] PersistenceError),
111}
112#[cfg(all(test, unix))]
113mod tests;