Skip to main content

ic_backup/policy/fence_reconciliation/
mod.rs

1//! Pure reserved-observation and original-acquisition attribution matching; no settlement IO.
2
3use 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/// Matched passive provider claim; not an authenticated receipt or current execution permit.
16#[derive(Clone, Debug)]
17pub struct FenceReconciliationView<'a> {
18    observation: &'a FenceReconciliationObservation,
19    outcome: ObservationOutcomeRecord,
20}
21impl FenceReconciliationView<'_> {
22    /// Read the passive matched outcome. Only a qualified integration may settle its journal.
23    #[must_use]
24    pub const fn outcome(&self) -> ObservationOutcomeRecord {
25        self.outcome
26    }
27    /// Read exact original reserved observation digest.
28    #[must_use]
29    pub const fn request(&self) -> &ArtifactChecksumRecord {
30        &self.observation.input().request
31    }
32    /// Read exact original observation attempt, without new reservation or allowance.
33    #[must_use]
34    pub const fn observation_attempt(&self) -> u32 {
35        self.observation.input().observation_attempt
36    }
37    /// Read the opaque request/attempt/result evidence qualified by the actual integration.
38    #[must_use]
39    pub const fn evidence(&self) -> &ArtifactChecksumRecord {
40        &self.observation.input().evidence
41    }
42    /// Read retained application attribution/nonapplication/uncertainty evidence.
43    #[must_use]
44    pub const fn settlement(&self) -> &FenceReconciliationEvidence {
45        &self.observation.input().settlement
46    }
47}
48/// Match current retained reservation and exact actual original-acquisition evidence.
49///
50/// This performs no IO, provider invocation, serialization or journal transition.
51/// An Active matching fence alone cannot construct acquired evidence: the provider
52/// independently qualifies exclusive attribution, fresh actual whole-unit context
53/// and custody. Absence/inactivity alone cannot qualify `NotApplied`. An indeterminate
54/// or lost reply stays pending; Uncertain needs a qualified settled observation.
55/// Views grant no new mutation, repetition, capture/load/start or release authority.
56/// # Errors
57/// Rejects stale/resolved reservations, wrong attempts/request/context/unit/purpose,
58/// inactive/rebound acquired fences or excessive call reporting.
59pub 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/// Typed pure attribution/result denial; no error settles or refunds an attempt.
150#[derive(Debug, Error)]
151pub enum FenceReconciliationError {
152    /// Original pending evidence no longer binds the request.
153    #[error(transparent)]
154    Reservation(#[from] FenceReconciliationRequestError),
155    /// Actual challenge-bound observation request differs.
156    #[error("fence reconciliation observation request mismatch")]
157    RequestMismatch,
158    /// Actual result belongs to another original mutation or observation attempt.
159    #[error("fence reconciliation result attempt mismatch")]
160    AttemptMismatch,
161    /// Actual network/caller/release differs from the full original plan.
162    #[error("fence reconciliation actual context mismatch")]
163    ContextMismatch,
164    /// Full actual inventory differs, including unselected metadata.
165    #[error("fence reconciliation actual inventory mismatch")]
166    InventoryMismatch,
167    /// Actual whole-unit coverage differs from the exact original selection.
168    #[error("fence reconciliation actual selection mismatch")]
169    SelectionMismatch,
170    /// Acquired proof is for another capture/restore purpose.
171    #[error("fence reconciliation original purpose mismatch")]
172    PurposeMismatch,
173    /// Acquired proof names another original fence.
174    #[error("fence reconciliation original fence mismatch")]
175    FenceMismatch,
176    /// Acquired proof rebinds the original membership revision.
177    #[error("fence reconciliation membership revision mismatch")]
178    MembershipRevisionMismatch,
179    /// Restore acquisition rebinds original outside-snapshot obligations.
180    #[error("fence reconciliation external obligations revision mismatch")]
181    ExternalObligationsRevisionMismatch,
182    /// Acquisition cannot be admitted under this port's known inactive fence result.
183    #[error("fence reconciliation acquired fence is not active")]
184    FenceNotActive,
185    /// This single reserved observation cannot report multiple remote calls.
186    #[error("fence reconciliation remote observation ceiling exceeded")]
187    ObservationLimitExceeded,
188}
189
190#[cfg(test)]
191mod tests;