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        attempt_journal::{ObservationOutcomeRecord, ObservationReceiptRequest},
7        fence_acquisition::{FenceAcquisitionError, FenceAcquisitionPayload},
8        fence_obligation::FenceObligationRecord,
9        fence_reconciliation::{
10            FenceReconciliationIntent, FenceReconciliationObservation, FenceReconciliationRequest,
11            FenceReconciliationRequestError,
12        },
13        operation_plan::OperationPlanError,
14    },
15    ops::persistence::{
16        AttemptJournalError, ExecutionProgressPersistenceError, ExecutionStageGuard,
17        ExecutionWorkflowPersistenceError, FenceObligationPersistenceError,
18        FenceObligationRequirement, read_fence_obligation,
19    },
20    policy::fence_reconciliation::{FenceReconciliationError, validate},
21    ports::fence_reconciliation::{FenceReconciliationProvider, FenceReconciliationProviderError},
22};
23use thiserror::Error;
24
25/// Reserve and observe one exact pending acquisition with mandatory fresh application admission.
26///
27/// Hold no attempt guards on entry. Retain the original stage, complete journals,
28/// acquisition bytes, requirement and obligation; restore retains both source and
29/// restore plans/layouts and outside-snapshot custody. Original payload admission
30/// precedes spending. Complete original-plan admission durably reserves one new
31/// observation; selected journal exclusion spans admission and the provider await.
32///
33/// `admit` qualifies actual current read authority, context, whole-unit membership,
34/// exact application observation semantics and never-dispatched custody for this
35/// reservation. Any remote preflight needs separate prior accounting. The provider
36/// retains/authenticates original request and result evidence and independently
37/// qualifies exclusive attribution, nonapplication or settled uncertainty. An Active
38/// fence or its current absence alone cannot qualify either acquisition outcome.
39///
40/// Success only returns matched passive evidence and leaves both attempts pending.
41/// Integration-owned durable retention and explicit settlement remain necessary.
42/// Cancellation, admission/provider failure or lost replies retain spending and
43/// deny reentry while the observation is pending. This step never reconstructs and
44/// redispatches a pending observation, retries acquisition, writes a receipt, refunds
45/// allowance or releases a fence/reference. No default application provider exists.
46/// # Errors
47/// Rejects missing/changed originals, exact payload or pending mutation, reservation,
48/// fresh admission, provider and passive association failures. Errors after a reply
49/// retain that complete observation; failures never authorize another dispatch.
50pub async fn observe_fence_acquisition<E: std::error::Error + 'static>(
51    stage: &ExecutionStageGuard<'_>,
52    requirement: FenceObligationRequirement<'_>,
53    obligation: &FenceObligationRecord,
54    payload: &FenceAcquisitionPayload,
55    challenge: ArtifactChecksumRecord,
56    provider: &mut impl FenceReconciliationProvider,
57    admit: impl AsyncFnOnce(&FenceReconciliationRequest<'_, '_>) -> Result<(), E>,
58) -> Result<FenceReconciliationObservation, FenceReconciliationExecutionError<E>> {
59    let plan = stage.plan();
60    let mut journal = super::fence_acquisition::open_original::<
61        FenceReconciliationExecutionError<E>,
62    >(stage, requirement, obligation, payload)?;
63    let intent = FenceReconciliationIntent::new(plan, obligation, journal.record()?, challenge)?;
64    journal.reserve_planned_observation(
65        &plan.digest(),
66        intent.mutation_attempt(),
67        intent.digest().hash(),
68    )?;
69    let request = FenceReconciliationRequest::new(&intent, journal.record()?)?;
70    admit(&request)
71        .await
72        .map_err(FenceReconciliationExecutionError::Admission)?;
73    read_fence_obligation(stage.layout()?, plan, requirement, &obligation.digest())?;
74    let observation = provider
75        .observe_fence_acquisition(&request, journal.record()?)
76        .await?;
77    let admit_reply = || -> Result<(), FenceReconciliationReplyError> {
78        validate(&request, journal.record()?, &observation)?;
79        read_fence_obligation(stage.layout()?, plan, requirement, &obligation.digest())?;
80        Ok(())
81    };
82    let admitted = admit_reply();
83    super::qualified_receipt::retain_reply(observation, admitted, |source, observation| {
84        FenceReconciliationExecutionError::AfterReply {
85            source,
86            observation,
87        }
88    })
89    .map(|(reply, ())| reply)
90}
91
92/// Explicitly retain an independently qualified original fence-acquisition observation.
93///
94/// Supply the original challenge and retained returned observation, including a late
95/// reply. Hold no attempt guards on entry. This opens the exact existing journal and
96/// admits the complete original plan/journal set before qualification and recording.
97/// Both original attempts must still be pending; no provider is called or reservation
98/// added. Selected journal exclusion spans the asynchronous qualification callback.
99///
100/// `qualify` independently authenticates the completed observation and durably retained
101/// request/result evidence, exclusive attribution or nonapplication/settled uncertainty,
102/// and actual whole-unit/outside-snapshot custody. Return the exact matched attempt,
103/// request, outcome and observation evidence. Passive matching alone supplies no receipt.
104/// Any remote qualification requires separate prior accounting outside this step.
105///
106/// Success delegates one receipt to the existing journal owner and retains all original
107/// allowances, obligations and source references. It supplies no current Active custody,
108/// capture/load/start permission, checkpoint, fence/reference release or prune admission.
109/// Settled uncertainty closes the observation while keeping the mutation pending.
110/// Cancellation before recording keeps both attempts pending. A write or post-write
111/// admission failure may leave a retained receipt; reopen original accounting to resolve
112/// it rather than repeating an observation or assuming that no settlement occurred.
113/// The borrowed observation remains available on every failure.
114/// # Errors
115/// Rejects changed originals, payload/reservation/result mismatch, missing/held journals,
116/// qualification or exact receipt mismatch, and durable-recording failures.
117pub async fn settle_fence_acquisition_observation<E: std::error::Error + 'static>(
118    stage: &ExecutionStageGuard<'_>,
119    requirement: FenceObligationRequirement<'_>,
120    obligation: &FenceObligationRecord,
121    payload: &FenceAcquisitionPayload,
122    challenge: ArtifactChecksumRecord,
123    observation: &FenceReconciliationObservation,
124    qualify: impl AsyncFnOnce(
125        &FenceReconciliationRequest<'_, '_>,
126        &FenceReconciliationObservation,
127    ) -> Result<ObservationReceiptRequest, E>,
128) -> Result<ObservationOutcomeRecord, FenceReconciliationSettlementError<E>> {
129    let plan = stage.plan();
130    let mut journal = super::fence_acquisition::open_original::<
131        FenceReconciliationSettlementError<E>,
132    >(stage, requirement, obligation, payload)?;
133    journal.admit_planned_progress(&plan.digest())?;
134    let intent = FenceReconciliationIntent::new(plan, obligation, journal.record()?, challenge)?;
135    let request = FenceReconciliationRequest::new(&intent, journal.record()?)?;
136    let admitted = validate(&request, journal.record()?, observation)?;
137    let receipt = qualify(&request, observation)
138        .await
139        .map_err(FenceReconciliationSettlementError::Qualification)?;
140    if receipt.attempt != admitted.observation_attempt()
141        || receipt.request != admitted.request().hash()
142        || receipt.outcome != admitted.outcome()
143        || receipt.evidence != admitted.evidence().hash()
144    {
145        return Err(FenceReconciliationSettlementError::ReceiptMismatch);
146    }
147    validate(&request, journal.record()?, observation)?;
148    read_fence_obligation(stage.layout()?, plan, requirement, &obligation.digest())?;
149    let outcome = receipt.outcome;
150    journal.record_planned_observation(&plan.digest(), receipt)?;
151    read_fence_obligation(stage.layout()?, plan, requirement, &obligation.digest())?;
152    Ok(outcome)
153}
154
155/// Explicit settlement rejection; no error permits observation reissue or evidence cleanup.
156#[derive(Debug, Error)]
157pub enum FenceReconciliationSettlementError<E: std::error::Error + 'static> {
158    /// Original acquisition authority cannot be derived.
159    #[error(transparent)]
160    Plan(#[from] OperationPlanError),
161    /// Retained stage or ancestor evidence differs.
162    #[error(transparent)]
163    Stage(#[from] ExecutionWorkflowPersistenceError),
164    /// Original requirement or obligation cannot be admitted.
165    #[error(transparent)]
166    Obligation(#[from] FenceObligationPersistenceError),
167    /// Existing selected journal cannot be admitted.
168    #[error(transparent)]
169    Journal(#[from] AttemptJournalError),
170    /// Original acquisition payload or authority differs.
171    #[error(transparent)]
172    Payload(#[from] FenceAcquisitionError),
173    /// Complete original accounting or durable receipt publication failed.
174    #[error(transparent)]
175    Progress(#[from] ExecutionProgressPersistenceError),
176    /// Original pending mutation/observation identity differs.
177    #[error(transparent)]
178    Request(#[from] FenceReconciliationRequestError),
179    /// Passive exact result matching failed.
180    #[error(transparent)]
181    Association(#[from] FenceReconciliationError),
182    /// Independent application qualification rejected; original attempts stay pending.
183    #[error("fence observation settlement qualification failed: {0}")]
184    Qualification(#[source] E),
185    /// Qualified receipt does not retain the exact matched observation.
186    #[error("fence observation settlement receipt mismatch")]
187    ReceiptMismatch,
188}
189
190/// Observation-step failure preserving pending attempts, original obligations and replies.
191#[derive(Debug, Error)]
192pub enum FenceReconciliationExecutionError<E: std::error::Error + 'static> {
193    /// Original acquisition authority cannot be derived.
194    #[error(transparent)]
195    Plan(#[from] OperationPlanError),
196    /// Retained stage or ancestor evidence differs.
197    #[error(transparent)]
198    Stage(#[from] ExecutionWorkflowPersistenceError),
199    /// Original purpose-specific requirement or obligation cannot be admitted.
200    #[error(transparent)]
201    Obligation(#[from] FenceObligationPersistenceError),
202    /// Original selected journal admission failed.
203    #[error(transparent)]
204    Journal(#[from] AttemptJournalError),
205    /// Original acquisition bytes or authority differ.
206    #[error(transparent)]
207    Payload(#[from] FenceAcquisitionError),
208    /// Complete journals or durable observation reservation failed.
209    #[error(transparent)]
210    Progress(#[from] ExecutionProgressPersistenceError),
211    /// Original pending mutation or current observation binding differs.
212    #[error(transparent)]
213    Request(#[from] FenceReconciliationRequestError),
214    /// Fresh application admission rejected after reservation.
215    #[error("fresh fence reconciliation admission failed: {0}")]
216    Admission(#[source] E),
217    /// The one provider invocation failed; the observation remains pending.
218    #[error(transparent)]
219    Provider(#[from] FenceReconciliationProviderError),
220    /// Admission after the reply failed, retaining its complete original evidence.
221    #[error("fence reconciliation reply rejected: {source}")]
222    AfterReply {
223        /// Typed original admission failure.
224        source: FenceReconciliationReplyError,
225        /// Complete returned passive evidence, without settlement authority.
226        observation: Box<FenceReconciliationObservation>,
227    },
228}
229
230/// Admission failure after a returned fence reconciliation reply.
231#[derive(Debug, Error)]
232pub enum FenceReconciliationReplyError {
233    /// Original selected journal cannot be re-admitted.
234    #[error(transparent)]
235    Journal(#[from] AttemptJournalError),
236    /// Passive exact reply association failed.
237    #[error(transparent)]
238    Association(#[from] FenceReconciliationError),
239    /// Original stage or ancestor evidence cannot be re-admitted.
240    #[error(transparent)]
241    Stage(#[from] ExecutionWorkflowPersistenceError),
242    /// Original requirement or obligation cannot be re-admitted.
243    #[error(transparent)]
244    Obligation(#[from] FenceObligationPersistenceError),
245}