Skip to main content

ic_backup/policy/ic_snapshot_upload_observation/settlement/
mod.rs

1//! Pure original metadata-allocation settlement admission.
2use super::validate_response;
3use crate::{
4    model::{
5        artifacts::ArtifactChecksumRecord,
6        attempt_journal::{AttemptJournalRecord, ObservationOutcomeRecord},
7        ic_observation::IcObservationResponse,
8        ic_snapshot_reply::{IcSnapshotInfo, IcSnapshotReply},
9        ic_snapshot_upload::IcSnapshotUploadError,
10        ic_snapshot_upload_observation::IcSnapshotUploadObservationRequest,
11    },
12    policy::{
13        ic_observation::{
14            IcObservationAssociationError, IcObservationReplyView, IcObservationResponseView,
15        },
16        inventory_settlement::{
17            InventorySettlementIdentity, InventorySettlementMismatch, InventorySettlementOutcome,
18            admitted_outcome, validate_pair,
19        },
20        settlement_identity::SettlementIdentity,
21        snapshot_inventory_delta::SnapshotInventoryDeltaError,
22    },
23};
24use thiserror::Error;
25
26use crate::model::inventory_settlement::IcInventoryAttribution;
27use crate::model::inventory_settlement::IcInventorySettlement;
28
29/// Exact local evidence and passive attribution; never an authenticated receipt.
30#[derive(Debug)]
31pub struct IcSnapshotUploadSettlementView<'a> {
32    observation: IcObservationResponseView<'a>,
33    baseline: &'a IcSnapshotReply<'a>,
34    settlement: &'a IcInventorySettlement,
35    resolution: InventorySettlementOutcome,
36}
37impl<'a> IcSnapshotUploadSettlementView<'a> {
38    /// Read the passive claim; only a qualified integration may record it.
39    #[must_use]
40    pub const fn outcome(&self) -> ObservationOutcomeRecord {
41        self.resolution.outcome()
42    }
43    /// Read the original independently retained baseline.
44    #[must_use]
45    pub const fn baseline(&self) -> &'a IcSnapshotReply<'a> {
46        self.baseline
47    }
48    /// Read the exact bounded current 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 IcInventorySettlement {
56        self.settlement
57    }
58    /// Read only the explicitly attributed new descriptor, with no singleton inference.
59    #[must_use]
60    pub fn allocated_snapshot(&self) -> Option<&IcSnapshotInfo> {
61        self.observation
62            .inventory_snapshot(self.resolution.snapshot_index()?)
63    }
64}
65
66/// Admit separately qualified allocation claims against exact original and current lists.
67///
68/// Every baseline ID and its metadata must remain unchanged. Applied requires an
69/// explicitly attributed new ID distinct from the retained source; several candidates
70/// may exist. Zero/one/many candidates imply no outcome. Negative evidence must exclude
71/// transient allocation/deletion. Unresolved requires a settled authenticated list,
72/// never a lost response. Integrations own original baseline chronology, actual
73/// authentication, freshness, attribution and stable custody; matching digests prove
74/// none of these. No calls, IO, receipts, retries, refunds or release admission occur.
75/// # Errors
76/// Rejects changed reservations/claims/wire, baseline drift, settlement identity drift
77/// and an Applied ID outside the exact new candidate set or original destination bounds.
78pub fn validate_settlement<'a>(
79    request: &IcSnapshotUploadObservationRequest<'a, '_>,
80    journal: &AttemptJournalRecord,
81    baseline: &'a IcSnapshotReply<'a>,
82    response: &'a IcObservationResponse,
83    challenge: &ArtifactChecksumRecord,
84    settlement: &'a IcInventorySettlement,
85) -> Result<IcSnapshotUploadSettlementView<'a>, IcSnapshotUploadSettlementError> {
86    let observation = validate_response(request, journal, response)?;
87    let IcObservationReplyView::Inventory(inventory) = observation.reply() else {
88        return Err(IcSnapshotUploadSettlementError::WrongInventory);
89    };
90    let authority = request.authority().digest();
91    let candidates = validate_pair::<IcSnapshotUploadSettlementError>(
92        request.payload().target(),
93        baseline,
94        inventory,
95        &InventorySettlementIdentity {
96            operation: SettlementIdentity {
97                authority: &settlement.authority,
98                mutation_attempt: settlement.mutation_attempt,
99                observation_attempt: settlement.observation_attempt,
100                challenge: &settlement.challenge,
101            },
102            baseline: &settlement.baseline,
103            inventory: &settlement.inventory,
104            observation_evidence: &settlement.observation_evidence,
105        },
106        &SettlementIdentity {
107            authority: &authority,
108            mutation_attempt: request.mutation_attempt(),
109            observation_attempt: request.observation_attempt(),
110            challenge,
111        },
112        &response.input().evidence,
113    )?;
114    if let IcInventoryAttribution::Applied { snapshot_id, .. } = &settlement.attribution {
115        request.mutation().validate_data_destination(snapshot_id)?;
116    }
117    let resolution = admitted_outcome(inventory, &candidates, &settlement.attribution)
118        .map_err(|()| IcSnapshotUploadSettlementError::NotNewCandidate)?;
119    Ok(IcSnapshotUploadSettlementView {
120        observation,
121        baseline,
122        settlement,
123        resolution,
124    })
125}
126
127/// Local admission denial; original spending and obligations remain retained.
128#[derive(Debug, Error)]
129pub enum IcSnapshotUploadSettlementError {
130    /// Existing exact reservation, claim or bounded wire admission failed.
131    #[error(transparent)]
132    Observation(#[from] IcObservationAssociationError),
133    /// Existing canonical closed-baseline comparison failed.
134    #[error(transparent)]
135    Baseline(#[from] SnapshotInventoryDeltaError),
136    /// Existing raw destination bounds or source-ID exclusion failed.
137    #[error(transparent)]
138    Destination(#[from] IcSnapshotUploadError),
139    /// A status projection cannot substitute for the original list.
140    #[error("metadata settlement requires snapshot inventory")]
141    WrongInventory,
142    /// Exact original settlement identity or retained evidence differs.
143    #[error(transparent)]
144    Identity(#[from] InventorySettlementMismatch),
145    /// The attributed ID is not a new candidate in the exact current inventory.
146    #[error("metadata settlement ID is not a new candidate")]
147    NotNewCandidate,
148}
149
150#[cfg(test)]
151mod tests;