ic_backup/workflow/fence_reconciliation/
mod.rs1use 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
25pub 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 match admit_reply() {
83 Ok(()) => Ok(observation),
84 Err(source) => Err(FenceReconciliationExecutionError::AfterReply {
85 source,
86 observation: Box::new(observation),
87 }),
88 }
89}
90
91pub async fn settle_fence_acquisition_observation<E: std::error::Error + 'static>(
117 stage: &ExecutionStageGuard<'_>,
118 requirement: FenceObligationRequirement<'_>,
119 obligation: &FenceObligationRecord,
120 payload: &FenceAcquisitionPayload,
121 challenge: ArtifactChecksumRecord,
122 observation: &FenceReconciliationObservation,
123 qualify: impl AsyncFnOnce(
124 &FenceReconciliationRequest<'_, '_>,
125 &FenceReconciliationObservation,
126 ) -> Result<ObservationReceiptRequest, E>,
127) -> Result<ObservationOutcomeRecord, FenceReconciliationSettlementError<E>> {
128 let plan = stage.plan();
129 let mut journal = super::fence_acquisition::open_original::<
130 FenceReconciliationSettlementError<E>,
131 >(stage, requirement, obligation, payload)?;
132 journal.admit_planned_progress(&plan.digest())?;
133 let intent = FenceReconciliationIntent::new(plan, obligation, journal.record()?, challenge)?;
134 let request = FenceReconciliationRequest::new(&intent, journal.record()?)?;
135 let admitted = validate(&request, journal.record()?, observation)?;
136 let receipt = qualify(&request, observation)
137 .await
138 .map_err(FenceReconciliationSettlementError::Qualification)?;
139 if receipt.attempt != admitted.observation_attempt()
140 || receipt.request != admitted.request().hash()
141 || receipt.outcome != admitted.outcome()
142 || receipt.evidence != admitted.evidence().hash()
143 {
144 return Err(FenceReconciliationSettlementError::ReceiptMismatch);
145 }
146 validate(&request, journal.record()?, observation)?;
147 read_fence_obligation(stage.layout()?, plan, requirement, &obligation.digest())?;
148 let outcome = receipt.outcome;
149 journal.record_planned_observation(&plan.digest(), receipt)?;
150 read_fence_obligation(stage.layout()?, plan, requirement, &obligation.digest())?;
151 Ok(outcome)
152}
153
154#[derive(Debug, Error)]
156pub enum FenceReconciliationSettlementError<E: std::error::Error + 'static> {
157 #[error(transparent)]
159 Plan(#[from] OperationPlanError),
160 #[error(transparent)]
162 Stage(#[from] ExecutionWorkflowPersistenceError),
163 #[error(transparent)]
165 Obligation(#[from] FenceObligationPersistenceError),
166 #[error(transparent)]
168 Journal(#[from] AttemptJournalError),
169 #[error(transparent)]
171 Payload(#[from] FenceAcquisitionError),
172 #[error(transparent)]
174 Progress(#[from] ExecutionProgressPersistenceError),
175 #[error(transparent)]
177 Request(#[from] FenceReconciliationRequestError),
178 #[error(transparent)]
180 Association(#[from] FenceReconciliationError),
181 #[error("fence observation settlement qualification failed: {0}")]
183 Qualification(#[source] E),
184 #[error("fence observation settlement receipt mismatch")]
186 ReceiptMismatch,
187}
188
189#[derive(Debug, Error)]
191pub enum FenceReconciliationExecutionError<E: std::error::Error + 'static> {
192 #[error(transparent)]
194 Plan(#[from] OperationPlanError),
195 #[error(transparent)]
197 Stage(#[from] ExecutionWorkflowPersistenceError),
198 #[error(transparent)]
200 Obligation(#[from] FenceObligationPersistenceError),
201 #[error(transparent)]
203 Journal(#[from] AttemptJournalError),
204 #[error(transparent)]
206 Payload(#[from] FenceAcquisitionError),
207 #[error(transparent)]
209 Progress(#[from] ExecutionProgressPersistenceError),
210 #[error(transparent)]
212 Request(#[from] FenceReconciliationRequestError),
213 #[error("fresh fence reconciliation admission failed: {0}")]
215 Admission(#[source] E),
216 #[error(transparent)]
218 Provider(#[from] FenceReconciliationProviderError),
219 #[error("fence reconciliation reply rejected: {source}")]
221 AfterReply {
222 source: FenceReconciliationReplyError,
224 observation: Box<FenceReconciliationObservation>,
226 },
227}
228
229#[derive(Debug, Error)]
231pub enum FenceReconciliationReplyError {
232 #[error(transparent)]
234 Journal(#[from] AttemptJournalError),
235 #[error(transparent)]
237 Association(#[from] FenceReconciliationError),
238 #[error(transparent)]
240 Stage(#[from] ExecutionWorkflowPersistenceError),
241 #[error(transparent)]
243 Obligation(#[from] FenceObligationPersistenceError),
244}