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).compare(&ResponseIdentity {
59        authority: &request.authority().digest(),
60        mutation_attempt: request.mutation_attempt(),
61        context: request.plan().context(),
62        target: request.payload().target(),
63    })?;
64    let reply = match request.payload().method() {
65        IcManagementMethodRecord::TakeCanisterSnapshot => {
66            IcMutationReplyView::Capture(IcSnapshotReply::decode(request.payload(), &input.reply)?)
67        }
68        // The request owner already excludes observation methods. The existing
69        // lifecycle codec remains the only owner of the other mutation reply shapes.
70        _ => IcMutationReplyView::Lifecycle(IcLifecycleReply::decode(
71            request.payload(),
72            &input.reply,
73        )?),
74    };
75    Ok(IcMutationAcknowledgementView {
76        acknowledgement,
77        reply,
78    })
79}
80
81/// Typed passive association rejection, preserving every original pending obligation.
82#[derive(Debug, Error)]
83pub enum IcMutationAssociationError {
84    /// Current original reservation does not match the request.
85    #[error(transparent)]
86    Reservation(#[from] IcMutationRequestError),
87    /// Passive claims differ from the exact original update identity.
88    #[error(transparent)]
89    Identity(#[from] ResponseIdentityMismatch),
90    /// Existing snapshot decoder rejected the capture reply.
91    #[error(transparent)]
92    Snapshot(#[from] IcSnapshotReplyError),
93    /// Existing lifecycle decoder rejected the exact empty tuple.
94    #[error(transparent)]
95    Lifecycle(#[from] IcLifecycleReplyError),
96}
97
98#[cfg(test)]
99mod tests;