ic_backup/workflow/fence_reconciliation/
mod.rs1use crate::{
4 model::{
5 artifacts::ArtifactChecksumRecord,
6 attempt_journal::{ObservationOutcomeRecord, ObservationReceiptRequest},
7 fence_acquisition::{FenceAcquisitionError, FenceAcquisitionPayload, original_authority},
8 fence_obligation::FenceObligationRecord,
9 fence_reconciliation::{
10 FenceReconciliationIntent, FenceReconciliationObservation, FenceReconciliationRequest,
11 FenceReconciliationRequestError,
12 },
13 operation_plan::OperationPlanError,
14 },
15 ops::persistence::{
16 AttemptJournalError, AttemptJournalGuard, ExecutionProgressPersistenceError,
17 ExecutionStageGuard, 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 layout = stage.layout()?;
61 read_fence_obligation(layout, plan, requirement, &obligation.digest())?;
62 let authority = plan.attempt_authority(obligation.acquisition_operation())?;
63 let mut journal = AttemptJournalGuard::open(layout, &authority)?;
64 original_authority(plan, obligation, journal.record()?, payload)?;
65 let intent = FenceReconciliationIntent::new(plan, obligation, journal.record()?, challenge)?;
66 journal.reserve_planned_observation(
67 &plan.digest(),
68 intent.mutation_attempt(),
69 intent.digest().hash(),
70 )?;
71 let request = FenceReconciliationRequest::new(&intent, journal.record()?)?;
72 admit(&request)
73 .await
74 .map_err(FenceReconciliationExecutionError::Admission)?;
75 read_fence_obligation(stage.layout()?, plan, requirement, &obligation.digest())?;
76 let observation = provider
77 .observe_fence_acquisition(&request, journal.record()?)
78 .await?;
79 let association = match journal.record() {
80 Ok(record) => record,
81 Err(source) => {
82 return Err(FenceReconciliationExecutionError::AfterReplyJournal {
83 source,
84 observation: Box::new(observation),
85 });
86 }
87 };
88 if let Err(source) = validate(&request, association, &observation) {
89 return Err(FenceReconciliationExecutionError::Association {
90 source,
91 observation: Box::new(observation),
92 });
93 }
94 let layout = match stage.layout() {
95 Ok(layout) => layout,
96 Err(source) => {
97 return Err(FenceReconciliationExecutionError::AfterReplyStage {
98 source,
99 observation: Box::new(observation),
100 });
101 }
102 };
103 if let Err(source) = read_fence_obligation(layout, plan, requirement, &obligation.digest()) {
104 return Err(FenceReconciliationExecutionError::AfterReplyObligation {
105 source,
106 observation: Box::new(observation),
107 });
108 }
109 Ok(observation)
110}
111
112pub async fn settle_fence_acquisition_observation<E: std::error::Error + 'static>(
138 stage: &ExecutionStageGuard<'_>,
139 requirement: FenceObligationRequirement<'_>,
140 obligation: &FenceObligationRecord,
141 payload: &FenceAcquisitionPayload,
142 challenge: ArtifactChecksumRecord,
143 observation: &FenceReconciliationObservation,
144 qualify: impl AsyncFnOnce(
145 &FenceReconciliationRequest<'_, '_>,
146 &FenceReconciliationObservation,
147 ) -> Result<ObservationReceiptRequest, E>,
148) -> Result<ObservationOutcomeRecord, FenceReconciliationSettlementError<E>> {
149 let plan = stage.plan();
150 let layout = stage.layout()?;
151 read_fence_obligation(layout, plan, requirement, &obligation.digest())?;
152 let authority = plan.attempt_authority(obligation.acquisition_operation())?;
153 let mut journal = AttemptJournalGuard::open(layout, &authority)?;
154 original_authority(plan, obligation, journal.record()?, payload)?;
155 journal.admit_planned_progress(&plan.digest())?;
156 let intent = FenceReconciliationIntent::new(plan, obligation, journal.record()?, challenge)?;
157 let request = FenceReconciliationRequest::new(&intent, journal.record()?)?;
158 let admitted = validate(&request, journal.record()?, observation)?;
159 let receipt = qualify(&request, observation)
160 .await
161 .map_err(FenceReconciliationSettlementError::Qualification)?;
162 if receipt.attempt != admitted.observation_attempt()
163 || receipt.request != admitted.request().hash()
164 || receipt.outcome != admitted.outcome()
165 || receipt.evidence != admitted.evidence().hash()
166 {
167 return Err(FenceReconciliationSettlementError::ReceiptMismatch);
168 }
169 validate(&request, journal.record()?, observation)?;
170 read_fence_obligation(stage.layout()?, plan, requirement, &obligation.digest())?;
171 let outcome = receipt.outcome;
172 journal.record_planned_observation(&plan.digest(), receipt)?;
173 read_fence_obligation(stage.layout()?, plan, requirement, &obligation.digest())?;
174 Ok(outcome)
175}
176
177#[derive(Debug, Error)]
179pub enum FenceReconciliationSettlementError<E: std::error::Error + 'static> {
180 #[error(transparent)]
182 Plan(#[from] OperationPlanError),
183 #[error(transparent)]
185 Stage(#[from] ExecutionWorkflowPersistenceError),
186 #[error(transparent)]
188 Obligation(#[from] FenceObligationPersistenceError),
189 #[error(transparent)]
191 Journal(#[from] AttemptJournalError),
192 #[error(transparent)]
194 Payload(#[from] FenceAcquisitionError),
195 #[error(transparent)]
197 Progress(#[from] ExecutionProgressPersistenceError),
198 #[error(transparent)]
200 Request(#[from] FenceReconciliationRequestError),
201 #[error(transparent)]
203 Association(#[from] FenceReconciliationError),
204 #[error("fence observation settlement qualification failed: {0}")]
206 Qualification(#[source] E),
207 #[error("fence observation settlement receipt mismatch")]
209 ReceiptMismatch,
210}
211
212#[derive(Debug, Error)]
214pub enum FenceReconciliationExecutionError<E: std::error::Error + 'static> {
215 #[error(transparent)]
217 Plan(#[from] OperationPlanError),
218 #[error(transparent)]
220 Stage(#[from] ExecutionWorkflowPersistenceError),
221 #[error(transparent)]
223 Obligation(#[from] FenceObligationPersistenceError),
224 #[error(transparent)]
226 Journal(#[from] AttemptJournalError),
227 #[error(transparent)]
229 Payload(#[from] FenceAcquisitionError),
230 #[error(transparent)]
232 Progress(#[from] ExecutionProgressPersistenceError),
233 #[error(transparent)]
235 Request(#[from] FenceReconciliationRequestError),
236 #[error("fresh fence reconciliation admission failed: {0}")]
238 Admission(#[source] E),
239 #[error(transparent)]
241 Provider(#[from] FenceReconciliationProviderError),
242 #[error("fence reconciliation association failed: {source}")]
244 Association {
245 source: FenceReconciliationError,
247 observation: Box<FenceReconciliationObservation>,
249 },
250 #[error("fence reconciliation journal changed after reply: {source}")]
252 AfterReplyJournal {
253 source: AttemptJournalError,
255 observation: Box<FenceReconciliationObservation>,
257 },
258 #[error("fence reconciliation stage changed after reply: {source}")]
260 AfterReplyStage {
261 source: ExecutionWorkflowPersistenceError,
263 observation: Box<FenceReconciliationObservation>,
265 },
266 #[error("fence reconciliation obligation changed after reply: {source}")]
268 AfterReplyObligation {
269 source: FenceObligationPersistenceError,
271 observation: Box<FenceReconciliationObservation>,
273 },
274}