Skip to main content

ic_backup/policy/inventory_settlement/
mod.rs

1//! Common exact identity and explicit new-candidate checks for inventory settlements.
2
3use crate::model::inventory_settlement::{IcInventoryAttribution, IcInventorySettlement};
4
5use super::ic_observation::IcObservationResponseView;
6
7use super::{
8    settlement_identity::{SettlementIdentity, SettlementIdentityMismatch},
9    snapshot_inventory_delta::{SnapshotInventoryDeltaError, compare_inventories},
10};
11use crate::model::{
12    artifacts::ArtifactChecksumRecord,
13    attempt_journal::ObservationOutcomeRecord,
14    ic_snapshot_reply::{IcSnapshotInfo, IcSnapshotReply},
15};
16
17fn compare_identity(
18    claimed: &IcInventorySettlement,
19    operation: &SettlementIdentity<'_>,
20    baseline: &ArtifactChecksumRecord,
21    inventory: &ArtifactChecksumRecord,
22    observation_evidence: &ArtifactChecksumRecord,
23) -> Result<(), InventorySettlementMismatch> {
24    SettlementIdentity::from(&claimed.context).compare(operation)?;
25    if &claimed.baseline != baseline {
26        return Err(InventorySettlementMismatch::Baseline);
27    }
28    if &claimed.inventory != inventory {
29        return Err(InventorySettlementMismatch::Inventory);
30    }
31    if &claimed.context.observation_evidence != observation_evidence {
32        return Err(InventorySettlementMismatch::ObservationEvidence);
33    }
34    Ok(())
35}
36
37/// Passive settlement claims differ from their independently qualified original evidence.
38#[derive(Debug, thiserror::Error, Eq, PartialEq)]
39pub enum InventorySettlementMismatch {
40    /// Full original operation authority differs.
41    #[error("inventory settlement authority differs")]
42    Authority,
43    /// Original mutation or observation attempt differs.
44    #[error("inventory settlement attempts differ")]
45    Attempt,
46    /// Caller-owned qualification challenge differs.
47    #[error("inventory settlement challenge differs")]
48    Challenge,
49    /// Exact retained baseline request and raw evidence differ.
50    #[error("inventory settlement baseline differs")]
51    Baseline,
52    /// Exact current inventory request and raw evidence differ.
53    #[error("inventory settlement inventory differs")]
54    Inventory,
55    /// Retained opaque observation evidence differs.
56    #[error("inventory settlement observation evidence differs")]
57    ObservationEvidence,
58}
59
60impl From<SettlementIdentityMismatch> for InventorySettlementMismatch {
61    fn from(value: SettlementIdentityMismatch) -> Self {
62        match value {
63            SettlementIdentityMismatch::Authority => Self::Authority,
64            SettlementIdentityMismatch::Attempt => Self::Attempt,
65            SettlementIdentityMismatch::Challenge => Self::Challenge,
66        }
67    }
68}
69
70pub(crate) fn validate_pair<'a, E>(
71    target: &str,
72    baseline: &IcSnapshotReply<'_>,
73    inventory: &'a IcSnapshotReply<'_>,
74    claimed: &IcInventorySettlement,
75    operation: &SettlementIdentity<'_>,
76    observation_evidence: &ArtifactChecksumRecord,
77) -> Result<Vec<&'a IcSnapshotInfo>, E>
78where
79    E: From<SnapshotInventoryDeltaError> + From<InventorySettlementMismatch>,
80{
81    let candidates = compare_inventories(target, baseline, inventory)?;
82    compare_identity(
83        claimed,
84        operation,
85        &baseline.digest(),
86        &inventory.digest(),
87        observation_evidence,
88    )?;
89    Ok(candidates)
90}
91
92// A snapshot index exists only for an explicitly attributed Applied claim.
93#[derive(Debug)]
94pub(crate) enum InventorySettlementOutcome {
95    Applied(usize),
96    NotApplied,
97    Uncertain,
98}
99
100impl InventorySettlementOutcome {
101    pub const fn outcome(&self) -> ObservationOutcomeRecord {
102        match self {
103            Self::Applied(_) => ObservationOutcomeRecord::Applied,
104            Self::NotApplied => ObservationOutcomeRecord::NotApplied,
105            Self::Uncertain => ObservationOutcomeRecord::Uncertain,
106        }
107    }
108
109    pub const fn snapshot_index(&self) -> Option<usize> {
110        match self {
111            Self::Applied(index) => Some(*index),
112            Self::NotApplied | Self::Uncertain => None,
113        }
114    }
115}
116
117pub(crate) fn admitted_outcome(
118    inventory: &IcSnapshotReply<'_>,
119    candidates: &[&IcSnapshotInfo],
120    attribution: &IcInventoryAttribution,
121) -> Result<InventorySettlementOutcome, ()> {
122    match attribution {
123        IcInventoryAttribution::Applied { snapshot_id, .. } => {
124            new_candidate_index(inventory, candidates, snapshot_id)
125                .map(InventorySettlementOutcome::Applied)
126                .ok_or(())
127        }
128        IcInventoryAttribution::NotApplied { .. } => Ok(InventorySettlementOutcome::NotApplied),
129        IcInventoryAttribution::Unresolved { .. } => Ok(InventorySettlementOutcome::Uncertain),
130    }
131}
132
133pub(crate) fn new_candidate_index(
134    inventory: &IcSnapshotReply<'_>,
135    candidates: &[&IcSnapshotInfo],
136    snapshot_id: &[u8],
137) -> Option<usize> {
138    if !candidates
139        .iter()
140        .any(|snapshot| snapshot.id() == snapshot_id)
141    {
142        return None;
143    }
144    inventory
145        .snapshots()
146        .binary_search_by(|snapshot| snapshot.id().cmp(snapshot_id))
147        .ok()
148}
149
150/// Matched passive inventory claim and exact inventories; never an authenticated receipt.
151#[derive(Debug)]
152pub struct IcInventorySettlementView<'a> {
153    pub(crate) observation: IcObservationResponseView<'a>,
154    pub(crate) baseline: &'a IcSnapshotReply<'a>,
155    pub(crate) settlement: &'a IcInventorySettlement,
156    pub(crate) resolution: InventorySettlementOutcome,
157}
158impl<'a> IcInventorySettlementView<'a> {
159    /// Read the independent claim; integration qualification precedes explicit recording.
160    #[must_use]
161    pub const fn outcome(&self) -> ObservationOutcomeRecord {
162        self.resolution.outcome()
163    }
164    /// Read the original independently retained full baseline.
165    #[must_use]
166    pub const fn baseline(&self) -> &'a IcSnapshotReply<'a> {
167        self.baseline
168    }
169    /// Read the exact current reserved list association.
170    #[must_use]
171    pub const fn observation(&self) -> &IcObservationResponseView<'a> {
172        &self.observation
173    }
174    /// Read the complete retained integration claims.
175    #[must_use]
176    pub const fn settlement(&self) -> &'a IcInventorySettlement {
177        self.settlement
178    }
179    /// Read only the explicitly attributed new descriptor, without singleton inference.
180    #[must_use]
181    pub fn snapshot(&self) -> Option<&IcSnapshotInfo> {
182        self.observation
183            .inventory_snapshot(self.resolution.snapshot_index()?)
184    }
185}
186
187#[cfg(test)]
188mod tests;