ic-backup 0.64.1

Host-side snapshot backup and same-release recovery for Internet Computer canisters
Documentation
//! Pure admission of independently qualified original capture settlement.

use crate::model::inventory_settlement::IcInventorySettlement;

use super::{IcObservationAssociationError, IcObservationReplyView, validate_response};
use crate::{
    model::{
        artifacts::ArtifactChecksumRecord,
        attempt_journal::AttemptJournalRecord,
        ic_observation::{IcObservationRequest, IcObservationResponse},
        ic_request::IcManagementMethodRecord as Method,
        ic_snapshot_reply::IcSnapshotReply,
    },
    policy::{
        inventory_settlement::{
            IcInventorySettlementView, InventorySettlementMismatch, admitted_snapshot_index,
            validate_pair,
        },
        settlement_identity::SettlementIdentity,
        snapshot_inventory_delta::SnapshotInventoryDeltaError,
    },
};
use thiserror::Error;

/// Bind independently qualified capture settlement to exact original/current inventories.
///
/// Reuses current reservations, actual claims, bounded decoding and the canonical
/// closed-baseline comparison. Every original ID and its metadata must remain unchanged.
/// Applied names an explicitly attributed new ID; zero/one/many candidates infer no
/// outcome. Negative evidence excludes transient capture/deletion. Unresolved needs
/// an actually settled authenticated list; lost replies remain pending.
///
/// Integrations own original baseline chronology, actual authentication, freshness,
/// read permission, attribution and custody. Matching digests establish none of those.
/// This performs no IO, calls, serialization, receipt transition, retries, refunds,
/// consistency/transfer qualification or terminal/fence/reference release admission.
/// # Errors
/// Rejects lifecycle/status lanes, changed reservations/claims/wire, baseline loss or
/// drift, settlement identity drift and an Applied ID outside the exact new candidates.
pub fn validate_capture_settlement<'a>(
    request: &IcObservationRequest<'a>,
    journal: &AttemptJournalRecord,
    baseline: &'a IcSnapshotReply<'a>,
    response: &'a IcObservationResponse,
    challenge: &ArtifactChecksumRecord,
    settlement: &'a IcInventorySettlement,
) -> Result<IcInventorySettlementView<'a>, IcCaptureSettlementError> {
    if request.mutation().method() != Method::TakeCanisterSnapshot
        || request.payload().method() != Method::ListCanisterSnapshots
    {
        return Err(IcCaptureSettlementError::UnsupportedMethod);
    }
    let observation = validate_response(request, journal, response)?;
    let IcObservationReplyView::Inventory(inventory) = observation.reply() else {
        return Err(IcCaptureSettlementError::UnsupportedMethod);
    };
    let authority = request.authority().digest();
    let candidates = validate_pair::<IcCaptureSettlementError>(
        request.mutation().target(),
        baseline,
        inventory,
        settlement,
        &SettlementIdentity {
            authority: &authority,
            mutation_attempt: request.mutation_attempt(),
            observation_attempt: request.observation_attempt(),
            challenge,
        },
        &response.input().evidence,
    )?;
    let snapshot_index = admitted_snapshot_index(inventory, &candidates, &settlement.attribution)
        .map_err(|()| IcCaptureSettlementError::NotNewCandidate)?;
    Ok(IcInventorySettlementView {
        observation,
        baseline,
        settlement,
        snapshot_index,
    })
}

/// Typed local claim denial; original allowances, obligations and references stay retained.
#[derive(Debug, Error)]
pub enum IcCaptureSettlementError {
    /// Only original non-replacing capture and a reserved list observation are supported.
    #[error("capture settlement requires original capture and list observation")]
    UnsupportedMethod,
    /// Existing association, current reservation or finite wire admission rejected.
    #[error(transparent)]
    Observation(#[from] IcObservationAssociationError),
    /// Existing closed full-baseline comparison rejected.
    #[error(transparent)]
    Baseline(#[from] SnapshotInventoryDeltaError),
    /// Exact original settlement identity or retained evidence differs.
    #[error(transparent)]
    Identity(#[from] InventorySettlementMismatch),
    /// The attributed raw ID is not a bounded new candidate in the current inventory.
    #[error("capture settlement ID is not a new candidate")]
    NotNewCandidate,
}

#[cfg(test)]
mod tests;