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