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
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 current = journal.view();
59        let request = Self {
60            plan,
61            mutation,
62            payload,
63            authority,
64            mutation_attempt: current
65                .pending_mutation
66                .ok_or(IcObservationRequestError::NoPendingMutation)?,
67            observation_attempt: current
68                .pending_observation
69                .ok_or(IcObservationRequestError::NoPendingObservation)?,
70        };
71        request.validate_journal(journal)?;
72        Ok(request)
73    }
74
75    /// Read the complete original upload plan and immutable allowances.
76    #[must_use]
77    pub const fn plan(&self) -> &OperationPlanRecord {
78        self.plan
79    }
80    /// Read exact original metadata upload bytes and retained source binding.
81    #[must_use]
82    pub const fn mutation(&self) -> &'request IcSnapshotUploadRequest<'source> {
83        self.mutation
84    }
85    /// Read the original accounted list receiver, target, method and Candid bytes.
86    #[must_use]
87    pub const fn payload(&self) -> &'request IcManagementRequestRecord {
88        self.payload
89    }
90    /// Read original plan-derived authority, without fresh spending permission.
91    #[must_use]
92    pub const fn authority(&self) -> &AttemptAuthorityRecord {
93        &self.authority
94    }
95    /// Read the original unresolved metadata-upload attempt.
96    #[must_use]
97    pub const fn mutation_attempt(&self) -> u32 {
98        self.mutation_attempt
99    }
100    /// Read the already consumed inventory-observation attempt.
101    #[must_use]
102    pub const fn observation_attempt(&self) -> u32 {
103        self.observation_attempt
104    }
105    /// Recheck exact current authority, both pending attempts and reserved list bytes.
106    /// # Errors
107    /// Rejects changed/settled reservations or different original authority/payload.
108    pub fn validate_journal(
109        &self,
110        journal: &AttemptJournalRecord,
111    ) -> Result<(), IcObservationRequestError> {
112        ObservationReservation {
113            authority: &self.authority,
114            mutation_attempt: self.mutation_attempt,
115            observation_attempt: self.observation_attempt,
116            request: self.payload.digest(),
117        }
118        .validate(journal)
119    }
120}
121
122/// Structural upload/list admission failure; no outcome or spending transition.
123#[derive(Debug, Error)]
124pub enum IcSnapshotUploadObservationError {
125    /// This inventory boundary admits metadata allocation only.
126    #[error("snapshot upload observation requires metadata intent")]
127    UnsupportedUpload,
128    /// Only the exact original list payload is supported.
129    #[error("snapshot upload observation requires snapshot list")]
130    UnsupportedObservation,
131    /// Original upload authority, bytes or source context differs.
132    #[error(transparent)]
133    Upload(#[from] IcSnapshotUploadAttemptError),
134    /// Existing original observation reservation admission failed.
135    #[error(transparent)]
136    Observation(#[from] IcObservationRequestError),
137}
138
139#[cfg(test)]
140mod tests;