Skip to main content

ic_backup/policy/ic_observation/capture_settlement/
mod.rs

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