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() >= 254 {
116            return Err(BootstrapAdmissionError::TooManyDeclarations);
117        }
118        let Some(record) = self
119            .ledger
120            .records()
121            .iter()
122            .find(|r| r.stable_key() == &key)
123        else {
124            return Err(BootstrapAdmissionError::Unknown(key));
125        };
126        self.pool.validate_authority(&key, authority)?;
127        self.pool.validate_id(record.slot().id())?;
128        self.selected.push(AllocationDeclaration {
129            stable_key: key,
130            slot: record.slot().clone(),
131        });
132        Ok(())
133    }
134
135    pub(super) fn complete(self) -> Result<Vec<AllocationDeclaration>, BootstrapAdmissionError> {
136        if let Some(error) = self.failure {
137            return Err(error);
138        }
139        Ok(self.selected)
140    }
141}