Skip to main content

ic_backup/model/application_ingress/request/
mod.rs

1//! Passive original application reservations; reconstructing one is never a resend permit.
2
3use super::{ApplicationIngressError, ApplicationIngressPayload};
4use crate::model::attempt_journal::ObservationReservation;
5use crate::model::{
6    attempt_journal::{AttemptAuthorityRecord, AttemptJournalRecord},
7    operation_plan::OperationPlanRecord,
8};
9
10/// Exact original application mutation after durable reservation.
11///
12/// The caller holds its original guard and independently admits fresh authority,
13/// dependencies, application safety and exclusive never-dispatched custody.
14#[derive(Debug)]
15pub struct ApplicationMutationRequest<'a> {
16    plan: &'a OperationPlanRecord,
17    payload: &'a ApplicationIngressPayload,
18    authority: AttemptAuthorityRecord,
19    mutation_attempt: u32,
20}
21impl<'a> ApplicationMutationRequest<'a> {
22    /// Bind original exact opaque bytes to the current pending mutation.
23    /// # Errors
24    /// Rejects changed authority/payload, absent mutation or pending recovery.
25    pub fn new(
26        plan: &'a OperationPlanRecord,
27        sequence: u64,
28        journal: &AttemptJournalRecord,
29        payload: &'a ApplicationIngressPayload,
30    ) -> Result<Self, ApplicationIngressError> {
31        let authority = authority(plan, sequence, journal)?;
32        payload.validate_mutation_binding(authority.binding())?;
33        let mutation_attempt = journal
34            .pending_mutation()
35            .ok_or(ApplicationIngressError::NoPendingMutation)?;
36        let request = Self {
37            plan,
38            payload,
39            authority,
40            mutation_attempt,
41        };
42        request.validate_journal(journal)?;
43        Ok(request)
44    }
45    /// Read the complete original plan/context and inventory.
46    #[must_use]
47    pub const fn plan(&self) -> &OperationPlanRecord {
48        self.plan
49    }
50    /// Read exact opaque update fields.
51    #[must_use]
52    pub const fn payload(&self) -> &ApplicationIngressPayload {
53        self.payload
54    }
55    /// Read full original authority and finite allowances.
56    #[must_use]
57    pub const fn authority(&self) -> &AttemptAuthorityRecord {
58        &self.authority
59    }
60    /// Read the already consumed mutation attempt.
61    #[must_use]
62    pub const fn mutation_attempt(&self) -> u32 {
63        self.mutation_attempt
64    }
65    /// Recheck current exact original reservation without authorizing dispatch.
66    /// # Errors
67    /// Rejects changed authority, mutation or pending observation.
68    pub fn validate_journal(
69        &self,
70        journal: &AttemptJournalRecord,
71    ) -> Result<(), ApplicationIngressError> {
72        journal.validate_pending_mutation(
73            &self.authority,
74            self.mutation_attempt,
75            ApplicationIngressError::AuthorityMismatch,
76            ApplicationIngressError::MutationMismatch,
77            ApplicationIngressError::ObservationPending,
78        )
79    }
80}
81
82/// Exact separately reserved application recovery update for an unresolved mutation.
83///
84/// This consumes the observation lane, even when its opaque method reads status.
85/// It never reuses mutation allowance or supplies application settlement semantics.
86#[derive(Debug)]
87pub struct ApplicationObservationRequest<'a> {
88    plan: &'a OperationPlanRecord,
89    payload: &'a ApplicationIngressPayload,
90    reservation: ObservationReservation,
91}
92impl<'a> ApplicationObservationRequest<'a> {
93    /// Bind exact recovery bytes to the independently reserved observation digest.
94    /// # Errors
95    /// Rejects changed authority/target, absent attempts or different reserved bytes.
96    pub fn new(
97        plan: &'a OperationPlanRecord,
98        sequence: u64,
99        journal: &AttemptJournalRecord,
100        payload: &'a ApplicationIngressPayload,
101    ) -> Result<Self, ApplicationIngressError> {
102        let authority = authority(plan, sequence, journal)?;
103        payload.validate_observation_binding(authority.binding())?;
104        let reservation = ObservationReservation::new(authority, journal, payload.digest())
105            .map_err(crate::model::ic_observation::IcObservationRequestError::from)?;
106        Ok(Self {
107            plan,
108            payload,
109            reservation,
110        })
111    }
112    /// Read the complete original mutation plan/context and inventory.
113    #[must_use]
114    pub const fn plan(&self) -> &OperationPlanRecord {
115        self.plan
116    }
117    /// Read exact original recovery update fields.
118    #[must_use]
119    pub const fn payload(&self) -> &ApplicationIngressPayload {
120        self.payload
121    }
122    /// Read original authority; its request still names the unresolved mutation.
123    #[must_use]
124    pub const fn authority(&self) -> &AttemptAuthorityRecord {
125        &self.reservation.authority
126    }
127    /// Read the original unresolved mutation attempt.
128    #[must_use]
129    pub const fn mutation_attempt(&self) -> u32 {
130        self.reservation.mutation_attempt
131    }
132    /// Read the independently consumed observation attempt.
133    #[must_use]
134    pub const fn observation_attempt(&self) -> u32 {
135        self.reservation.observation_attempt
136    }
137    /// Recheck exact current observation identity without a dispatch permit.
138    /// # Errors
139    /// Rejects settled/replaced attempts, changed authority or different reserved bytes.
140    pub fn validate_journal(
141        &self,
142        journal: &AttemptJournalRecord,
143    ) -> Result<(), ApplicationIngressError> {
144        self.reservation
145            .validate(journal)
146            .map_err(crate::model::ic_observation::IcObservationRequestError::from)?;
147        Ok(())
148    }
149}
150
151fn authority(
152    plan: &OperationPlanRecord,
153    sequence: u64,
154    journal: &AttemptJournalRecord,
155) -> Result<AttemptAuthorityRecord, ApplicationIngressError> {
156    let authority = plan.attempt_authority(sequence)?;
157    if journal.authority() != &authority {
158        return Err(ApplicationIngressError::AuthorityMismatch);
159    }
160    Ok(authority)
161}