Skip to main content

ic_backup/workflow/ic_snapshot_capture/
mod.rs

1//! One freshly reserved capture update under exact original stage evidence.
2
3use crate::{
4    model::{
5        ic_mutation::{IcMutationAcknowledgement, IcMutationRequest, IcMutationRequestError},
6        ic_request::{IcManagementMethodRecord, IcManagementRequestRecord},
7        operation_plan::OperationPlanError,
8    },
9    ops::persistence::{
10        AttemptJournalError, ExecutionProgressPersistenceError, ExecutionStageGuard,
11        ExecutionWorkflowPersistenceError,
12    },
13    policy::ic_mutation::{IcMutationAssociationError, validate_acknowledgement},
14    ports::ic_mutation::{IcMutationProvider, IcMutationProviderError},
15};
16use thiserror::Error;
17
18/// Durably reserve an original capture, freshly admit it and submit one exact update.
19///
20/// Hold no attempt guards on entry. Only `TakeCanisterSnapshot` is accepted, with
21/// canonical original target/wire binding checked before spending. The existing
22/// journal is opened, never created, and complete original-plan admission owns its
23/// durable reservation. Keep the selected journal locked through fresh admission,
24/// one provider invocation and bounded passive acknowledgement association.
25/// Retained stage/ancestor checks bracket dispatch. Pending or Applied attempts
26/// cannot invoke admission or dispatch again; reopening grants no recapture.
27///
28/// The mandatory fallible `admit` callback must qualify fresh actual context/control,
29/// original capture consistency, quiescence/fence obligations and exclusive
30/// never-dispatched command custody. Any remote preflight calls need their own prior
31/// accounting. There is no default permission, provider or application consistency
32/// lane. The existing provider retains its authenticated single-update contract.
33/// Admission and submission are awaited while the selected journal stays locked.
34/// Cancellation releases that lock while retaining its pending reservation;
35/// even cancellation before dispatch grants no repeat callback or submission.
36///
37/// Success returns a bounded passive acknowledgement and leaves spending pending.
38/// Integrations authenticate exact original attribution and explicitly record any
39/// qualified receipt using the existing journal owner. An ID or matching wire reply
40/// supplies no automatic outcome, complete capture/download, terminal or fence release.
41/// # Errors
42/// Rejects noncapture/changed payloads, original evidence, spending, fresh admission,
43/// provider failures or malformed/mismatched replies. All post-reservation failures
44/// retain consumption; returned acknowledgements survive later rejection in typed
45/// errors. No failure permits retry, recapture, refund, cleanup or automatic uncertainty.
46pub async fn capture_snapshot<E: std::error::Error + 'static>(
47    stage: &ExecutionStageGuard<'_>,
48    operation_sequence: u64,
49    payload: &IcManagementRequestRecord,
50    provider: &mut impl IcMutationProvider,
51    admit: impl AsyncFnOnce(&IcMutationRequest<'_>) -> Result<(), E>,
52) -> Result<IcMutationAcknowledgement, IcSnapshotCaptureExecutionError<E>> {
53    if payload.method() != IcManagementMethodRecord::TakeCanisterSnapshot {
54        return Err(IcSnapshotCaptureExecutionError::CaptureRequired);
55    }
56    let plan = stage.plan();
57    let authority = plan.attempt_authority(operation_sequence)?;
58    let (acknowledgement, admission) = super::ic_mutation::submit_with(
59        stage,
60        &authority,
61        payload,
62        provider,
63        async |request| {
64            admit(request)
65                .await
66                .map_err(IcSnapshotCaptureExecutionError::Admission)?;
67            stage.layout()?;
68            Ok::<_, IcSnapshotCaptureExecutionError<E>>(())
69        },
70        async |journal, request, acknowledgement| {
71            validate_acknowledgement(request, journal.record()?, acknowledgement)?;
72            stage.layout()?;
73            Ok::<_, IcSnapshotCaptureReplyError>(())
74        },
75    )
76    .await?;
77    let admitted = admission;
78    super::qualified_receipt::retain_reply(acknowledgement, admitted, |source, acknowledgement| {
79        IcSnapshotCaptureExecutionError::AfterReply {
80            source,
81            acknowledgement,
82        }
83    })
84    .map(|(reply, ())| reply)
85}
86
87/// Capture-step failures retain original spending and any bounded returned acknowledgement.
88#[derive(Debug, Error)]
89pub enum IcSnapshotCaptureExecutionError<E: std::error::Error + 'static> {
90    /// Only the original capture method may enter this coordinator.
91    #[error("snapshot capture requires take_canister_snapshot")]
92    CaptureRequired,
93    /// Original operation authority cannot be derived.
94    #[error(transparent)]
95    Plan(#[from] OperationPlanError),
96    /// Workflow, stage or original ancestor evidence failed admission.
97    #[error(transparent)]
98    Stage(#[from] ExecutionWorkflowPersistenceError),
99    /// Original selected journal admission failed.
100    #[error(transparent)]
101    Journal(#[from] AttemptJournalError),
102    /// Complete original progress, prerequisites or durable reservation failed.
103    #[error(transparent)]
104    Progress(#[from] ExecutionProgressPersistenceError),
105    /// Canonical original mutation binding or current reservation differs.
106    #[error(transparent)]
107    Request(#[from] IcMutationRequestError),
108    /// Fresh integration-owned admission rejected after reservation.
109    #[error("fresh snapshot capture admission failed: {0}")]
110    Admission(#[source] E),
111    /// The single provider call failed; spending remains pending.
112    #[error(transparent)]
113    Provider(#[from] IcMutationProviderError),
114    /// Admission after a reply failed, retaining its complete bounded evidence.
115    #[error("snapshot capture reply rejected: {source}")]
116    AfterReply {
117        /// Typed original admission failure.
118        source: IcSnapshotCaptureReplyError,
119        /// Complete returned passive evidence, without settlement authority.
120        acknowledgement: Box<IcMutationAcknowledgement>,
121    },
122}
123
124/// Admission failure after a returned snapshot capture reply.
125#[derive(Debug, Error)]
126pub enum IcSnapshotCaptureReplyError {
127    /// Original selected journal cannot be re-admitted.
128    #[error(transparent)]
129    Journal(#[from] AttemptJournalError),
130    /// Passive exact reply association failed.
131    #[error(transparent)]
132    Association(#[from] IcMutationAssociationError),
133    /// Original stage or ancestor evidence cannot be re-admitted.
134    #[error(transparent)]
135    Stage(#[from] ExecutionWorkflowPersistenceError),
136}
137
138#[cfg(all(test, unix))]
139mod tests;