Skip to main content

ic_memory/runtime/
admission.rs

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