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