Skip to main content

ic_backup/policy/ic_snapshot_upload_data_observation/settlement/
mod.rs

1//! Local admission of already qualified original-write settlement claims.
2
3use super::{
4    IcSnapshotUploadDataObservationAssociationError, IcSnapshotUploadDataObservationView,
5    validate_response,
6};
7use crate::model::{
8    artifacts::ArtifactChecksumRecord,
9    attempt_journal::{AttemptJournalRecord, ObservationOutcomeRecord},
10    ic_snapshot_upload_data_observation::{
11        IcSnapshotUploadDataAttribution, IcSnapshotUploadDataObservationRequest,
12        IcSnapshotUploadDataObservationResponse, IcSnapshotUploadDataSettlement,
13    },
14};
15use crate::policy::settlement_identity::{SettlementIdentity, SettlementIdentityMismatch};
16use thiserror::Error;
17
18/// Matched passive attribution and exact readback; never an authenticated receipt.
19#[derive(Debug)]
20pub struct IcSnapshotUploadDataSettlementView<'request, 'metadata> {
21    observation: IcSnapshotUploadDataObservationView<'request, 'metadata>,
22    settlement: &'request IcSnapshotUploadDataSettlement,
23    outcome: ObservationOutcomeRecord,
24}
25impl<'request, 'metadata> IcSnapshotUploadDataSettlementView<'request, 'metadata> {
26    /// Read the passive claimed outcome; only a qualified integration may record it.
27    #[must_use]
28    pub const fn outcome(&self) -> ObservationOutcomeRecord {
29        self.outcome
30    }
31    /// Read the existing exact bounded readback projection, with no additional decoding owner.
32    #[must_use]
33    pub const fn observation(&self) -> &IcSnapshotUploadDataObservationView<'request, 'metadata> {
34        &self.observation
35    }
36    /// Read retained attribution and complete settlement evidence, without copying counters.
37    #[must_use]
38    pub const fn settlement(&self) -> &'request IcSnapshotUploadDataSettlement {
39        self.settlement
40    }
41}
42
43/// Match original reservations, fresh challenge and exact independently qualified read evidence.
44///
45/// Applied requires matching original bytes plus exclusive original-write attribution;
46/// byte equality alone supplies no claim. `NotApplied` requires qualified exclusion,
47/// even when initialized/preexisting bytes match. Unresolved requires an actually
48/// settled authenticated read; lost replies cannot enter this successful-read boundary.
49///
50/// Authentication, original allocation/write attribution, challenge freshness and
51/// stable custody remain integration-owned. This performs no IO, remote call, receipt
52/// transition, retry, refund or load/start/terminal/reference/fence-release admission.
53/// # Errors
54/// Rejects current reservation/claim/codec drift, changed settlement identity or evidence,
55/// stale challenge, and an Applied claim whose current bytes differ from original intent.
56pub fn validate_settlement<'request, 'metadata>(
57    request: &IcSnapshotUploadDataObservationRequest<'request, '_, 'metadata>,
58    journal: &AttemptJournalRecord,
59    response: &'request IcSnapshotUploadDataObservationResponse,
60    challenge: &ArtifactChecksumRecord,
61    settlement: &'request IcSnapshotUploadDataSettlement,
62) -> Result<
63    IcSnapshotUploadDataSettlementView<'request, 'metadata>,
64    IcSnapshotUploadDataSettlementError,
65> {
66    let observation = validate_response(request, journal, response)?;
67    SettlementIdentity {
68        authority: &settlement.authority,
69        mutation_attempt: settlement.mutation_attempt,
70        observation_attempt: settlement.observation_attempt,
71        challenge: &settlement.challenge,
72    }
73    .compare(&SettlementIdentity {
74        authority: &request.authority().digest(),
75        mutation_attempt: request.mutation_attempt(),
76        observation_attempt: request.observation_attempt(),
77        challenge,
78    })?;
79    if settlement.readback != observation.reply().digest() {
80        return Err(IcSnapshotUploadDataSettlementError::ReadbackMismatch);
81    }
82    if settlement.observation_evidence != response.input().evidence {
83        return Err(IcSnapshotUploadDataSettlementError::ObservationEvidenceMismatch);
84    }
85    let outcome = match &settlement.attribution {
86        IcSnapshotUploadDataAttribution::Applied { .. } => {
87            if !observation.matches_original_chunk() {
88                return Err(IcSnapshotUploadDataSettlementError::AppliedBytesMismatch);
89            }
90            ObservationOutcomeRecord::Applied
91        }
92        IcSnapshotUploadDataAttribution::NotApplied { .. } => ObservationOutcomeRecord::NotApplied,
93        IcSnapshotUploadDataAttribution::Unresolved { .. } => ObservationOutcomeRecord::Uncertain,
94    };
95    Ok(IcSnapshotUploadDataSettlementView {
96        observation,
97        settlement,
98        outcome,
99    })
100}
101
102/// Exact local settlement admission denial; all spending and obligations remain retained.
103#[derive(Debug, Error)]
104pub enum IcSnapshotUploadDataSettlementError {
105    /// Existing reservation, passive actual claims or data decoder rejected.
106    #[error(transparent)]
107    Observation(#[from] IcSnapshotUploadDataObservationAssociationError),
108    /// Exact original settlement identity or retained evidence differs.
109    #[error(transparent)]
110    Identity(#[from] SettlementIdentityMismatch),
111    /// Exact metadata/request/raw reply digest differs.
112    #[error("data upload settlement readback differs")]
113    ReadbackMismatch,
114    /// Retained opaque observation evidence differs despite potentially equal bytes.
115    #[error("data upload settlement observation evidence differs")]
116    ObservationEvidenceMismatch,
117    /// Applied cannot admit a current extent different from the original encoded write.
118    #[error("data upload settlement applied bytes differ from original intent")]
119    AppliedBytesMismatch,
120}
121
122#[cfg(test)]
123mod tests;