Skip to main content

ic_backup/policy/membership/
mod.rs

1//! Pure admission of a current provider result; no IO, scheduling or authority mutation.
2
3use crate::model::{
4    artifacts::ArtifactChecksumRecord,
5    inventory::InventoryRecord,
6    membership::{MembershipObservation, MembershipObservationRequest},
7};
8use thiserror::Error;
9
10/// Read-only matching observation; not a dispatch permit, continuity proof or fresh permission.
11#[derive(Clone, Debug)]
12pub struct MembershipView<'a> {
13    observation: &'a MembershipObservation,
14    selected_targets: &'a [String],
15}
16
17impl MembershipView<'_> {
18    /// Read the exact original challenge/boundary-bound request digest.
19    #[must_use]
20    pub const fn request(&self) -> &ArtifactChecksumRecord {
21        &self.observation.request
22    }
23    /// Read the complete matching current inventory supplied by the provider.
24    #[must_use]
25    pub const fn inventory(&self) -> &InventoryRecord {
26        &self.observation.inventory
27    }
28    /// Read original exact selection in canonical order, without dispatch order.
29    #[must_use]
30    pub const fn selected_targets(&self) -> &[String] {
31        self.selected_targets
32    }
33    /// Read optional current revision; matching revisions alone prove no continuity.
34    #[must_use]
35    pub const fn revision(&self) -> Option<&ArtifactChecksumRecord> {
36        self.observation.revision.as_ref()
37    }
38    /// Read the provider-owned opaque evidence identifier, not a signature or permission.
39    #[must_use]
40    pub const fn evidence(&self) -> &ArtifactChecksumRecord {
41        &self.observation.evidence
42    }
43    /// Read reported calls; this does not reserve, refund or replenish any allowance.
44    #[must_use]
45    pub const fn remote_observations(&self) -> u32 {
46        self.observation.remote_observations
47    }
48}
49
50/// Validate exact current request/context/full inventory and descriptive call bound.
51///
52/// The caller qualifies provider authenticity, freshness and coherent custody.
53/// This policy calls no provider, serializes no records, changes no plan/journal
54/// and makes no application consistency, lifecycle or permission claim.
55/// # Errors
56/// Rejects another request, context, inventory or excessive reported observations.
57pub fn validate<'a>(
58    request: &'a MembershipObservationRequest<'_>,
59    observation: &'a MembershipObservation,
60) -> Result<MembershipView<'a>, MembershipError> {
61    let digest = request.digest();
62    let binding = request.binding();
63    super::observed_context::admit_request_context(
64        &digest,
65        &observation.request,
66        binding,
67        &observation.context,
68        MembershipError::RequestMismatch,
69        MembershipError::ContextMismatch,
70    )?;
71    if observation.inventory != *request.inventory() {
72        return Err(MembershipError::InventoryMismatch);
73    }
74    super::observed_context::admit_limit(
75        request.max_remote_observations(),
76        observation.remote_observations,
77        |limit, reported| MembershipError::ObservationLimitExceeded { limit, reported },
78    )?;
79    Ok(MembershipView {
80        observation,
81        selected_targets: request.selected_targets(),
82    })
83}
84
85/// Typed mismatch; no rejected result admits an effect or changes original authority.
86#[derive(Debug, Eq, Error, PartialEq)]
87pub enum MembershipError {
88    /// Original intent, operation, challenge, boundary or descriptive ceiling changed.
89    #[error("membership request identity mismatch")]
90    RequestMismatch,
91    /// Actually observed canonical context differs from the original declaration.
92    #[error("membership observed {0} mismatch")]
93    ContextMismatch(&'static str),
94    /// Full current inventory differs, including unselected relationships/metadata.
95    #[error("membership inventory mismatch")]
96    InventoryMismatch,
97    /// Provider reports more remote observations than the descriptive request permits.
98    #[error("membership reports {reported} observations above ceiling {limit}")]
99    ObservationLimitExceeded {
100        /// Original descriptive ceiling; not a journal spending allowance.
101        limit: u32,
102        /// Actual provider-reported remote observations.
103        reported: u32,
104    },
105}
106
107#[cfg(test)]
108mod tests;