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