Skip to main content

ic_backup/workflow/ic_snapshot_restore/
mod.rs

1//! One original same-ID load/start with fresh safety and independently qualified settlement.
2
3mod source;
4pub use source::{IcRestoreSource, IcRestoreSourceError};
5
6use crate::{
7    model::{
8        attempt_journal::MutationReceiptRequest,
9        execution_workflow::ExecutionStagePredecessorRecord,
10        ic_mutation::{IcMutationAcknowledgement, IcMutationRequest, IcMutationRequestError},
11        restore_safety::{RestoreSafetyObservation, RestoreSafetyRequest},
12    },
13    ops::persistence::{
14        AttemptJournalError, ExecutionProgressPersistenceError, ExecutionStageCheckpointError,
15        ExecutionStageGuard, ExecutionWorkflowPersistenceError, RestoreSafetyPersistenceError,
16        read_restore_safety_requirement,
17    },
18    policy::{
19        ic_mutation::{IcMutationAssociationError, IcMutationReplyView, validate_acknowledgement},
20        restore_safety::{RestoreSafetyError, validate},
21    },
22    ports::ic_mutation::{IcMutationProvider, IcMutationProviderError},
23};
24use thiserror::Error;
25
26/// Execute and checkpoint one exact singleton original load or start stage.
27///
28/// Retain the complete original stage/journals, source plan, safety requirement and
29/// source references first; hold no attempt guards. `source` explicitly binds an
30/// existing snapshot or the complete original upload destination independently of
31/// the load plan. Re-admit that provenance across all boundaries. `safety` binds the exact original
32/// load/start bytes and a fresh integration-owned challenge. Read the requirement
33/// under both layouts before spending and recheck it across admission and settlement.
34/// No source is reconstructed, uploaded, replaced or released by this function.
35///
36/// After durable reservation, mandatory `admit` must qualify current authenticated
37/// control, snapshot-origin permissions, complete uploaded source, application safety,
38/// stable byte/command custody and proof of no prior dispatch. Return actual fresh
39/// safety observations; the existing policy checks exact context/inventory/selection,
40/// original lane/fence and load/start preconditions before one provider invocation.
41/// All remote preflight/qualification calls need separate prior accounting. There is
42/// no default no-external-effects lane or automatic stopped/load-success inference.
43///
44/// Mandatory `qualify` independently authenticates original successful attribution
45/// and durably retains exact request/reply evidence before its explicit Applied
46/// receipt. Record through the existing journal owner under exclusion, then release
47/// the lock and checkpoint canonical request/raw-reply evidence. Load settlement
48/// does not establish application acceptance for a later start: qualify that afresh.
49/// Pending/Applied stages never invoke callbacks/providers again. Checkpoint replay
50/// stays local; failure preserves spending, replies, obligations and references.
51/// This is one bounded step, not complete restore, terminal or fence/reference release.
52/// Admission, submission and independent qualification are awaited under the
53/// selected journal lock. Cancellation retains its current pending/Applied state
54/// and grants no repeat invocation. Qualification must durably retain returned
55/// bytes before awaiting work that could be cancelled; in-memory replies do not
56/// survive cancellation. The core imposes no executor or `Send` requirement.
57/// # Errors
58/// Rejects changed/non-singleton originals, retained source/requirement failures,
59/// spending, fresh admission/safety, provider/association, qualification/receipt or
60/// checkpoint failure. Returned bounded replies survive post-reply rejection.
61pub async fn restore_snapshot<E: std::error::Error + 'static>(
62    stage: &ExecutionStageGuard<'_>,
63    source: IcRestoreSource<'_>,
64    safety: &RestoreSafetyRequest<'_>,
65    provider: &mut impl IcMutationProvider,
66    admit: impl AsyncFnOnce(
67        &IcMutationRequest<'_>,
68        &RestoreSafetyRequest<'_>,
69    ) -> Result<RestoreSafetyObservation, E>,
70    qualify: impl AsyncFnOnce(
71        &IcMutationRequest<'_>,
72        &IcMutationAcknowledgement,
73    ) -> Result<MutationReceiptRequest, E>,
74) -> Result<(IcMutationAcknowledgement, ExecutionStagePredecessorRecord), IcRestoreExecutionError<E>>
75{
76    let plan = stage.plan();
77    let sequence = safety.binding().operation_sequence();
78    let [operation] = plan.operations() else {
79        return Err(IcRestoreExecutionError::OriginalMismatch);
80    };
81    if operation.operation_sequence() != sequence {
82        return Err(IcRestoreExecutionError::OriginalMismatch);
83    }
84    let retain = || {
85        source.validate(stage, safety.wire())?;
86        read_restore_safety_requirement(
87            stage.layout()?,
88            source.layout,
89            plan,
90            source.plan,
91            &safety.requirement().digest(),
92        )?;
93        Ok::<_, IcRestoreReplyError<E>>(())
94    };
95    retain()?;
96    let authority = plan
97        .attempt_authority(sequence)
98        .map_err(IcMutationRequestError::from)?;
99    let (acknowledgement, settlement) = super::ic_mutation::submit_with(
100        stage,
101        &authority,
102        safety.wire(),
103        provider,
104        async |request| {
105            let observation = admit(request, safety)
106                .await
107                .map_err(IcRestoreExecutionError::Admission)?;
108            validate(safety, &observation)?;
109            retain()?;
110            Ok::<_, IcRestoreExecutionError<E>>(())
111        },
112        async |journal, request, acknowledgement| {
113            let admitted = validate_acknowledgement(request, journal.record()?, acknowledgement)?;
114            retain()?;
115            let receipt = qualify(request, acknowledgement)
116                .await
117                .map_err(IcRestoreReplyError::Qualification)?;
118            super::qualified_receipt::record_applied(
119                journal,
120                receipt,
121                request.mutation_attempt(),
122                safety.wire().digest().hash(),
123                IcRestoreReplyError::ReceiptRequired,
124                retain,
125            )?;
126            retain()?;
127            let IcMutationReplyView::Lifecycle(reply) = admitted.reply() else {
128                return Err(IcRestoreReplyError::ReceiptRequired);
129            };
130            Ok::<_, IcRestoreReplyError<E>>(reply.digest())
131        },
132    )
133    .await?;
134    super::qualified_receipt::checkpoint_reply(
135        stage,
136        acknowledgement,
137        settlement,
138        |source, acknowledgement| IcRestoreExecutionError::AfterReply {
139            source,
140            acknowledgement,
141        },
142    )
143}
144
145/// Original restore-step failure; no variant authorizes reissue or cleanup.
146#[derive(Debug, Error)]
147pub enum IcRestoreExecutionError<E: std::error::Error + 'static> {
148    /// Only an exact singleton original load/start stage is admitted.
149    #[error("restore requires an exact singleton original load/start stage")]
150    OriginalMismatch,
151    /// Original workflow/stage/ancestor admission failed.
152    #[error(transparent)]
153    Stage(#[from] ExecutionWorkflowPersistenceError),
154    /// Existing journal admission failed.
155    #[error(transparent)]
156    Journal(#[from] AttemptJournalError),
157    /// Complete original progress/prerequisites or reservation failed.
158    #[error(transparent)]
159    Progress(#[from] ExecutionProgressPersistenceError),
160    /// Exact original mutation binding failed.
161    #[error(transparent)]
162    Request(#[from] IcMutationRequestError),
163    /// Original source/requirement retention failed before reply.
164    #[error(transparent)]
165    Retention(#[from] IcRestoreReplyError<E>),
166    /// Integration-owned fresh admission failed after reservation.
167    #[error("fresh restore admission failed: {0}")]
168    Admission(#[source] E),
169    /// Existing current restore-safety policy rejected before dispatch.
170    #[error(transparent)]
171    Safety(#[from] RestoreSafetyError),
172    /// Exactly one provider call failed; original spending stays pending.
173    #[error(transparent)]
174    Provider(#[from] IcMutationProviderError),
175    /// Failure after reply retains its original bounded acknowledgement.
176    #[error("restore reply settlement failed: {source}")]
177    AfterReply {
178        /// Existing evidence/qualification/persistence rejection.
179        source: IcRestoreReplyError<E>,
180        /// Exact original bounded reply without an inferred outcome.
181        acknowledgement: Box<IcMutationAcknowledgement>,
182    },
183}
184
185/// Post-reply restore rejection, preserving original receipts and pending spending.
186#[derive(Debug, Error)]
187pub enum IcRestoreReplyError<E: std::error::Error + 'static> {
188    /// Existing snapshot or complete uploaded destination no longer matches.
189    #[error(transparent)]
190    Source(#[from] IcRestoreSourceError),
191    /// Original stage/ancestor/layout admission failed.
192    #[error(transparent)]
193    Stage(#[from] ExecutionWorkflowPersistenceError),
194    /// Existing original source/requirement admission failed.
195    #[error(transparent)]
196    Retention(#[from] RestoreSafetyPersistenceError),
197    /// Existing selected journal/receipt admission failed.
198    #[error(transparent)]
199    Journal(#[from] AttemptJournalError),
200    /// Existing bounded original acknowledgement association failed.
201    #[error(transparent)]
202    Association(#[from] IcMutationAssociationError),
203    /// Authentication/attribution or durable original-byte retention failed.
204    #[error("restore reply qualification failed: {0}")]
205    Qualification(#[source] E),
206    /// Require an explicit qualified exact original Applied receipt.
207    #[error("restore requires an explicit original Applied receipt")]
208    ReceiptRequired,
209    /// Existing checkpoint failed; Applied and occupied evidence remain.
210    #[error(transparent)]
211    Checkpoint(#[from] ExecutionStageCheckpointError),
212}
213
214#[cfg(test)]
215mod tests;