Skip to main content

ic_backup/model/ic_mutation/
mod.rs

1//! Exact reserved IC mutation identity and bounded passive reply association.
2
3use crate::model::attempt_journal::MutationReservation;
4use crate::model::ic_update_response::IcUpdateResponseInput;
5use crate::model::{
6    attempt_journal::{AttemptAuthorityRecord, AttemptJournalRecord},
7    ic_lifecycle_reply::MAX_IC_LIFECYCLE_REPLY_BYTES,
8    ic_request::{IcManagementRequestRecord, IcRequestError},
9    ic_snapshot_reply::MAX_IC_SNAPSHOT_REPLY_BYTES,
10    operation_plan::{OperationPlanError, OperationPlanRecord},
11};
12use std::fmt;
13use thiserror::Error;
14
15/// Maximum owned raw reply bytes, derived from both existing finite codec owners.
16pub const MAX_IC_MUTATION_REPLY_BYTES: usize =
17    if MAX_IC_SNAPSHOT_REPLY_BYTES < MAX_IC_LIFECYCLE_REPLY_BYTES {
18        MAX_IC_SNAPSHOT_REPLY_BYTES
19    } else {
20        MAX_IC_LIFECYCLE_REPLY_BYTES
21    };
22
23/// Structural request for one already reserved original host-ingress mutation.
24///
25/// Retained identity does not prove fresh permissions, prerequisites, application
26/// safety or exclusive custody. A pending reservation does not prove it was never
27/// dispatched. This request is not a dispatch permit or an interruption retry.
28#[derive(Debug)]
29pub struct IcMutationRequest<'a> {
30    plan: &'a OperationPlanRecord,
31    payload: &'a IcManagementRequestRecord,
32    reservation: MutationReservation,
33}
34
35impl<'a> IcMutationRequest<'a> {
36    /// Bind exact original plan, allowances and IC bytes after mutation reservation.
37    ///
38    /// Accepts only capture, load, start and stop; observations have separate
39    /// reservations. Derivation never creates a journal, spends or resets allowance.
40    /// # Errors
41    /// Rejects unknown operations, changed authority/bytes, absent pending mutation
42    /// or an observation already reserved for recovery.
43    pub fn new(
44        plan: &'a OperationPlanRecord,
45        operation_sequence: u64,
46        journal: &AttemptJournalRecord,
47        payload: &'a IcManagementRequestRecord,
48    ) -> Result<Self, IcMutationRequestError> {
49        let authority = plan.attempt_authority_for_journal(
50            operation_sequence,
51            journal,
52            IcMutationRequestError::Reservation(
53                crate::model::attempt_journal::MutationReservationError::AuthorityMismatch,
54            ),
55        )?;
56        payload.validate_mutation_binding(authority.binding())?;
57        let reservation = MutationReservation::new(authority, journal)?;
58        Ok(Self {
59            plan,
60            payload,
61            reservation,
62        })
63    }
64
65    /// Read original full context, inventory, selection and dependency declarations.
66    #[must_use]
67    pub const fn plan(&self) -> &OperationPlanRecord {
68        self.plan
69    }
70    /// Read exact management receiver, routing target, method and Candid argument bytes.
71    #[must_use]
72    pub const fn payload(&self) -> &'a IcManagementRequestRecord {
73        self.payload
74    }
75    /// Read canonical original identity and immutable mutation/observation limits.
76    #[must_use]
77    pub const fn authority(&self) -> &AttemptAuthorityRecord {
78        &self.reservation.authority
79    }
80    /// Read the original already consumed mutation attempt number.
81    #[must_use]
82    pub const fn mutation_attempt(&self) -> u32 {
83        self.reservation.mutation_attempt
84    }
85    /// Recheck the exact current pending reservation without IO or effect admission.
86    /// # Errors
87    /// Rejects different authority, replaced/settled mutation or observation recovery.
88    pub fn validate_journal(
89        &self,
90        journal: &AttemptJournalRecord,
91    ) -> Result<(), IcMutationRequestError> {
92        self.reservation.validate(journal).map_err(Into::into)
93    }
94}
95
96/// Immutable bounded passive acknowledgement; not a receipt or authenticated outcome.
97///
98/// No Serde or default admission exists. Raw bytes are hidden from Debug; explicit
99/// access retains exact evidence. Decoding happens in the existing method-specific
100/// reply owners during pure association, never in a second codec.
101#[derive(Clone)]
102pub struct IcMutationAcknowledgement {
103    input: IcUpdateResponseInput,
104}
105
106impl IcMutationAcknowledgement {
107    /// Admit finite attempt/raw-byte bounds and normalize the actual target identity.
108    /// # Errors
109    /// Rejects zero/excessive attempts, oversized raw replies and invalid principals.
110    pub fn new(mut input: IcUpdateResponseInput) -> Result<Self, IcMutationAcknowledgementError> {
111        input.admit(MAX_IC_MUTATION_REPLY_BYTES)?;
112        Ok(Self { input })
113    }
114    /// Read canonical passive association fields and exact raw reply, without mutation access.
115    #[must_use]
116    pub const fn input(&self) -> &IcUpdateResponseInput {
117        &self.input
118    }
119}
120
121impl fmt::Debug for IcMutationAcknowledgement {
122    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
123        self.input
124            .fmt_redacted(formatter, "IcMutationAcknowledgement")
125    }
126}
127
128/// Original reservation denial; no variant consumes, refunds or grants call authority.
129#[derive(Debug, Error)]
130pub enum IcMutationRequestError {
131    /// Original reservation denial, retaining the canonical typed accounting cause.
132    #[error("original mutation reservation rejected: {0}")]
133    Reservation(#[from] crate::model::attempt_journal::MutationReservationError),
134    /// Exact closed method, target or original Candid payload digest differs.
135    #[error(transparent)]
136    Payload(#[from] IcRequestError),
137    /// Original plan cannot derive the requested operation authority.
138    #[error(transparent)]
139    Plan(#[from] OperationPlanError),
140}
141
142/// Passive reply boundary denial with no raw payload in diagnostics.
143#[derive(Clone, Copy, Debug, Eq, Error, PartialEq)]
144pub enum IcMutationAcknowledgementError {
145    /// Original attempt must be within the existing 1..=1,024 total-attempt bound.
146    #[error("invalid IC mutation acknowledgement attempt")]
147    InvalidAttempt,
148    /// Raw reply exceeded the existing finite codec byte bounds.
149    #[error("IC mutation acknowledgement reply too large")]
150    ReplyTooLarge,
151    /// Actual provider target is not a principal.
152    #[error("invalid IC mutation acknowledgement target")]
153    InvalidTarget,
154}
155
156#[cfg(test)]
157mod tests;
158
159impl From<crate::model::ic_update_response::UpdateResponseAdmissionError>
160    for IcMutationAcknowledgementError
161{
162    fn from(error: crate::model::ic_update_response::UpdateResponseAdmissionError) -> Self {
163        match error {
164            crate::model::ic_update_response::UpdateResponseAdmissionError::InvalidAttempt => {
165                Self::InvalidAttempt
166            }
167            crate::model::ic_update_response::UpdateResponseAdmissionError::ReplyTooLarge => {
168                Self::ReplyTooLarge
169            }
170            crate::model::ic_update_response::UpdateResponseAdmissionError::InvalidTarget => {
171                Self::InvalidTarget
172            }
173        }
174    }
175}