Skip to main content

ic_backup/policy/ic_observation/capture_settlement/
mod.rs

1//! Pure admission of independently qualified original capture settlement.
2
3use super::{
4    IcObservationAssociationError, IcObservationReplyView, IcObservationResponseView,
5    validate_response,
6};
7use crate::{
8    model::{
9        artifacts::ArtifactChecksumRecord,
10        attempt_journal::{AttemptJournalRecord, ObservationOutcomeRecord},
11        ic_observation::{IcCaptureSettlement, IcObservationRequest, IcObservationResponse},
12        ic_request::IcManagementMethodRecord as Method,
13        ic_snapshot_reply::{IcSnapshotInfo, IcSnapshotReply},
14    },
15    policy::{
16        inventory_settlement::{
17            InventoryAttribution, InventorySettlementIdentity, InventorySettlementMismatch,
18            admitted_outcome, validate_pair,
19        },
20        settlement_identity::SettlementIdentity,
21        snapshot_inventory_delta::SnapshotInventoryDeltaError,
22    },
23};
24use thiserror::Error;
25
26#[cfg(test)]
27use crate::model::ic_observation::IcCaptureAttribution;
28
29/// Matched passive capture claim and exact inventories; never an authenticated receipt.
30#[derive(Debug)]
31pub struct IcCaptureSettlementView<'a> {
32    observation: IcObservationResponseView<'a>,
33    baseline: &'a IcSnapshotReply<'a>,
34    settlement: &'a IcCaptureSettlement,
35    captured_snapshot: Option<usize>,
36    outcome: ObservationOutcomeRecord,
37}
38impl<'a> IcCaptureSettlementView<'a> {
39    /// Read the independent claim; integration qualification precedes explicit recording.
40    #[must_use]
41    pub const fn outcome(&self) -> ObservationOutcomeRecord {
42        self.outcome
43    }
44    /// Read the original independently retained full baseline.
45    #[must_use]
46    pub const fn baseline(&self) -> &'a IcSnapshotReply<'a> {
47        self.baseline
48    }
49    /// Read the exact current reserved list association.
50    #[must_use]
51    pub const fn observation(&self) -> &IcObservationResponseView<'a> {
52        &self.observation
53    }
54    /// Read the complete retained integration claims.
55    #[must_use]
56    pub const fn settlement(&self) -> &'a IcCaptureSettlement {
57        self.settlement
58    }
59    /// Read only the explicitly attributed new descriptor, without singleton inference.
60    #[must_use]
61    pub fn captured_snapshot(&self) -> Option<&IcSnapshotInfo> {
62        self.observation.inventory_snapshot(self.captured_snapshot?)
63    }
64}
65
66/// Bind independently qualified capture settlement to exact original/current inventories.
67///
68/// Reuses current reservations, actual claims, bounded decoding and the canonical
69/// closed-baseline comparison. Every original ID and its metadata must remain unchanged.
70/// Applied names an explicitly attributed new ID; zero/one/many candidates infer no
71/// outcome. Negative evidence excludes transient capture/deletion. Unresolved needs
72/// an actually settled authenticated list; lost replies remain pending.
73///
74/// Integrations own original baseline chronology, actual authentication, freshness,
75/// read permission, attribution and custody. Matching digests establish none of those.
76/// This performs no IO, calls, serialization, receipt transition, retries, refunds,
77/// consistency/transfer qualification or terminal/fence/reference release admission.
78/// # Errors
79/// Rejects lifecycle/status lanes, changed reservations/claims/wire, baseline loss or
80/// drift, settlement identity drift and an Applied ID outside the exact new candidates.
81pub fn validate_capture_settlement<'a>(
82    request: &IcObservationRequest<'a>,
83    journal: &AttemptJournalRecord,
84    baseline: &'a IcSnapshotReply<'a>,
85    response: &'a IcObservationResponse,
86    challenge: &ArtifactChecksumRecord,
87    settlement: &'a IcCaptureSettlement,
88) -> Result<IcCaptureSettlementView<'a>, IcCaptureSettlementError> {
89    if request.mutation().method() != Method::TakeCanisterSnapshot
90        || request.payload().method() != Method::ListCanisterSnapshots
91    {
92        return Err(IcCaptureSettlementError::UnsupportedMethod);
93    }
94    let observation = validate_response(request, journal, response)?;
95    let IcObservationReplyView::Inventory(inventory) = observation.reply() else {
96        return Err(IcCaptureSettlementError::UnsupportedMethod);
97    };
98    let authority = request.authority().digest();
99    let candidates = validate_pair::<IcCaptureSettlementError>(
100        request.mutation().target(),
101        baseline,
102        inventory,
103        &InventorySettlementIdentity {
104            operation: SettlementIdentity {
105                authority: &settlement.authority,
106                mutation_attempt: settlement.mutation_attempt,
107                observation_attempt: settlement.observation_attempt,
108                challenge: &settlement.challenge,
109            },
110            baseline: &settlement.baseline,
111            inventory: &settlement.inventory,
112            observation_evidence: &settlement.observation_evidence,
113        },
114        &SettlementIdentity {
115            authority: &authority,
116            mutation_attempt: request.mutation_attempt(),
117            observation_attempt: request.observation_attempt(),
118            challenge,
119        },
120        &response.input().evidence,
121    )?;
122    let (outcome, captured_snapshot) = admitted_outcome(
123        inventory,
124        &candidates,
125        InventoryAttribution::from(&settlement.attribution),
126    )
127    .map_err(|()| IcCaptureSettlementError::NotNewCandidate)?;
128    Ok(IcCaptureSettlementView {
129        observation,
130        baseline,
131        settlement,
132        captured_snapshot,
133        outcome,
134    })
135}
136
137/// Typed local claim denial; original allowances, obligations and references stay retained.
138#[derive(Debug, Error)]
139pub enum IcCaptureSettlementError {
140    /// Only original non-replacing capture and a reserved list observation are supported.
141    #[error("capture settlement requires original capture and list observation")]
142    UnsupportedMethod,
143    /// Existing association, current reservation or finite wire admission rejected.
144    #[error(transparent)]
145    Observation(#[from] IcObservationAssociationError),
146    /// Existing closed full-baseline comparison rejected.
147    #[error(transparent)]
148    Baseline(#[from] SnapshotInventoryDeltaError),
149    /// Exact original settlement identity or retained evidence differs.
150    #[error(transparent)]
151    Identity(#[from] InventorySettlementMismatch),
152    /// The attributed raw ID is not a bounded new candidate in the current inventory.
153    #[error("capture settlement ID is not a new candidate")]
154    NotNewCandidate,
155}
156
157#[cfg(test)]
158mod tests;