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