ic_backup/policy/fence_reconciliation/
mod.rs1use crate::model::{
4 artifacts::ArtifactChecksumRecord,
5 attempt_journal::{AttemptJournalRecord, ObservationOutcomeRecord},
6 consistency::ApplicationFenceState,
7 fence_obligation::FenceObligationScopeRecord,
8 fence_reconciliation::{
9 FenceReconciliationEvidence, FenceReconciliationObservation, FenceReconciliationRequest,
10 FenceReconciliationRequestError, MAX_FENCE_RECONCILIATION_REMOTE_OBSERVATIONS,
11 },
12};
13use thiserror::Error;
14
15#[derive(Clone, Debug)]
17pub struct FenceReconciliationView<'a> {
18 observation: &'a FenceReconciliationObservation,
19 outcome: ObservationOutcomeRecord,
20}
21impl FenceReconciliationView<'_> {
22 #[must_use]
24 pub const fn outcome(&self) -> ObservationOutcomeRecord {
25 self.outcome
26 }
27 #[must_use]
29 pub const fn request(&self) -> &ArtifactChecksumRecord {
30 &self.observation.input().request
31 }
32 #[must_use]
34 pub const fn observation_attempt(&self) -> u32 {
35 self.observation.input().observation_attempt
36 }
37 #[must_use]
39 pub const fn evidence(&self) -> &ArtifactChecksumRecord {
40 &self.observation.input().evidence
41 }
42 #[must_use]
44 pub const fn settlement(&self) -> &FenceReconciliationEvidence {
45 &self.observation.input().settlement
46 }
47}
48pub fn validate<'a>(
60 request: &FenceReconciliationRequest<'_, '_>,
61 journal: &AttemptJournalRecord,
62 observation: &'a FenceReconciliationObservation,
63) -> Result<FenceReconciliationView<'a>, FenceReconciliationError> {
64 request.validate_journal(journal)?;
65 let input = observation.input();
66 let intent = request.intent();
67 if input.request != request.digest() {
68 return Err(FenceReconciliationError::RequestMismatch);
69 }
70 if input.mutation_attempt != intent.mutation_attempt()
71 || input.observation_attempt != request.observation_attempt()
72 {
73 return Err(FenceReconciliationError::AttemptMismatch);
74 }
75 if &input.context != intent.plan().context() {
76 return Err(FenceReconciliationError::ContextMismatch);
77 }
78 if &input.inventory != intent.plan().inventory() {
79 return Err(FenceReconciliationError::InventoryMismatch);
80 }
81 if input.selected_targets != intent.plan().selected_targets() {
82 return Err(FenceReconciliationError::SelectionMismatch);
83 }
84 if input.remote_observations > MAX_FENCE_RECONCILIATION_REMOTE_OBSERVATIONS {
85 return Err(FenceReconciliationError::ObservationLimitExceeded);
86 }
87 let outcome = match_claim(intent.obligation().scope(), &input.settlement)?;
88 Ok(FenceReconciliationView {
89 observation,
90 outcome,
91 })
92}
93fn match_claim(
94 scope: &FenceObligationScopeRecord,
95 actual: &FenceReconciliationEvidence,
96) -> Result<ObservationOutcomeRecord, FenceReconciliationError> {
97 match (scope, actual) {
98 (_, FenceReconciliationEvidence::NotAcquired { .. }) => {
99 Ok(ObservationOutcomeRecord::NotApplied)
100 }
101 (_, FenceReconciliationEvidence::Unresolved { .. }) => {
102 Ok(ObservationOutcomeRecord::Uncertain)
103 }
104 (
105 FenceObligationScopeRecord::Capture {
106 identity,
107 membership_revision,
108 ..
109 },
110 FenceReconciliationEvidence::AcquiredCapture { fence, .. },
111 ) => {
112 if &fence.identity != identity {
113 return Err(FenceReconciliationError::FenceMismatch);
114 }
115 if &fence.membership_revision != membership_revision {
116 return Err(FenceReconciliationError::MembershipRevisionMismatch);
117 }
118 active(fence.state)?;
119 Ok(ObservationOutcomeRecord::Applied)
120 }
121 (
122 FenceObligationScopeRecord::Restore {
123 fence: original, ..
124 },
125 FenceReconciliationEvidence::AcquiredRestore { fence, .. },
126 ) => {
127 if fence.binding.identity != original.identity {
128 return Err(FenceReconciliationError::FenceMismatch);
129 }
130 if fence.binding.membership_revision != original.membership_revision {
131 return Err(FenceReconciliationError::MembershipRevisionMismatch);
132 }
133 if fence.binding.external_obligations_revision != original.external_obligations_revision
134 {
135 return Err(FenceReconciliationError::ExternalObligationsRevisionMismatch);
136 }
137 active(fence.state)?;
138 Ok(ObservationOutcomeRecord::Applied)
139 }
140 _ => Err(FenceReconciliationError::PurposeMismatch),
141 }
142}
143fn active(state: ApplicationFenceState) -> Result<(), FenceReconciliationError> {
144 if state != ApplicationFenceState::Active {
145 return Err(FenceReconciliationError::FenceNotActive);
146 }
147 Ok(())
148}
149#[derive(Debug, Error)]
151pub enum FenceReconciliationError {
152 #[error(transparent)]
154 Reservation(#[from] FenceReconciliationRequestError),
155 #[error("fence reconciliation observation request mismatch")]
157 RequestMismatch,
158 #[error("fence reconciliation result attempt mismatch")]
160 AttemptMismatch,
161 #[error("fence reconciliation actual context mismatch")]
163 ContextMismatch,
164 #[error("fence reconciliation actual inventory mismatch")]
166 InventoryMismatch,
167 #[error("fence reconciliation actual selection mismatch")]
169 SelectionMismatch,
170 #[error("fence reconciliation original purpose mismatch")]
172 PurposeMismatch,
173 #[error("fence reconciliation original fence mismatch")]
175 FenceMismatch,
176 #[error("fence reconciliation membership revision mismatch")]
178 MembershipRevisionMismatch,
179 #[error("fence reconciliation external obligations revision mismatch")]
181 ExternalObligationsRevisionMismatch,
182 #[error("fence reconciliation acquired fence is not active")]
184 FenceNotActive,
185 #[error("fence reconciliation remote observation ceiling exceeded")]
187 ObservationLimitExceeded,
188}
189
190#[cfg(test)]
191mod tests;