Skip to main content

ic_backup/workflow/fence_reconciliation/
mod.rs

1//! One original acquisition observation under retained stage and obligation custody.
2
3use crate::{
4    model::{
5        artifacts::ArtifactChecksumRecord,
6        fence_acquisition::{FenceAcquisitionError, FenceAcquisitionPayload, original_authority},
7        fence_obligation::FenceObligationRecord,
8        fence_reconciliation::{
9            FenceReconciliationIntent, FenceReconciliationObservation, FenceReconciliationRequest,
10            FenceReconciliationRequestError,
11        },
12        operation_plan::OperationPlanError,
13    },
14    ops::persistence::{
15        AttemptJournalError, AttemptJournalGuard, ExecutionProgressPersistenceError,
16        ExecutionStageGuard, ExecutionWorkflowPersistenceError, FenceObligationPersistenceError,
17        FenceObligationRequirement, read_fence_obligation,
18    },
19    policy::fence_reconciliation::{FenceReconciliationError, validate},
20    ports::fence_reconciliation::{FenceReconciliationProvider, FenceReconciliationProviderError},
21};
22use thiserror::Error;
23
24/// Reserve and observe one exact pending acquisition with mandatory fresh application admission.
25///
26/// Hold no attempt guards on entry. Retain the original stage, complete journals,
27/// acquisition bytes, requirement and obligation; restore retains both source and
28/// restore plans/layouts and outside-snapshot custody. Original payload admission
29/// precedes spending. Complete original-plan admission durably reserves one new
30/// observation; selected journal exclusion spans admission and the provider await.
31///
32/// `admit` qualifies actual current read authority, context, whole-unit membership,
33/// exact application observation semantics and never-dispatched custody for this
34/// reservation. Any remote preflight needs separate prior accounting. The provider
35/// retains/authenticates original request and result evidence and independently
36/// qualifies exclusive attribution, nonapplication or settled uncertainty. An Active
37/// fence or its current absence alone cannot qualify either acquisition outcome.
38///
39/// Success only returns matched passive evidence and leaves both attempts pending.
40/// Integration-owned durable retention and explicit settlement remain necessary.
41/// Cancellation, admission/provider failure or lost replies retain spending and
42/// deny reentry while the observation is pending. This step never reconstructs and
43/// redispatches a pending observation, retries acquisition, writes a receipt, refunds
44/// allowance or releases a fence/reference. No default application provider exists.
45/// # Errors
46/// Rejects missing/changed originals, exact payload or pending mutation, reservation,
47/// fresh admission, provider and passive association failures. Errors after a reply
48/// retain that complete observation; failures never authorize another dispatch.
49pub async fn observe_fence_acquisition<E: std::error::Error + 'static>(
50    stage: &ExecutionStageGuard<'_>,
51    requirement: FenceObligationRequirement<'_>,
52    obligation: &FenceObligationRecord,
53    payload: &FenceAcquisitionPayload,
54    challenge: ArtifactChecksumRecord,
55    provider: &mut impl FenceReconciliationProvider,
56    admit: impl AsyncFnOnce(&FenceReconciliationRequest<'_, '_>) -> Result<(), E>,
57) -> Result<FenceReconciliationObservation, FenceReconciliationExecutionError<E>> {
58    let plan = stage.plan();
59    let layout = stage.layout()?;
60    read_fence_obligation(layout, plan, requirement, &obligation.digest())?;
61    let authority = plan.attempt_authority(obligation.acquisition_operation())?;
62    let mut journal = AttemptJournalGuard::open(layout, &authority)?;
63    original_authority(plan, obligation, journal.record()?, payload)?;
64    let intent = FenceReconciliationIntent::new(plan, obligation, journal.record()?, challenge)?;
65    journal.reserve_planned_observation(
66        &plan.digest(),
67        intent.mutation_attempt(),
68        intent.digest().hash(),
69    )?;
70    let request = FenceReconciliationRequest::new(&intent, journal.record()?)?;
71    admit(&request)
72        .await
73        .map_err(FenceReconciliationExecutionError::Admission)?;
74    read_fence_obligation(stage.layout()?, plan, requirement, &obligation.digest())?;
75    let observation = provider
76        .observe_fence_acquisition(&request, journal.record()?)
77        .await?;
78    let association = match journal.record() {
79        Ok(record) => record,
80        Err(source) => {
81            return Err(FenceReconciliationExecutionError::AfterReplyJournal {
82                source,
83                observation: Box::new(observation),
84            });
85        }
86    };
87    if let Err(source) = validate(&request, association, &observation) {
88        return Err(FenceReconciliationExecutionError::Association {
89            source,
90            observation: Box::new(observation),
91        });
92    }
93    let layout = match stage.layout() {
94        Ok(layout) => layout,
95        Err(source) => {
96            return Err(FenceReconciliationExecutionError::AfterReplyStage {
97                source,
98                observation: Box::new(observation),
99            });
100        }
101    };
102    if let Err(source) = read_fence_obligation(layout, plan, requirement, &obligation.digest()) {
103        return Err(FenceReconciliationExecutionError::AfterReplyObligation {
104            source,
105            observation: Box::new(observation),
106        });
107    }
108    Ok(observation)
109}
110
111/// Observation-step failure preserving pending attempts, original obligations and replies.
112#[derive(Debug, Error)]
113pub enum FenceReconciliationExecutionError<E: std::error::Error + 'static> {
114    /// Original acquisition authority cannot be derived.
115    #[error(transparent)]
116    Plan(#[from] OperationPlanError),
117    /// Retained stage or ancestor evidence differs.
118    #[error(transparent)]
119    Stage(#[from] ExecutionWorkflowPersistenceError),
120    /// Original purpose-specific requirement or obligation cannot be admitted.
121    #[error(transparent)]
122    Obligation(#[from] FenceObligationPersistenceError),
123    /// Original selected journal admission failed.
124    #[error(transparent)]
125    Journal(#[from] AttemptJournalError),
126    /// Original acquisition bytes or authority differ.
127    #[error(transparent)]
128    Payload(#[from] FenceAcquisitionError),
129    /// Complete journals or durable observation reservation failed.
130    #[error(transparent)]
131    Progress(#[from] ExecutionProgressPersistenceError),
132    /// Original pending mutation or current observation binding differs.
133    #[error(transparent)]
134    Request(#[from] FenceReconciliationRequestError),
135    /// Fresh application admission rejected after reservation.
136    #[error("fresh fence reconciliation admission failed: {0}")]
137    Admission(#[source] E),
138    /// The one provider invocation failed; the observation remains pending.
139    #[error(transparent)]
140    Provider(#[from] FenceReconciliationProviderError),
141    /// Passive result association rejected the returned observation.
142    #[error("fence reconciliation association failed: {source}")]
143    Association {
144        /// Original result mismatch.
145        source: FenceReconciliationError,
146        /// Complete returned observation, without authenticated settlement authority.
147        observation: Box<FenceReconciliationObservation>,
148    },
149    /// Selected journal re-admission failed after reply.
150    #[error("fence reconciliation journal changed after reply: {source}")]
151    AfterReplyJournal {
152        /// Original journal rejection.
153        source: AttemptJournalError,
154        /// Complete returned observation.
155        observation: Box<FenceReconciliationObservation>,
156    },
157    /// Retained stage/ancestor admission failed after reply.
158    #[error("fence reconciliation stage changed after reply: {source}")]
159    AfterReplyStage {
160        /// Original stage/ancestor rejection.
161        source: ExecutionWorkflowPersistenceError,
162        /// Complete returned observation.
163        observation: Box<FenceReconciliationObservation>,
164    },
165    /// Original requirement/obligation admission failed after reply.
166    #[error("fence reconciliation obligation changed after reply: {source}")]
167    AfterReplyObligation {
168        /// Original requirement/obligation rejection.
169        source: FenceObligationPersistenceError,
170        /// Complete returned observation.
171        observation: Box<FenceReconciliationObservation>,
172    },
173}