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 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("historical selection {stable_key} by {authority} lacks a current grant: {source}")]
44    Range {
45        stable_key: StableKey,
46        authority: String,
47        source: crate::MemoryManagerRangeAuthorityError,
48    },
49    #[error(transparent)]
50    Registry(#[from] crate::StaticMemoryDeclarationError),
51}
52
53///
54/// BootstrapAdmission
55///
56/// Bounded preparation context supplied only after validated ledger recovery.
57/// Consumers may reject identity transitions or explicitly include known
58/// historical allocations before the existing resolve/validate/commit boundary.
59/// No memory handles or mutable recovered state are exposed. Failed selections
60/// poison this attempt even if a consumer ignores their returned errors.
61///
62
63pub struct BootstrapAdmission<'a> {
64    ledger: &'a AllocationLedger,
65    declarations: &'a SealedDeclarationSnapshot,
66    selected: Vec<MemoryRequest>,
67    failure: Option<BootstrapAdmissionError>,
68}
69
70impl<'a> BootstrapAdmission<'a> {
71    pub(super) const fn new(
72        ledger: &'a AllocationLedger,
73        declarations: &'a SealedDeclarationSnapshot,
74    ) -> Self {
75        Self {
76            ledger,
77            declarations,
78            selected: Vec::new(),
79            failure: None,
80        }
81    }
82
83    /// Original sealed input; preparation cannot remove declarations or add grants.
84    #[must_use]
85    pub const fn declarations(&self) -> &SealedDeclarationSnapshot {
86        self.declarations
87    }
88
89    /// At most 255 validated allocation summaries, including governance records.
90    ///
91    /// # Panics
92    ///
93    /// Panics only if an internal validated-ledger invariant is broken.
94    #[must_use = "recovered allocation metadata is inspected only when the iterator is consumed"]
95    pub fn recovered_allocations(
96        &self,
97    ) -> impl ExactSizeIterator<Item = RecoveredAllocationMetadata<'_>> {
98        self.ledger
99            .allocation_history()
100            .records()
101            .iter()
102            .map(|record| RecoveredAllocationMetadata {
103                stable_key: record.stable_key(),
104                slot: record.slot(),
105                state: record.state(),
106                schema: record
107                    .schema_history()
108                    .last()
109                    .expect("validated schema history")
110                    .schema(),
111            })
112    }
113
114    /// Whether the original input or an earlier selection already names this key.
115    #[must_use]
116    pub fn is_declared(&self, key: &StableKey) -> bool {
117        // Sealing owns canonical fixed/request keys. Selections retain callback
118        // order, so only that unsealed tail needs a scan.
119        key.as_str() == crate::IC_MEMORY_LEDGER_STABLE_KEY
120            || self.declarations.registered_declaration(key).is_some()
121            || self
122                .declarations
123                .requests()
124                .binary_search_by(|request| request.stable_key().cmp(key))
125                .is_ok()
126            || self
127                .selected
128                .iter()
129                .any(|request| request.stable_key() == key)
130    }
131
132    /// Include a known, nonretired key under an explicit current host grant.
133    /// Retains its slot and latest schema metadata. Final current policy and all
134    /// ordinary collision/retirement checks still run after preparation.
135    pub fn include_historical(
136        &mut self,
137        authority: &str,
138        stable_key: &str,
139    ) -> Result<(), BootstrapAdmissionError> {
140        if let Some(error) = &self.failure {
141            return Err(error.clone());
142        }
143        let result = self.select(authority, stable_key);
144        if let Err(error) = &result {
145            self.failure = Some(error.clone());
146        }
147        result
148    }
149
150    fn select(&mut self, authority: &str, stable_key: &str) -> Result<(), BootstrapAdmissionError> {
151        // Constructor bounds names before they can enter selection diagnostics.
152        let request = MemoryRequest::new(authority, stable_key, SchemaMetadata::default())?;
153        let key = request.stable_key();
154        if self.is_declared(key) {
155            return Err(BootstrapAdmissionError::Duplicate(key.clone()));
156        }
157        if self.declarations.registered_declarations().len()
158            + self.declarations.requests().len()
159            + self.selected.len()
160            >= 254
161        {
162            return Err(BootstrapAdmissionError::TooManyDeclarations);
163        }
164        let record = self
165            .ledger
166            .allocation_history()
167            .records()
168            .iter()
169            .find(|r| r.stable_key() == key)
170            .ok_or_else(|| BootstrapAdmissionError::Unknown(key.clone()))?;
171        if matches!(record.state(), AllocationState::Retired { .. }) {
172            return Err(BootstrapAdmissionError::Retired(key.clone()));
173        }
174        self.declarations
175            .range_authority()
176            .validate_slot_authority(record.slot(), authority)
177            .map_err(|source| BootstrapAdmissionError::Range {
178                stable_key: key.clone(),
179                authority: authority.to_string(),
180                source,
181            })?;
182        self.selected.push(
183            request.with_schema(
184                record
185                    .schema_history()
186                    .last()
187                    .expect("validated schema history")
188                    .schema()
189                    .clone(),
190            ),
191        );
192        Ok(())
193    }
194
195    pub(super) fn complete(self) -> Result<Vec<MemoryRequest>, BootstrapAdmissionError> {
196        if let Some(error) = self.failure {
197            return Err(error);
198        }
199        Ok(self.selected)
200    }
201}