Skip to main content

ic_backup/model/attempt_journal/observation/
mod.rs

1//! Passive pending-observation identity owned by original attempt accounting.
2use super::{AttemptAuthorityRecord, AttemptJournalRecord};
3use crate::model::artifacts::ArtifactChecksumRecord;
4use thiserror::Error;
5
6#[derive(Debug)]
7pub(crate) struct ObservationReservation {
8    pub authority: AttemptAuthorityRecord,
9    pub mutation_attempt: u32,
10    pub observation_attempt: u32,
11    pub request: ArtifactChecksumRecord,
12}
13
14/// Read only the already consumed original attempt IDs, in mutation-first order.
15fn pending_attempts(
16    journal: &AttemptJournalRecord,
17) -> Result<(u32, u32), ObservationReservationError> {
18    Ok((
19        journal
20            .pending_mutation()
21            .ok_or(ObservationReservationError::NoPendingMutation)?,
22        journal
23            .pending_observation()
24            .ok_or(ObservationReservationError::NoPendingObservation)?,
25    ))
26}
27
28impl ObservationReservation {
29    pub(crate) fn new(
30        authority: AttemptAuthorityRecord,
31        journal: &AttemptJournalRecord,
32        request: ArtifactChecksumRecord,
33    ) -> Result<Self, ObservationReservationError> {
34        let (mutation_attempt, observation_attempt) = pending_attempts(journal)?;
35        let reservation = Self {
36            authority,
37            mutation_attempt,
38            observation_attempt,
39            request,
40        };
41        reservation.validate(journal)?;
42        Ok(reservation)
43    }
44
45    pub(crate) fn validate(
46        &self,
47        journal: &AttemptJournalRecord,
48    ) -> Result<(), ObservationReservationError> {
49        Self::validate_identity(
50            journal,
51            &self.authority,
52            self.mutation_attempt,
53            self.observation_attempt,
54            self.request.hash(),
55        )
56    }
57
58    pub(crate) fn validate_identity(
59        journal: &AttemptJournalRecord,
60        authority: &AttemptAuthorityRecord,
61        mutation_attempt: u32,
62        observation_attempt: u32,
63        request: &str,
64    ) -> Result<(), ObservationReservationError> {
65        if journal.authority() != authority {
66            return Err(ObservationReservationError::AuthorityMismatch);
67        }
68        if journal.pending_mutation() != Some(mutation_attempt) {
69            return Err(ObservationReservationError::MutationMismatch);
70        }
71        if journal.pending_observation() != Some(observation_attempt) {
72            return Err(ObservationReservationError::ObservationMismatch);
73        }
74        if journal.pending_observation_request() != Some(request) {
75            return Err(ObservationReservationError::RequestMismatch);
76        }
77        Ok(())
78    }
79}
80
81/// Original recovery reservation refusal; no variant grants dispatch or resets spending.
82#[derive(Clone, Copy, Debug, Eq, Error, PartialEq)]
83pub enum ObservationReservationError {
84    /// Current journal differs from the full original operation authority.
85    #[error("original observation authority mismatch")]
86    AuthorityMismatch,
87    /// There is no unresolved original mutation reservation.
88    #[error("observation requires a pending original mutation")]
89    NoPendingMutation,
90    /// There is no independently reserved original observation.
91    #[error("observation requires a pending original observation")]
92    NoPendingObservation,
93    /// The original mutation attempt changed or settled.
94    #[error("original observation mutation mismatch")]
95    MutationMismatch,
96    /// The original observation attempt changed or settled.
97    #[error("original observation attempt mismatch")]
98    ObservationMismatch,
99    /// Reserved observation digest differs from the exact retained payload.
100    #[error("original observation request mismatch")]
101    RequestMismatch,
102}