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 baseline_digest = baseline.digest();
100    let inventory_digest = inventory.digest();
101    let candidates = validate_pair::<IcCaptureSettlementError>(
102        request.mutation().target(),
103        baseline,
104        inventory,
105        &InventorySettlementIdentity {
106            operation: SettlementIdentity {
107                authority: &settlement.authority,
108                mutation_attempt: settlement.mutation_attempt,
109                observation_attempt: settlement.observation_attempt,
110                challenge: &settlement.challenge,
111            },
112            baseline: &settlement.baseline,
113            inventory: &settlement.inventory,
114            observation_evidence: &settlement.observation_evidence,
115        },
116        &InventorySettlementIdentity {
117            operation: SettlementIdentity {
118                authority: &authority,
119                mutation_attempt: request.mutation_attempt(),
120                observation_attempt: request.observation_attempt(),
121                challenge,
122            },
123            baseline: &baseline_digest,
124            inventory: &inventory_digest,
125            observation_evidence: &response.input().evidence,
126        },
127    )?;
128    let (outcome, captured_snapshot) = admitted_outcome(
129        inventory,
130        &candidates,
131        InventoryAttribution::from(&settlement.attribution),
132    )
133    .map_err(|()| IcCaptureSettlementError::NotNewCandidate)?;
134    Ok(IcCaptureSettlementView {
135        observation,
136        baseline,
137        settlement,
138        captured_snapshot,
139        outcome,
140    })
141}
142
143/// Typed local claim denial; original allowances, obligations and references stay retained.
144#[derive(Debug, Error)]
145pub enum IcCaptureSettlementError {
146    /// Only original non-replacing capture and a reserved list observation are supported.
147    #[error("capture settlement requires original capture and list observation")]
148    UnsupportedMethod,
149    /// Existing association, current reservation or finite wire admission rejected.
150    #[error(transparent)]
151    Observation(#[from] IcObservationAssociationError),
152    /// Existing closed full-baseline comparison rejected.
153    #[error(transparent)]
154    Baseline(#[from] SnapshotInventoryDeltaError),
155    /// Exact original settlement identity or retained evidence differs.
156    #[error(transparent)]
157    Identity(#[from] InventorySettlementMismatch),
158    /// The attributed raw ID is not a bounded new candidate in the current inventory.
159    #[error("capture settlement ID is not a new candidate")]
160    NotNewCandidate,
161}
162
163#[cfg(test)]
164mod tests;