use crate::model::{
artifacts::ArtifactChecksumRecord,
attempt_journal::{AttemptJournalRecord, ObservationOutcomeRecord},
consistency::ApplicationFenceState,
fence_obligation::FenceObligationScopeRecord,
fence_reconciliation::{
FenceReconciliationEvidence, FenceReconciliationObservation, FenceReconciliationRequest,
FenceReconciliationRequestError, MAX_FENCE_RECONCILIATION_REMOTE_OBSERVATIONS,
},
};
use thiserror::Error;
#[derive(Clone, Debug)]
pub struct FenceReconciliationView<'a> {
observation: &'a FenceReconciliationObservation,
outcome: ObservationOutcomeRecord,
}
impl FenceReconciliationView<'_> {
#[must_use]
pub const fn outcome(&self) -> ObservationOutcomeRecord {
self.outcome
}
#[must_use]
pub const fn request(&self) -> &ArtifactChecksumRecord {
&self.observation.input().request
}
#[must_use]
pub const fn observation_attempt(&self) -> u32 {
self.observation.input().observation_attempt
}
#[must_use]
pub const fn evidence(&self) -> &ArtifactChecksumRecord {
&self.observation.input().evidence
}
#[must_use]
pub const fn settlement(&self) -> &FenceReconciliationEvidence {
&self.observation.input().settlement
}
}
pub fn validate<'a>(
request: &FenceReconciliationRequest<'_, '_>,
journal: &AttemptJournalRecord,
observation: &'a FenceReconciliationObservation,
) -> Result<FenceReconciliationView<'a>, FenceReconciliationError> {
request.validate_journal(journal)?;
let input = observation.input();
let intent = request.intent();
if input.request != request.digest() {
return Err(FenceReconciliationError::RequestMismatch);
}
if input.mutation_attempt != intent.mutation_attempt()
|| input.observation_attempt != request.observation_attempt()
{
return Err(FenceReconciliationError::AttemptMismatch);
}
if &input.context != intent.plan().context() {
return Err(FenceReconciliationError::ContextMismatch);
}
if &input.inventory != intent.plan().inventory() {
return Err(FenceReconciliationError::InventoryMismatch);
}
if input.selected_targets != intent.plan().selected_targets() {
return Err(FenceReconciliationError::SelectionMismatch);
}
if input.remote_observations > MAX_FENCE_RECONCILIATION_REMOTE_OBSERVATIONS {
return Err(FenceReconciliationError::ObservationLimitExceeded);
}
let outcome = match_claim(intent.obligation().scope(), &input.settlement)?;
Ok(FenceReconciliationView {
observation,
outcome,
})
}
fn match_claim(
scope: &FenceObligationScopeRecord,
actual: &FenceReconciliationEvidence,
) -> Result<ObservationOutcomeRecord, FenceReconciliationError> {
match (scope, actual) {
(_, FenceReconciliationEvidence::NotAcquired { .. }) => {
Ok(ObservationOutcomeRecord::NotApplied)
}
(_, FenceReconciliationEvidence::Unresolved { .. }) => {
Ok(ObservationOutcomeRecord::Uncertain)
}
(
FenceObligationScopeRecord::Capture {
identity,
membership_revision,
..
},
FenceReconciliationEvidence::AcquiredCapture { fence, .. },
) => {
if &fence.identity != identity {
return Err(FenceReconciliationError::FenceMismatch);
}
if &fence.membership_revision != membership_revision {
return Err(FenceReconciliationError::MembershipRevisionMismatch);
}
active(fence.state)?;
Ok(ObservationOutcomeRecord::Applied)
}
(
FenceObligationScopeRecord::Restore {
fence: original, ..
},
FenceReconciliationEvidence::AcquiredRestore { fence, .. },
) => {
if fence.binding.identity != original.identity {
return Err(FenceReconciliationError::FenceMismatch);
}
if fence.binding.membership_revision != original.membership_revision {
return Err(FenceReconciliationError::MembershipRevisionMismatch);
}
if fence.binding.external_obligations_revision != original.external_obligations_revision
{
return Err(FenceReconciliationError::ExternalObligationsRevisionMismatch);
}
active(fence.state)?;
Ok(ObservationOutcomeRecord::Applied)
}
_ => Err(FenceReconciliationError::PurposeMismatch),
}
}
fn active(state: ApplicationFenceState) -> Result<(), FenceReconciliationError> {
if state != ApplicationFenceState::Active {
return Err(FenceReconciliationError::FenceNotActive);
}
Ok(())
}
#[derive(Debug, Error)]
pub enum FenceReconciliationError {
#[error(transparent)]
Reservation(#[from] FenceReconciliationRequestError),
#[error("fence reconciliation observation request mismatch")]
RequestMismatch,
#[error("fence reconciliation result attempt mismatch")]
AttemptMismatch,
#[error("fence reconciliation actual context mismatch")]
ContextMismatch,
#[error("fence reconciliation actual inventory mismatch")]
InventoryMismatch,
#[error("fence reconciliation actual selection mismatch")]
SelectionMismatch,
#[error("fence reconciliation original purpose mismatch")]
PurposeMismatch,
#[error("fence reconciliation original fence mismatch")]
FenceMismatch,
#[error("fence reconciliation membership revision mismatch")]
MembershipRevisionMismatch,
#[error("fence reconciliation external obligations revision mismatch")]
ExternalObligationsRevisionMismatch,
#[error("fence reconciliation acquired fence is not active")]
FenceNotActive,
#[error("fence reconciliation remote observation ceiling exceeded")]
ObservationLimitExceeded,
}
#[cfg(test)]
mod tests;