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 {
59        authority: &input.authority,
60        mutation_attempt: input.mutation_attempt,
61        context: &input.context,
62        target: &input.target,
63    }
64    .compare(&ResponseIdentity {
65        authority: &request.authority().digest(),
66        mutation_attempt: request.mutation_attempt(),
67        context: request.plan().context(),
68        target: request.payload().target(),
69    })
70    .map_err(|mismatch| match mismatch {
71        ResponseIdentityMismatch::Authority => IcMutationAssociationError::AuthorityMismatch,
72        ResponseIdentityMismatch::Attempt => IcMutationAssociationError::AttemptMismatch,
73        ResponseIdentityMismatch::Context => IcMutationAssociationError::ContextMismatch,
74        ResponseIdentityMismatch::Target => IcMutationAssociationError::TargetMismatch,
75    })?;
76    let reply = match request.payload().method() {
77        IcManagementMethodRecord::TakeCanisterSnapshot => {
78            IcMutationReplyView::Capture(IcSnapshotReply::decode(request.payload(), &input.reply)?)
79        }
80        // The request owner already excludes observation methods. The existing
81        // lifecycle codec remains the only owner of the other mutation reply shapes.
82        _ => IcMutationReplyView::Lifecycle(IcLifecycleReply::decode(
83            request.payload(),
84            &input.reply,
85        )?),
86    };
87    Ok(IcMutationAcknowledgementView {
88        acknowledgement,
89        reply,
90    })
91}
92
93/// Typed passive association rejection, preserving every original pending obligation.
94#[derive(Debug, Error)]
95pub enum IcMutationAssociationError {
96    /// Current original reservation does not match the request.
97    #[error(transparent)]
98    Reservation(#[from] IcMutationRequestError),
99    /// Reply claims another full original operation authority.
100    #[error("IC mutation acknowledgement authority mismatch")]
101    AuthorityMismatch,
102    /// Reply claims another already allocated mutation attempt.
103    #[error("IC mutation acknowledgement attempt mismatch")]
104    AttemptMismatch,
105    /// Claimed actual network/caller/release differs from original context.
106    #[error("IC mutation acknowledgement context mismatch")]
107    ContextMismatch,
108    /// Claimed actual target differs from the exact original routing target.
109    #[error("IC mutation acknowledgement target mismatch")]
110    TargetMismatch,
111    /// Existing snapshot decoder rejected the capture reply.
112    #[error(transparent)]
113    Snapshot(#[from] IcSnapshotReplyError),
114    /// Existing lifecycle decoder rejected the exact empty tuple.
115    #[error(transparent)]
116    Lifecycle(#[from] IcLifecycleReplyError),
117}
118
119#[cfg(test)]
120mod tests;