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