Skip to main content

ic_backup/model/ic_snapshot_upload_observation/
mod.rs

1//! Exact reserved inventory observations of unresolved metadata uploads.
2
3use crate::model::{
4    attempt_journal::{AttemptAuthorityRecord, AttemptJournalRecord},
5    ic_observation::{IcObservationRequestError, ObservationReservation},
6    ic_request::{IcManagementMethodRecord, IcManagementRequestRecord},
7    ic_snapshot_upload::{
8        IcSnapshotUploadAttemptError, IcSnapshotUploadKind, IcSnapshotUploadRequest,
9        original_authority,
10    },
11    operation_plan::OperationPlanRecord,
12};
13use thiserror::Error;
14
15/// Original metadata-upload intent and its already spent exact list observation.
16///
17/// This declaration supplies no dispatch permission, authentication or allocation
18/// attribution. Integrations retain original bytes and qualify fresh read access,
19/// chronology, command custody and proof of no prior observation dispatch.
20/// Lost observations stay pending; reconstruction permits no repeated call.
21#[derive(Debug)]
22pub struct IcSnapshotUploadObservationRequest<'request, 'source> {
23    plan: &'request OperationPlanRecord,
24    mutation: &'request IcSnapshotUploadRequest<'source>,
25    payload: &'request IcManagementRequestRecord,
26    authority: AttemptAuthorityRecord,
27    mutation_attempt: u32,
28    observation_attempt: u32,
29}
30
31impl<'request, 'source> IcSnapshotUploadObservationRequest<'request, 'source> {
32    /// Bind exact original metadata intent and an independently reserved list payload.
33    ///
34    /// Data uploads and status observations have no admission through this boundary.
35    /// No journal is created, reserved, reset or settled by construction.
36    /// # Errors
37    /// Rejects unsupported methods, changed original bindings and missing reservations.
38    pub fn new(
39        plan: &'request OperationPlanRecord,
40        operation_sequence: u64,
41        journal: &AttemptJournalRecord,
42        mutation: &'request IcSnapshotUploadRequest<'source>,
43        payload: &'request IcManagementRequestRecord,
44    ) -> Result<Self, IcSnapshotUploadObservationError> {
45        if !matches!(mutation.kind(), IcSnapshotUploadKind::Metadata) {
46            return Err(IcSnapshotUploadObservationError::UnsupportedUpload);
47        }
48        if payload.method() != IcManagementMethodRecord::ListCanisterSnapshots {
49            return Err(IcSnapshotUploadObservationError::UnsupportedObservation);
50        }
51        let authority = original_authority(plan, operation_sequence, journal, mutation)?;
52        payload
53            .validate_observation_binding(authority.binding(), &payload.digest())
54            .map_err(IcObservationRequestError::from)?;
55        let current = journal.view();
56        let request = Self {
57            plan,
58            mutation,
59            payload,
60            authority,
61            mutation_attempt: current
62                .pending_mutation
63                .ok_or(IcObservationRequestError::NoPendingMutation)?,
64            observation_attempt: current
65                .pending_observation
66                .ok_or(IcObservationRequestError::NoPendingObservation)?,
67        };
68        request.validate_journal(journal)?;
69        Ok(request)
70    }
71
72    /// Read the complete original upload plan and immutable allowances.
73    #[must_use]
74    pub const fn plan(&self) -> &OperationPlanRecord {
75        self.plan
76    }
77    /// Read exact original metadata upload bytes and retained source binding.
78    #[must_use]
79    pub const fn mutation(&self) -> &'request IcSnapshotUploadRequest<'source> {
80        self.mutation
81    }
82    /// Read the original accounted list receiver, target, method and Candid bytes.
83    #[must_use]
84    pub const fn payload(&self) -> &'request IcManagementRequestRecord {
85        self.payload
86    }
87    /// Read original plan-derived authority, without fresh spending permission.
88    #[must_use]
89    pub const fn authority(&self) -> &AttemptAuthorityRecord {
90        &self.authority
91    }
92    /// Read the original unresolved metadata-upload attempt.
93    #[must_use]
94    pub const fn mutation_attempt(&self) -> u32 {
95        self.mutation_attempt
96    }
97    /// Read the already consumed inventory-observation attempt.
98    #[must_use]
99    pub const fn observation_attempt(&self) -> u32 {
100        self.observation_attempt
101    }
102    /// Recheck exact current authority, both pending attempts and reserved list bytes.
103    /// # Errors
104    /// Rejects changed/settled reservations or different original authority/payload.
105    pub fn validate_journal(
106        &self,
107        journal: &AttemptJournalRecord,
108    ) -> Result<(), IcObservationRequestError> {
109        ObservationReservation {
110            authority: &self.authority,
111            mutation_attempt: self.mutation_attempt,
112            observation_attempt: self.observation_attempt,
113            payload: self.payload,
114        }
115        .validate(journal)
116    }
117}
118
119/// Structural upload/list admission failure; no outcome or spending transition.
120#[derive(Debug, Error)]
121pub enum IcSnapshotUploadObservationError {
122    /// This inventory boundary admits metadata allocation only.
123    #[error("snapshot upload observation requires metadata intent")]
124    UnsupportedUpload,
125    /// Only the exact original list payload is supported.
126    #[error("snapshot upload observation requires snapshot list")]
127    UnsupportedObservation,
128    /// Original upload authority, bytes or source context differs.
129    #[error(transparent)]
130    Upload(#[from] IcSnapshotUploadAttemptError),
131    /// Existing original observation reservation admission failed.
132    #[error(transparent)]
133    Observation(#[from] IcObservationRequestError),
134}
135
136#[cfg(test)]
137mod tests;