Skip to main content

ic_backup/policy/ic_mutation/
mod.rs

1//! Pure original IC update reply association using the existing bounded codecs.
2
3use super::response_identity::{ResponseIdentity, ResponseIdentityMismatch};
4use crate::model::{
5    attempt_journal::AttemptJournalRecord,
6    ic_lifecycle_reply::{IcLifecycleReply, IcLifecycleReplyError},
7    ic_mutation::{IcMutationAcknowledgement, IcMutationRequest, IcMutationRequestError},
8    ic_request::IcManagementMethodRecord,
9    ic_snapshot_reply::{IcSnapshotReply, IcSnapshotReplyError},
10};
11use thiserror::Error;
12
13/// Method-specific local wire evidence; neither branch proves effect settlement.
14#[derive(Debug)]
15pub enum IcMutationReplyView<'a> {
16    /// Exact raw snapshot identity/timestamp/size under the original capture declaration.
17    Capture(IcSnapshotReply<'a>),
18    /// Exact empty stop/start/load tuple under the original lifecycle declaration.
19    Lifecycle(IcLifecycleReply<'a>),
20}
21
22/// Borrowed original acknowledgement with decoded method-specific local evidence.
23///
24/// No receipt, spending transition, fresh permission, attribution, restored-state
25/// safety or terminal/reference/fence release follows from successful association.
26#[derive(Debug)]
27pub struct IcMutationAcknowledgementView<'a> {
28    acknowledgement: &'a IcMutationAcknowledgement,
29    reply: IcMutationReplyView<'a>,
30}
31impl<'a> IcMutationAcknowledgementView<'a> {
32    /// Read exact retained passive context/authority/attempt/raw reply/evidence fields.
33    #[must_use]
34    pub const fn acknowledgement(&self) -> &'a IcMutationAcknowledgement {
35        self.acknowledgement
36    }
37    /// Read the existing codec's exact method-specific wire projection.
38    #[must_use]
39    pub const fn reply(&self) -> &IcMutationReplyView<'a> {
40        &self.reply
41    }
42}
43
44/// Recheck original reservation, actual association claims and bounded reply shape.
45///
46/// Network/caller/target authentication, freshness, exclusive original-request
47/// attribution and current lifecycle/fence/restore safety remain integration-owned.
48/// This pure function invokes no provider and changes no journal or allowance.
49/// # Errors
50/// Rejects changed reservation/authority/attempt/context/target and invalid Candid bytes.
51pub fn validate_acknowledgement<'a>(
52    request: &IcMutationRequest<'a>,
53    journal: &AttemptJournalRecord,
54    acknowledgement: &'a IcMutationAcknowledgement,
55) -> Result<IcMutationAcknowledgementView<'a>, IcMutationAssociationError> {
56    request.validate_journal(journal)?;
57    let input = acknowledgement.input();
58    ResponseIdentity::from(input)
59        .compare(&ResponseIdentity {
60            authority: &request.authority().digest(),
61            mutation_attempt: request.mutation_attempt(),
62            context: request.plan().context(),
63            target: request.payload().target(),
64        })
65        .map_err(|mismatch| match mismatch {
66            ResponseIdentityMismatch::Authority => IcMutationAssociationError::AuthorityMismatch,
67            ResponseIdentityMismatch::Attempt => IcMutationAssociationError::AttemptMismatch,
68            ResponseIdentityMismatch::Context => IcMutationAssociationError::ContextMismatch,
69            ResponseIdentityMismatch::Target => IcMutationAssociationError::TargetMismatch,
70        })?;
71    let reply = match request.payload().method() {
72        IcManagementMethodRecord::TakeCanisterSnapshot => {
73            IcMutationReplyView::Capture(IcSnapshotReply::decode(request.payload(), &input.reply)?)
74        }
75        // The request owner already excludes observation methods. The existing
76        // lifecycle codec remains the only owner of the other mutation reply shapes.
77        _ => IcMutationReplyView::Lifecycle(IcLifecycleReply::decode(
78            request.payload(),
79            &input.reply,
80        )?),
81    };
82    Ok(IcMutationAcknowledgementView {
83        acknowledgement,
84        reply,
85    })
86}
87
88/// Typed passive association rejection, preserving every original pending obligation.
89#[derive(Debug, Error)]
90pub enum IcMutationAssociationError {
91    /// Current original reservation does not match the request.
92    #[error(transparent)]
93    Reservation(#[from] IcMutationRequestError),
94    /// Reply claims another full original operation authority.
95    #[error("IC mutation acknowledgement authority mismatch")]
96    AuthorityMismatch,
97    /// Reply claims another already allocated mutation attempt.
98    #[error("IC mutation acknowledgement attempt mismatch")]
99    AttemptMismatch,
100    /// Claimed actual network/caller/release differs from original context.
101    #[error("IC mutation acknowledgement context mismatch")]
102    ContextMismatch,
103    /// Claimed actual target differs from the exact original routing target.
104    #[error("IC mutation acknowledgement target mismatch")]
105    TargetMismatch,
106    /// Existing snapshot decoder rejected the capture reply.
107    #[error(transparent)]
108    Snapshot(#[from] IcSnapshotReplyError),
109    /// Existing lifecycle decoder rejected the exact empty tuple.
110    #[error(transparent)]
111    Lifecycle(#[from] IcLifecycleReplyError),
112}
113
114#[cfg(test)]
115mod tests;