Skip to main content

ic_backup/model/ic_snapshot_upload/attempt/
mod.rs

1//! Original pending reservations over exact already retained upload payloads.
2
3use super::IcSnapshotUploadRequest;
4use crate::model::{
5    attempt_journal::{AttemptAuthorityRecord, AttemptJournalRecord},
6    operation_plan::{OperationPlanError, OperationPlanRecord},
7};
8use thiserror::Error;
9
10/// Structural original pending upload identity; reconstruction is no redispatch permit.
11#[derive(Debug)]
12pub struct IcSnapshotUploadAttempt<'request, 'source> {
13    plan: &'request OperationPlanRecord,
14    payload: &'request IcSnapshotUploadRequest<'source>,
15    authority: AttemptAuthorityRecord,
16    mutation_attempt: u32,
17}
18
19impl<'request, 'source> IcSnapshotUploadAttempt<'request, 'source> {
20    /// Bind the full original plan, exact target/bytes and pending mutation reservation.
21    ///
22    /// This performs no provider call, record creation, reservation or settlement.
23    /// A data payload includes its qualified new ID before its own plan is retained;
24    /// the prior metadata plan/allowance cannot be rebound into new data authority.
25    /// # Errors
26    /// Rejects unknown operations, different authority/payload, no pending mutation
27    /// or observation recovery already in progress.
28    pub fn new(
29        plan: &'request OperationPlanRecord,
30        operation_sequence: u64,
31        journal: &AttemptJournalRecord,
32        payload: &'request IcSnapshotUploadRequest<'source>,
33    ) -> Result<Self, IcSnapshotUploadAttemptError> {
34        let authority = plan.attempt_authority(operation_sequence)?;
35        if journal.authority() != &authority {
36            return Err(IcSnapshotUploadAttemptError::AuthorityMismatch);
37        }
38        if payload.target() != authority.binding().target()
39            || payload.binding_digest().hash() != authority.binding().request()
40        {
41            return Err(IcSnapshotUploadAttemptError::PayloadMismatch);
42        }
43        if plan.context().network() != payload.source_plan().context().network()
44            || plan.context().release() != payload.source_plan().context().release()
45        {
46            return Err(IcSnapshotUploadAttemptError::SourceContextMismatch);
47        }
48        let mutation_attempt = journal
49            .view()
50            .pending_mutation
51            .ok_or(IcSnapshotUploadAttemptError::NoPendingMutation)?;
52        let attempt = Self {
53            plan,
54            payload,
55            authority,
56            mutation_attempt,
57        };
58        attempt.validate_journal(journal)?;
59        Ok(attempt)
60    }
61    /// Read the exact retained full original plan, context and allowances.
62    #[must_use]
63    pub const fn plan(&self) -> &'request OperationPlanRecord {
64        self.plan
65    }
66    /// Read the exact originally planned upload wire/source declaration.
67    #[must_use]
68    pub const fn payload(&self) -> &'request IcSnapshotUploadRequest<'source> {
69        self.payload
70    }
71    /// Read original plan-derived authority; it grants no fresh effect.
72    #[must_use]
73    pub const fn authority(&self) -> &AttemptAuthorityRecord {
74        &self.authority
75    }
76    /// Read the already consumed original mutation number.
77    #[must_use]
78    pub const fn mutation_attempt(&self) -> u32 {
79        self.mutation_attempt
80    }
81    /// Recheck exact current authority and reservation before passive association.
82    /// # Errors
83    /// Rejects changed/settled mutation, authority mismatch or pending observation.
84    pub fn validate_journal(
85        &self,
86        journal: &AttemptJournalRecord,
87    ) -> Result<(), IcSnapshotUploadAttemptError> {
88        if journal.authority() != &self.authority {
89            return Err(IcSnapshotUploadAttemptError::AuthorityMismatch);
90        }
91        let view = journal.view();
92        if view.pending_mutation != Some(self.mutation_attempt) {
93            return Err(IcSnapshotUploadAttemptError::MutationMismatch);
94        }
95        if view.pending_observation.is_some() {
96            return Err(IcSnapshotUploadAttemptError::ObservationPending);
97        }
98        Ok(())
99    }
100}
101
102/// Original upload reservation rejection; no variant changes spending or recovery.
103#[derive(Debug, Error)]
104pub enum IcSnapshotUploadAttemptError {
105    /// Upload intent changes the original source network or release identity.
106    #[error("snapshot upload source network or release differs")]
107    SourceContextMismatch,
108    /// Original full-plan authority/context/operation/allowances differ.
109    #[error("snapshot upload original authority mismatch")]
110    AuthorityMismatch,
111    /// Exact canonical target or original source/wire binding digest differs.
112    #[error("snapshot upload original payload mismatch")]
113    PayloadMismatch,
114    /// No original unresolved mutation was reserved.
115    #[error("snapshot upload requires an original pending mutation")]
116    NoPendingMutation,
117    /// Original mutation has changed or already settled.
118    #[error("snapshot upload original mutation differs")]
119    MutationMismatch,
120    /// An already reserved recovery observation prevents original submission association.
121    #[error("snapshot upload recovery observation is pending")]
122    ObservationPending,
123    /// Original full plan has no such operation or cannot derive its authority.
124    #[error(transparent)]
125    Plan(#[from] OperationPlanError),
126}