Skip to main content

ic_backup/ops/persistence/fence_obligation/
mod.rs

1//! Bounded no-replace original fence obligations under retained plan/requirement custody.
2
3use super::{
4    BackupLayoutGuard, ConsistencyPersistenceError, JournalLockError, PersistenceError,
5    RestoreSafetyPersistenceError, immutable_record, read_consistency_requirement,
6    read_restore_safety_requirement,
7};
8use crate::model::{
9    artifacts::ArtifactChecksumRecord,
10    consistency::{ApplicationFenceBinding, ConsistencyRequirementRecord},
11    fence_obligation::{FenceObligationError, FenceObligationRecord, MAX_FENCE_OBLIGATION_BYTES},
12    operation_plan::OperationPlanRecord,
13    restore_safety::RestoreSafetyRequirementRecord,
14};
15use thiserror::Error;
16
17/// Borrowed original declarations and guarded source custody used at the IO boundary.
18///
19/// These inputs grant no acquisition, Active fence, spending or release authority.
20#[derive(Clone, Copy, Debug)]
21pub enum FenceObligationRequirement<'a> {
22    /// Exact retained original coordinated capture declaration and chosen fence.
23    Capture {
24        /// Original requirement, already durably retained with its plan.
25        requirement: &'a ConsistencyRequirementRecord,
26        /// Exact original integration-retained identity and membership revision.
27        fence: &'a ApplicationFenceBinding,
28    },
29    /// Exact retained original restore/source declaration under both layout guards.
30    Restore {
31        /// Unchanged source layout exclusion.
32        source_layout: &'a BackupLayoutGuard,
33        /// Exact original source plan, already retained there.
34        source: &'a OperationPlanRecord,
35        /// Original fenced restore safety requirement, already durably retained.
36        requirement: &'a RestoreSafetyRequirementRecord,
37    },
38}
39impl FenceObligationRequirement<'_> {
40    fn validate(
41        self,
42        layout: &BackupLayoutGuard,
43        plan: &OperationPlanRecord,
44        record: &FenceObligationRecord,
45    ) -> Result<(), FenceObligationPersistenceError> {
46        match self {
47            Self::Capture { requirement, fence } => {
48                record.validate_capture(plan, requirement, fence)?;
49                read_consistency_requirement(layout, plan, &requirement.digest())?;
50            }
51            Self::Restore {
52                source_layout,
53                source,
54                requirement,
55            } => {
56                record.validate_restore(plan, source, requirement)?;
57                read_restore_safety_requirement(
58                    layout,
59                    source_layout,
60                    plan,
61                    source,
62                    &requirement.digest(),
63                )?;
64            }
65        }
66        Ok(())
67    }
68}
69/// Durably publish fixed `fence-obligation.json` without replacing retained obligations.
70///
71/// Publish before reservation/dispatch of its explicit acquisition operation.
72/// Attempt journals separately own every reservation, outcome and reconciliation.
73/// This function admits retained original declarations, not executable requests.
74/// # Errors
75/// Rejects absent/changed originals, inappropriate fence scope, existing/unsafe paths or IO.
76pub fn create_fence_obligation(
77    layout: &BackupLayoutGuard,
78    plan: &OperationPlanRecord,
79    requirement: FenceObligationRequirement<'_>,
80    record: &FenceObligationRecord,
81) -> Result<(), FenceObligationPersistenceError> {
82    requirement.validate(layout, plan, record)?;
83    immutable_record::create(
84        layout,
85        "fence-obligation.json",
86        MAX_FENCE_OBLIGATION_BYTES,
87        record,
88    )
89}
90/// Read the exact bounded retained obligation; absence never recreates or releases it.
91///
92/// Lost local publication replies reconcile through this read. Recovery must also
93/// reopen the exact original acquisition journal; a missing journal is not an
94/// unspent allowance. No fresh provider call or fence action occurs here.
95/// # Errors
96/// Rejects changed originals, another digest, unsafe/missing paths or oversized/invalid bytes.
97pub fn read_fence_obligation(
98    layout: &BackupLayoutGuard,
99    plan: &OperationPlanRecord,
100    requirement: FenceObligationRequirement<'_>,
101    expected: &ArtifactChecksumRecord,
102) -> Result<FenceObligationRecord, FenceObligationPersistenceError> {
103    // Validate retained plans before following a location derived from the held layout.
104    super::read_operation_plan(layout, &plan.digest())?;
105    immutable_record::read_with(
106        layout,
107        "fence-obligation.json",
108        MAX_FENCE_OBLIGATION_BYTES,
109        |record: &FenceObligationRecord| {
110            requirement.validate(layout, plan, record)?;
111            if &record.digest() != expected {
112                return Err(FenceObligationPersistenceError::DigestMismatch);
113            }
114            Ok(())
115        },
116    )
117}
118/// Typed denial preserving original obligation bytes and acquisition spending.
119#[derive(Debug, Error)]
120pub enum FenceObligationPersistenceError {
121    /// Retained obligation differs from the original exact expected digest.
122    #[error("fence obligation digest mismatch")]
123    DigestMismatch,
124    /// Exact original fence scope or plan differs.
125    #[error(transparent)]
126    Obligation(#[from] FenceObligationError),
127    /// Original plan cannot be read under its unchanged layout.
128    #[error(transparent)]
129    Plan(#[from] super::OperationPlanPersistenceError),
130    /// Exact original capture requirement cannot be read.
131    #[error(transparent)]
132    Consistency(#[from] ConsistencyPersistenceError),
133    /// Exact original restore/source requirement cannot be read.
134    #[error(transparent)]
135    Restore(#[from] RestoreSafetyPersistenceError),
136    /// Journal exclusion failed.
137    #[error(transparent)]
138    Lock(#[from] JournalLockError),
139    /// Bounded/durable filesystem or JSON operation failed.
140    #[error(transparent)]
141    Persistence(#[from] PersistenceError),
142}
143
144#[cfg(all(test, unix))]
145mod tests;