Skip to main content

ic_backup/workflow/fence_acquisition/
mod.rs

1//! One freshly reserved application acquisition under exact original stage/obligation custody.
2
3use crate::{
4    model::{
5        fence_acquisition::{
6            FenceAcquisitionAcknowledgement, FenceAcquisitionError, FenceAcquisitionPayload,
7            FenceAcquisitionRequest, original_authority,
8        },
9        fence_obligation::FenceObligationRecord,
10        operation_plan::OperationPlanError,
11    },
12    ops::persistence::{
13        AttemptJournalError, AttemptJournalGuard, ExecutionProgressPersistenceError,
14        ExecutionStageGuard, ExecutionWorkflowPersistenceError, FenceObligationPersistenceError,
15        FenceObligationRequirement, read_fence_obligation,
16    },
17    policy::fence_acquisition::{FenceAcquisitionAcknowledgementError, validate_acknowledgement},
18    ports::fence_acquisition::{FenceAcquisitionProvider, FenceAcquisitionProviderError},
19};
20use thiserror::Error;
21
22/// Reserve the original acquisition, freshly admit it and invoke one exact application update.
23///
24/// Hold no attempt guards on entry. Retain the full original stage, every journal,
25/// requirement and whole-unit obligation before calling. Restore requires its source
26/// layout/plan and outside-snapshot custody. Exact original payload admission precedes
27/// spending; complete original-plan admission owns the durable mutation reservation.
28/// The selected journal remains locked across admission and the provider await.
29/// Cancellation retains pending spending and denies redispatch through this step.
30///
31/// Mandatory fresh `admit` must qualify actual context, whole-unit membership,
32/// application method/argument purpose, original fence/revisions, permissions,
33/// dependencies, command custody and proof this reservation has never dispatched.
34/// Any remote preflight has its own prior accounting. The configured provider retains
35/// exact signed ingress before its one update and exact reply bytes before returning.
36/// There is no default application, permission or retention callback.
37///
38/// Recheck retained stage/ancestors and requirement/obligation around dispatch.
39/// Success returns passive association only and leaves the mutation pending.
40/// Applications independently qualify attribution and Active custody before explicit
41/// settlement; lost replies use original reconciliation accounting. No receipt, refund,
42/// automatic uncertainty, acquired fence or terminal/fence/reference release follows.
43/// # Errors
44/// Rejects changed/missing originals, binding/spending, fresh admission, provider or
45/// association failures. Every post-reservation error retains consumption; post-reply
46/// rejections retain the returned acknowledgement. No failure permits redispatch.
47pub async fn acquire_fence<E: std::error::Error + 'static>(
48    stage: &ExecutionStageGuard<'_>,
49    requirement: FenceObligationRequirement<'_>,
50    obligation: &FenceObligationRecord,
51    payload: &FenceAcquisitionPayload,
52    provider: &mut impl FenceAcquisitionProvider,
53    admit: impl AsyncFnOnce(&FenceAcquisitionRequest<'_>) -> Result<(), E>,
54) -> Result<FenceAcquisitionAcknowledgement, FenceAcquisitionExecutionError<E>> {
55    let plan = stage.plan();
56    let layout = stage.layout()?;
57    read_fence_obligation(layout, plan, requirement, &obligation.digest())?;
58    let authority = plan.attempt_authority(obligation.acquisition_operation())?;
59    let mut journal = AttemptJournalGuard::open(layout, &authority)?;
60    original_authority(plan, obligation, journal.record()?, payload)?;
61    journal.reserve_planned_mutation(&plan.digest())?;
62    let request = FenceAcquisitionRequest::new(plan, obligation, journal.record()?, payload)?;
63    admit(&request)
64        .await
65        .map_err(FenceAcquisitionExecutionError::Admission)?;
66    read_fence_obligation(stage.layout()?, plan, requirement, &obligation.digest())?;
67    let acknowledgement = provider.acquire_fence(&request, journal.record()?).await?;
68    let admit_reply = || -> Result<(), FenceAcquisitionReplyError> {
69        validate_acknowledgement(&request, journal.record()?, &acknowledgement)?;
70        read_fence_obligation(stage.layout()?, plan, requirement, &obligation.digest())?;
71        Ok(())
72    };
73    match admit_reply() {
74        Ok(()) => Ok(acknowledgement),
75        Err(source) => Err(FenceAcquisitionExecutionError::AfterReply {
76            source,
77            acknowledgement: Box::new(acknowledgement),
78        }),
79    }
80}
81
82/// Acquisition-step failure preserving original spending, obligations and passive replies.
83#[derive(Debug, Error)]
84pub enum FenceAcquisitionExecutionError<E: std::error::Error + 'static> {
85    /// Exact original acquisition authority cannot be derived.
86    #[error(transparent)]
87    Plan(#[from] OperationPlanError),
88    /// Original stage, workflow or ancestor evidence differs.
89    #[error(transparent)]
90    Stage(#[from] ExecutionWorkflowPersistenceError),
91    /// Original requirement or obligation cannot be admitted.
92    #[error(transparent)]
93    Obligation(#[from] FenceObligationPersistenceError),
94    /// Original selected journal admission failed.
95    #[error(transparent)]
96    Journal(#[from] AttemptJournalError),
97    /// Complete original journals, prerequisites or durable reservation failed.
98    #[error(transparent)]
99    Progress(#[from] ExecutionProgressPersistenceError),
100    /// Exact original payload or current reservation differs.
101    #[error(transparent)]
102    Request(#[from] FenceAcquisitionError),
103    /// Fresh application admission rejected after reservation.
104    #[error("fresh fence acquisition admission failed: {0}")]
105    Admission(#[source] E),
106    /// Single provider invocation failed; no nonapplication or refund is inferred.
107    #[error(transparent)]
108    Provider(#[from] FenceAcquisitionProviderError),
109    /// Admission after the reply failed, retaining its complete original evidence.
110    #[error("fence acquisition reply rejected: {source}")]
111    AfterReply {
112        /// Typed original admission failure.
113        source: FenceAcquisitionReplyError,
114        /// Complete returned passive evidence, without settlement authority.
115        acknowledgement: Box<FenceAcquisitionAcknowledgement>,
116    },
117}
118
119/// Admission failure after a returned fence acquisition reply.
120#[derive(Debug, Error)]
121pub enum FenceAcquisitionReplyError {
122    /// Original selected journal cannot be re-admitted.
123    #[error(transparent)]
124    Journal(#[from] AttemptJournalError),
125    /// Passive exact reply association failed.
126    #[error(transparent)]
127    Association(#[from] FenceAcquisitionAcknowledgementError),
128    /// Original stage or ancestor evidence cannot be re-admitted.
129    #[error(transparent)]
130    Stage(#[from] ExecutionWorkflowPersistenceError),
131    /// Original requirement or obligation cannot be re-admitted.
132    #[error(transparent)]
133    Obligation(#[from] FenceObligationPersistenceError),
134}
135
136#[cfg(all(test, unix))]
137mod tests;