Skip to main content

ic_memory/runtime/
admission.rs

1use crate::{
2    AllocationDeclaration, AllocationLedger, AllocationRecord, AllocationState,
3    SealedDeclarationSnapshot, StableKey,
4};
5
6///
7/// BootstrapAdmissionError
8///
9/// Historical declaration completion rejected before staging or persistence.
10///
11
12#[non_exhaustive]
13#[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)]
14pub enum BootstrapAdmissionError {
15    #[error("historical key {0} is unknown")]
16    Unknown(StableKey),
17    #[error("historical key {0} is retired")]
18    Retired(StableKey),
19    #[error("key {0} is already declared or selected")]
20    Duplicate(StableKey),
21    #[error("completed declarations exceed 254 external allocations")]
22    TooManyDeclarations,
23    #[error(transparent)]
24    Pool(#[from] crate::MemoryAllocationPoolError),
25    #[error(transparent)]
26    Declarations(#[from] crate::MemoryDeclarationError),
27}
28
29///
30/// BootstrapAdmission
31///
32/// Bounded preparation context supplied only after validated ledger recovery.
33/// Consumers may reject identity transitions or explicitly include known
34/// historical allocations before the existing resolve/validate/commit boundary.
35/// No memory handles or mutable recovered state are exposed. Failed selections
36/// poison this attempt even if a consumer ignores their returned errors.
37///
38
39pub struct BootstrapAdmission<'a> {
40    ledger: &'a AllocationLedger,
41    declarations: &'a SealedDeclarationSnapshot,
42    pool: &'a crate::MemoryAllocationPool,
43    selected: Vec<AllocationDeclaration>,
44    failure: Option<BootstrapAdmissionError>,
45}
46
47impl<'a> BootstrapAdmission<'a> {
48    pub(super) const fn new(
49        ledger: &'a AllocationLedger,
50        declarations: &'a SealedDeclarationSnapshot,
51        pool: &'a crate::MemoryAllocationPool,
52    ) -> Self {
53        Self {
54            ledger,
55            declarations,
56            pool,
57            selected: Vec::new(),
58            failure: None,
59        }
60    }
61
62    /// Original sealed input; preparation cannot remove declarations or change host policy.
63    #[must_use]
64    pub const fn declarations(&self) -> &SealedDeclarationSnapshot {
65        self.declarations
66    }
67
68    /// Borrow at most 255 validated allocation records, including governance.
69    /// Records grant no memory access and contain no historical authority label;
70    /// current host namespace grants remain the authorization owner.
71    #[must_use = "recovered allocation metadata is inspected only when the iterator is consumed"]
72    pub fn recovered_allocations(&self) -> impl ExactSizeIterator<Item = &AllocationRecord> {
73        self.ledger.records().iter()
74    }
75
76    /// Whether the original input or an earlier selection already names this key.
77    #[must_use]
78    pub fn is_declared(&self, key: &StableKey) -> bool {
79        // Sealing owns canonical source request keys. Selections retain callback
80        // order, so only that unsealed tail needs a scan.
81        key.as_str() == crate::IC_MEMORY_LEDGER_STABLE_KEY
82            || self
83                .declarations
84                .requests()
85                .binary_search_by(|request| request.stable_key().cmp(key))
86                .is_ok()
87            || self
88                .selected
89                .iter()
90                .any(|declaration| declaration.stable_key() == key)
91    }
92
93    /// Include a known, nonretired key under an explicit current host namespace grant.
94    /// Retains its slot and latest schema metadata. Final current policy and all
95    /// ordinary collision/retirement checks still run after preparation.
96    pub fn include_historical(
97        &mut self,
98        authority: &str,
99        stable_key: &str,
100    ) -> Result<(), BootstrapAdmissionError> {
101        if let Some(error) = &self.failure {
102            return Err(error.clone());
103        }
104        let result = self.select(authority, stable_key);
105        if let Err(error) = &result {
106            self.failure = Some(error.clone());
107        }
108        result
109    }
110
111    fn select(&mut self, authority: &str, stable_key: &str) -> Result<(), BootstrapAdmissionError> {
112        // Request validation bounds names before selection diagnostics, without
113        // creating an owner string or schema that admission would discard.
114        let key = crate::requests::checked_request_key(authority, stable_key)?;
115        if self.is_declared(&key) {
116            return Err(BootstrapAdmissionError::Duplicate(key));
117        }
118        if self.declarations.requests().len() + self.selected.len() >= 254 {
119            return Err(BootstrapAdmissionError::TooManyDeclarations);
120        }
121        let Some(record) = self
122            .ledger
123            .records()
124            .iter()
125            .find(|r| r.stable_key() == &key)
126        else {
127            return Err(BootstrapAdmissionError::Unknown(key));
128        };
129        if matches!(record.state(), AllocationState::Retired) {
130            return Err(BootstrapAdmissionError::Retired(key));
131        }
132        self.pool.validate_authority(&key, authority)?;
133        self.pool.validate_id(record.slot().id())?;
134        self.selected.push(AllocationDeclaration {
135            stable_key: key,
136            slot: record.slot().clone(),
137            schema: record.schema().clone(),
138        });
139        Ok(())
140    }
141
142    pub(super) fn complete(self) -> Result<Vec<AllocationDeclaration>, BootstrapAdmissionError> {
143        if let Some(error) = self.failure {
144            return Err(error);
145        }
146        Ok(self.selected)
147    }
148}