Skip to main content

ic_memory/runtime/
admission.rs

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