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            InventorySettlementOutcome, 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    resolution: InventorySettlementOutcome,
36}
37impl<'a> IcCaptureSettlementView<'a> {
38    /// Read the independent claim; integration qualification precedes explicit recording.
39    #[must_use]
40    pub const fn outcome(&self) -> ObservationOutcomeRecord {
41        self.resolution.outcome()
42    }
43    /// Read the original independently retained full baseline.
44    #[must_use]
45    pub const fn baseline(&self) -> &'a IcSnapshotReply<'a> {
46        self.baseline
47    }
48    /// Read the exact current reserved list association.
49    #[must_use]
50    pub const fn observation(&self) -> &IcObservationResponseView<'a> {
51        &self.observation
52    }
53    /// Read the complete retained integration claims.
54    #[must_use]
55    pub const fn settlement(&self) -> &'a IcCaptureSettlement {
56        self.settlement
57    }
58    /// Read only the explicitly attributed new descriptor, without singleton inference.
59    #[must_use]
60    pub fn captured_snapshot(&self) -> Option<&IcSnapshotInfo> {
61        self.observation
62            .inventory_snapshot(self.resolution.snapshot_index()?)
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 resolution = 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        resolution,
133    })
134}
135
136/// Typed local claim denial; original allowances, obligations and references stay retained.
137#[derive(Debug, Error)]
138pub enum IcCaptureSettlementError {
139    /// Only original non-replacing capture and a reserved list observation are supported.
140    #[error("capture settlement requires original capture and list observation")]
141    UnsupportedMethod,
142    /// Existing association, current reservation or finite wire admission rejected.
143    #[error(transparent)]
144    Observation(#[from] IcObservationAssociationError),
145    /// Existing closed full-baseline comparison rejected.
146    #[error(transparent)]
147    Baseline(#[from] SnapshotInventoryDeltaError),
148    /// Exact original settlement identity or retained evidence differs.
149    #[error(transparent)]
150    Identity(#[from] InventorySettlementMismatch),
151    /// The attributed raw ID is not a bounded new candidate in the current inventory.
152    #[error("capture settlement ID is not a new candidate")]
153    NotNewCandidate,
154}
155
156#[cfg(test)]
157mod tests;