Skip to main content

ic_memory/
runtime.rs

1use crate::{
2    AllocationBootstrap, AllocationDeclaration, AllocationHistory, AllocationLedger,
3    AllocationPolicy, AllocationSlotDescriptor, DeclarationSnapshot, StableCellLedgerError,
4    StableCellLedgerRecord, StableKey, ValidatedAllocations,
5    registry::{
6        StaticMemoryDeclaration, StaticMemoryDeclarationError, StaticMemoryRangeDeclaration,
7        seal_static_memory_registry, static_memory_declarations, static_memory_range_declarations,
8    },
9    slot::{
10        IC_MEMORY_AUTHORITY_OWNER, IC_MEMORY_AUTHORITY_PURPOSE, IC_MEMORY_LEDGER_LABEL,
11        IC_MEMORY_LEDGER_STABLE_KEY, MEMORY_MANAGER_LEDGER_ID, MemoryManagerAuthorityRecord,
12        MemoryManagerIdRange, MemoryManagerRangeAuthority, MemoryManagerRangeAuthorityError,
13        MemoryManagerRangeMode, MemoryManagerSlotError,
14    },
15};
16use ic_stable_structures::{
17    Cell, DefaultMemoryImpl,
18    memory_manager::{MemoryId, MemoryManager, VirtualMemory},
19};
20use std::{
21    cell::RefCell,
22    collections::BTreeMap,
23    convert::Infallible,
24    sync::{
25        Mutex,
26        atomic::{AtomicBool, Ordering},
27    },
28};
29
30type DefaultLedgerCell = Cell<StableCellLedgerRecord, VirtualMemory<DefaultMemoryImpl>>;
31
32thread_local! {
33    static DEFAULT_MEMORY_MANAGER: MemoryManager<DefaultMemoryImpl> =
34        MemoryManager::init(DefaultMemoryImpl::default());
35    static DEFAULT_LEDGER_CELL: RefCell<Option<DefaultLedgerCell>> = const {
36        RefCell::new(None)
37    };
38}
39
40static EAGER_INIT_HOOKS: Mutex<Vec<fn()>> = Mutex::new(Vec::new());
41static VALIDATED_ALLOCATIONS: Mutex<Option<ValidatedAllocations>> = Mutex::new(None);
42static BOOTSTRAPPED: AtomicBool = AtomicBool::new(false);
43
44///
45/// RuntimeBootstrapError
46///
47/// Failure to bootstrap the generic `ic-memory` runtime layer.
48#[derive(Debug, thiserror::Error)]
49pub enum RuntimeBootstrapError<P> {
50    /// Runtime registration or snapshot collection failed.
51    #[error(transparent)]
52    Registry(#[from] StaticMemoryDeclarationError),
53    /// Runtime range authority table is invalid.
54    #[error(transparent)]
55    Range(#[from] MemoryManagerRangeAuthorityError),
56    /// Runtime ledger genesis construction failed.
57    #[error(transparent)]
58    LedgerIntegrity(#[from] crate::LedgerIntegrityError),
59    /// Protected ledger recovery or commit failed.
60    #[error(transparent)]
61    LedgerCommit(#[from] crate::LedgerCommitError),
62    /// Stable-cell ledger storage is corrupt before protected recovery can run.
63    #[error(transparent)]
64    StableCellLedger(#[from] StableCellLedgerError),
65    /// Declaration validation failed.
66    #[error(transparent)]
67    Validation(#[from] crate::AllocationValidationError<RuntimePolicyError<P>>),
68    /// Validated declarations could not be staged.
69    #[error(transparent)]
70    Staging(#[from] crate::AllocationStageError),
71    /// Runtime state lock was poisoned.
72    #[error("ic-memory runtime lock poisoned")]
73    RuntimeLockPoisoned,
74}
75
76///
77/// RuntimeOpenError
78///
79/// Failure to open a validated allocation through the default runtime substrate.
80#[derive(Clone, Debug, Eq, thiserror::Error, PartialEq)]
81pub enum RuntimeOpenError {
82    /// Runtime bootstrap has not published validated allocations.
83    #[error("ic-memory runtime has not completed bootstrap validation")]
84    NotBootstrapped,
85    /// Runtime state lock was poisoned.
86    #[error("ic-memory runtime lock poisoned")]
87    RuntimeLockPoisoned,
88    /// Stable-key grammar failure.
89    #[error(transparent)]
90    StableKey(#[from] crate::StableKeyError),
91    /// The stable key was not present in the validated declaration set.
92    #[error("stable key '{0}' was not validated by ic-memory runtime bootstrap")]
93    StableKeyNotValidated(String),
94    /// The validated slot is not a usable `MemoryManager` ID.
95    #[error(transparent)]
96    MemoryManagerSlot(#[from] MemoryManagerSlotError),
97    /// The requested memory ID does not match the validated stable-key binding.
98    #[error(
99        "stable key '{stable_key}' is validated for MemoryManager ID {validated_id}, not requested ID {requested_id}"
100    )]
101    MemoryIdMismatch {
102        /// Stable key being opened.
103        stable_key: String,
104        /// Validated MemoryManager ID.
105        validated_id: u8,
106        /// Requested MemoryManager ID.
107        requested_id: u8,
108    },
109}
110
111///
112/// RuntimePolicyError
113///
114/// Failure in generic runtime range policy or caller-supplied policy.
115#[derive(Clone, Debug, Eq, thiserror::Error, PartialEq)]
116pub enum RuntimePolicyError<P> {
117    /// Runtime range authority rejected the declaration.
118    #[error(transparent)]
119    Range(#[from] MemoryManagerRangeAuthorityError),
120    /// Runtime metadata is internally inconsistent.
121    #[error("runtime declaration metadata is missing for stable key '{0}'")]
122    MissingDeclarationMetadata(String),
123    /// `ic_memory.*` stable keys are reserved to the `ic-memory` authority.
124    #[error("stable key '{stable_key}' is reserved to authority '{expected_authority}'")]
125    ReservedStableKeyAuthority {
126        /// Stable key being declared.
127        stable_key: String,
128        /// Required declaring authority.
129        expected_authority: &'static str,
130    },
131    /// Caller-supplied policy rejected the declaration.
132    #[error(transparent)]
133    Custom(P),
134}
135
136/// Register a pre-bootstrap declaration hook.
137pub fn defer_eager_init(f: fn()) {
138    assert!(
139        !is_default_memory_manager_bootstrapped(),
140        "ic-memory eager-init registration attempted after runtime bootstrap"
141    );
142    EAGER_INIT_HOOKS
143        .lock()
144        .expect("ic-memory eager-init queue poisoned")
145        .push(f);
146}
147
148/// Return true once default runtime bootstrap has completed.
149#[must_use]
150pub fn is_default_memory_manager_bootstrapped() -> bool {
151    BOOTSTRAPPED.load(Ordering::SeqCst)
152}
153
154/// Return the published validated allocations for the default runtime substrate.
155pub fn validated_allocations() -> Result<ValidatedAllocations, RuntimeOpenError> {
156    if !is_default_memory_manager_bootstrapped() {
157        return Err(RuntimeOpenError::NotBootstrapped);
158    }
159    VALIDATED_ALLOCATIONS
160        .lock()
161        .map_err(|_| RuntimeOpenError::RuntimeLockPoisoned)?
162        .clone()
163        .ok_or(RuntimeOpenError::NotBootstrapped)
164}
165
166/// Bootstrap the default `MemoryManager<DefaultMemoryImpl>` runtime using generic policy.
167pub fn bootstrap_default_memory_manager()
168-> Result<ValidatedAllocations, RuntimeBootstrapError<Infallible>> {
169    bootstrap_default_memory_manager_with_policy(&NoopPolicy)
170}
171
172/// Bootstrap the default runtime and layer caller-supplied policy over generic range checks.
173///
174/// Authority order is explicit:
175///
176/// 1. `ic-memory` always owns its governance range.
177/// 2. If any user range is registered, all `MemoryManager` declarations must
178///    belong to the range claimed by their declaring crate.
179/// 3. The caller-supplied [`AllocationPolicy`] then applies framework-specific
180///    namespace and lifecycle rules.
181///
182/// Framework adapters such as Canic should register only the ranges they want
183/// this generic runtime to enforce. If a framework wants its own policy to be
184/// authoritative for application space, it should omit user range registrations
185/// for that space and enforce the rule in its [`AllocationPolicy`].
186pub fn bootstrap_default_memory_manager_with_policy<P: AllocationPolicy>(
187    policy: &P,
188) -> Result<ValidatedAllocations, RuntimeBootstrapError<P::Error>> {
189    if let Ok(validated) = validated_allocations() {
190        return Ok(validated);
191    }
192
193    run_eager_init_hooks();
194
195    let registered_declarations = static_memory_declarations()?;
196    let registered_ranges = static_memory_range_declarations()?;
197    let user_ranges_registered = !registered_ranges.is_empty();
198    let declaration_metadata = declaration_metadata(&registered_declarations);
199    let range_authority = range_authority(registered_ranges)?;
200    let snapshot = declaration_snapshot(registered_declarations)?;
201    seal_static_memory_registry()?;
202    let policy = RuntimeMemoryManagerPolicy {
203        range_authority,
204        user_ranges_registered,
205        declaration_metadata,
206        custom_policy: policy,
207    };
208    let genesis = AllocationLedger::new(
209        crate::CURRENT_LEDGER_SCHEMA_VERSION,
210        crate::CURRENT_PHYSICAL_FORMAT_ID,
211        0,
212        AllocationHistory::default(),
213    )?;
214
215    let validated = with_default_ledger_cell(
216        |cell| -> Result<ValidatedAllocations, RuntimeBootstrapError<P::Error>> {
217            let mut record = cell.get().clone();
218            let mut bootstrap = AllocationBootstrap::new(record.store_mut());
219            let commit = bootstrap
220                .initialize_validate_and_commit(&genesis, snapshot, &policy, None)
221                .map_err(runtime_bootstrap_error_from_bootstrap)?;
222            cell.set(record);
223            Ok(commit.validated)
224        },
225    )?;
226
227    publish_validated_allocations(validated.clone())?;
228    BOOTSTRAPPED.store(true, Ordering::SeqCst);
229    Ok(validated)
230}
231
232/// Open a validated `MemoryManager` memory by stable key and expected ID.
233pub fn open_default_memory_manager_memory(
234    stable_key: &str,
235    id: u8,
236) -> Result<VirtualMemory<DefaultMemoryImpl>, RuntimeOpenError> {
237    let key = StableKey::parse(stable_key)?;
238    let validated = validated_allocations()?;
239    let slot = validated
240        .slot_for(&key)
241        .ok_or_else(|| RuntimeOpenError::StableKeyNotValidated(stable_key.to_string()))?;
242    let validated_id = slot.memory_manager_id()?;
243    if validated_id != id {
244        return Err(RuntimeOpenError::MemoryIdMismatch {
245            stable_key: stable_key.to_string(),
246            validated_id,
247            requested_id: id,
248        });
249    }
250    Ok(default_memory_manager_memory(id))
251}
252
253fn run_eager_init_hooks() {
254    let hooks = {
255        let mut hooks = EAGER_INIT_HOOKS
256            .lock()
257            .expect("ic-memory eager-init queue poisoned");
258        std::mem::take(&mut *hooks)
259    };
260
261    for hook in hooks {
262        hook();
263    }
264}
265
266fn with_default_ledger_cell<P, T>(
267    op: impl FnOnce(&mut DefaultLedgerCell) -> Result<T, RuntimeBootstrapError<P>>,
268) -> Result<T, RuntimeBootstrapError<P>> {
269    DEFAULT_LEDGER_CELL.with(|cell| {
270        let mut cell = cell.borrow_mut();
271        if cell.is_none() {
272            let memory = default_memory_manager_memory(MEMORY_MANAGER_LEDGER_ID);
273            crate::validate_stable_cell_ledger_memory(&memory)?;
274            *cell = Some(Cell::init(memory, StableCellLedgerRecord::default()));
275        }
276        op(cell.as_mut().expect("default ledger cell initialized"))
277    })
278}
279
280fn default_memory_manager_memory(id: u8) -> VirtualMemory<DefaultMemoryImpl> {
281    DEFAULT_MEMORY_MANAGER.with(|manager| manager.get(MemoryId::new(id)))
282}
283
284fn publish_validated_allocations<P>(
285    validated: ValidatedAllocations,
286) -> Result<(), RuntimeBootstrapError<P>> {
287    *VALIDATED_ALLOCATIONS
288        .lock()
289        .map_err(|_| RuntimeBootstrapError::RuntimeLockPoisoned)? = Some(validated);
290    Ok(())
291}
292
293fn declaration_snapshot(
294    registrations: Vec<StaticMemoryDeclaration>,
295) -> Result<DeclarationSnapshot, StaticMemoryDeclarationError> {
296    let mut declarations = Vec::with_capacity(registrations.len() + 1);
297    declarations.push(internal_ledger_declaration()?);
298    declarations.extend(
299        registrations
300            .into_iter()
301            .map(StaticMemoryDeclaration::into_declaration),
302    );
303    DeclarationSnapshot::new(declarations).map_err(StaticMemoryDeclarationError::Declaration)
304}
305
306fn declaration_metadata(registrations: &[StaticMemoryDeclaration]) -> BTreeMap<String, String> {
307    let mut metadata = BTreeMap::new();
308    metadata.insert(
309        IC_MEMORY_LEDGER_STABLE_KEY.to_string(),
310        IC_MEMORY_AUTHORITY_OWNER.to_string(),
311    );
312    for registration in registrations {
313        metadata.insert(
314            registration.declaration().stable_key().as_str().to_string(),
315            registration.declaring_crate().to_string(),
316        );
317    }
318    metadata
319}
320
321fn range_authority(
322    registrations: Vec<StaticMemoryRangeDeclaration>,
323) -> Result<MemoryManagerRangeAuthority, MemoryManagerRangeAuthorityError> {
324    let mut records = Vec::with_capacity(registrations.len() + 1);
325    records.push(internal_ledger_range()?);
326    records.extend(
327        registrations
328            .into_iter()
329            .map(StaticMemoryRangeDeclaration::into_record),
330    );
331    MemoryManagerRangeAuthority::from_records(records)
332}
333
334fn internal_ledger_declaration() -> Result<AllocationDeclaration, crate::DeclarationSnapshotError> {
335    AllocationDeclaration::memory_manager(
336        IC_MEMORY_LEDGER_STABLE_KEY,
337        MEMORY_MANAGER_LEDGER_ID,
338        IC_MEMORY_LEDGER_LABEL,
339    )
340}
341
342fn internal_ledger_range() -> Result<MemoryManagerAuthorityRecord, MemoryManagerRangeAuthorityError>
343{
344    MemoryManagerAuthorityRecord::new(
345        MemoryManagerIdRange::new(
346            MEMORY_MANAGER_LEDGER_ID,
347            crate::MEMORY_MANAGER_GOVERNANCE_MAX_ID,
348        )?,
349        IC_MEMORY_AUTHORITY_OWNER,
350        MemoryManagerRangeMode::Reserved,
351        Some(IC_MEMORY_AUTHORITY_PURPOSE.to_string()),
352    )
353}
354
355fn runtime_bootstrap_error_from_bootstrap<P>(
356    err: crate::BootstrapError<RuntimePolicyError<P>>,
357) -> RuntimeBootstrapError<P> {
358    match err {
359        crate::BootstrapError::Ledger(err) => RuntimeBootstrapError::LedgerCommit(err),
360        crate::BootstrapError::Validation(err) => RuntimeBootstrapError::Validation(err),
361        crate::BootstrapError::Staging(err) => RuntimeBootstrapError::Staging(err),
362    }
363}
364
365struct RuntimeMemoryManagerPolicy<'a, P> {
366    range_authority: MemoryManagerRangeAuthority,
367    user_ranges_registered: bool,
368    declaration_metadata: BTreeMap<String, String>,
369    custom_policy: &'a P,
370}
371
372impl<P: AllocationPolicy> AllocationPolicy for RuntimeMemoryManagerPolicy<'_, P> {
373    type Error = RuntimePolicyError<P::Error>;
374
375    fn validate_key(&self, key: &StableKey) -> Result<(), Self::Error> {
376        let declaring_crate = self.declaring_crate(key)?;
377        if crate::is_ic_memory_stable_key(key.as_str())
378            && declaring_crate != IC_MEMORY_AUTHORITY_OWNER
379        {
380            return Err(RuntimePolicyError::ReservedStableKeyAuthority {
381                stable_key: key.as_str().to_string(),
382                expected_authority: IC_MEMORY_AUTHORITY_OWNER,
383            });
384        }
385        self.custom_policy
386            .validate_key(key)
387            .map_err(RuntimePolicyError::Custom)
388    }
389
390    fn validate_slot(
391        &self,
392        key: &StableKey,
393        slot: &AllocationSlotDescriptor,
394    ) -> Result<(), Self::Error> {
395        self.validate_runtime_range(key, slot)?;
396        self.custom_policy
397            .validate_slot(key, slot)
398            .map_err(RuntimePolicyError::Custom)
399    }
400
401    fn validate_reserved_slot(
402        &self,
403        key: &StableKey,
404        slot: &AllocationSlotDescriptor,
405    ) -> Result<(), Self::Error> {
406        self.validate_runtime_range(key, slot)?;
407        self.custom_policy
408            .validate_reserved_slot(key, slot)
409            .map_err(RuntimePolicyError::Custom)
410    }
411}
412
413impl<P: AllocationPolicy> RuntimeMemoryManagerPolicy<'_, P> {
414    fn declaring_crate(&self, key: &StableKey) -> Result<&str, RuntimePolicyError<P::Error>> {
415        self.declaration_metadata
416            .get(key.as_str())
417            .map(String::as_str)
418            .ok_or_else(|| RuntimePolicyError::MissingDeclarationMetadata(key.as_str().to_string()))
419    }
420
421    fn validate_runtime_range(
422        &self,
423        key: &StableKey,
424        slot: &AllocationSlotDescriptor,
425    ) -> Result<(), RuntimePolicyError<P::Error>> {
426        let declaring_crate = self.declaring_crate(key)?;
427        // Range claims are authoritative generic policy in the default runtime.
428        // Once any user range is registered, every user declaration must fit
429        // the declaring crate's claimed range. With no user ranges, only the
430        // internal ic-memory governance range is enforced here and custom
431        // policy may decide application-space ownership.
432        if declaring_crate == IC_MEMORY_AUTHORITY_OWNER || self.user_ranges_registered {
433            self.range_authority
434                .validate_slot_authority(slot, declaring_crate)?;
435            return Ok(());
436        }
437
438        let id = slot
439            .memory_manager_id()
440            .map_err(MemoryManagerRangeAuthorityError::Slot)?;
441        if self
442            .range_authority
443            .authority_for_id(id)
444            .map_err(RuntimePolicyError::Range)?
445            .is_some()
446        {
447            self.range_authority
448                .validate_slot_authority(slot, declaring_crate)?;
449        }
450        Ok(())
451    }
452}
453
454struct NoopPolicy;
455
456impl AllocationPolicy for NoopPolicy {
457    type Error = Infallible;
458
459    fn validate_key(&self, _key: &StableKey) -> Result<(), Self::Error> {
460        Ok(())
461    }
462
463    fn validate_slot(
464        &self,
465        _key: &StableKey,
466        _slot: &AllocationSlotDescriptor,
467    ) -> Result<(), Self::Error> {
468        Ok(())
469    }
470
471    fn validate_reserved_slot(
472        &self,
473        _key: &StableKey,
474        _slot: &AllocationSlotDescriptor,
475    ) -> Result<(), Self::Error> {
476        Ok(())
477    }
478}
479
480#[cfg(test)]
481pub(crate) fn reset_for_tests() {
482    crate::registry::reset_static_memory_declarations_for_tests();
483    EAGER_INIT_HOOKS
484        .lock()
485        .expect("ic-memory eager-init queue poisoned")
486        .clear();
487    *VALIDATED_ALLOCATIONS
488        .lock()
489        .expect("ic-memory runtime validation state poisoned") = None;
490    BOOTSTRAPPED.store(false, Ordering::SeqCst);
491    DEFAULT_LEDGER_CELL.with_borrow_mut(|cell| {
492        *cell = None;
493    });
494}
495
496#[cfg(test)]
497mod tests {
498    use super::*;
499    use crate::registry::{
500        TEST_REGISTRY_LOCK, register_static_memory_manager_declaration,
501        register_static_memory_manager_range,
502    };
503    use std::sync::atomic::{AtomicBool, Ordering};
504
505    static EAGER_INIT_RAN: AtomicBool = AtomicBool::new(false);
506
507    fn register_crate_a() {
508        register_static_memory_manager_range(
509            100,
510            109,
511            "crate_a",
512            MemoryManagerRangeMode::Reserved,
513            None,
514        )
515        .expect("crate A range");
516        register_static_memory_manager_declaration(100, "crate_a", "users", "crate_a.users.v1")
517            .expect("crate A memory");
518    }
519
520    fn register_crate_b() {
521        register_static_memory_manager_range(
522            110,
523            119,
524            "crate_b",
525            MemoryManagerRangeMode::Reserved,
526            None,
527        )
528        .expect("crate B range");
529        register_static_memory_manager_declaration(110, "crate_b", "orders", "crate_b.orders.v1")
530            .expect("crate B memory");
531    }
532
533    fn mark_eager_init() {
534        EAGER_INIT_RAN.store(true, Ordering::SeqCst);
535        register_static_memory_manager_declaration(101, "crate_a", "audit", "crate_a.audit.v1")
536            .expect("eager-init declaration");
537    }
538
539    #[test]
540    fn multi_crate_declarations_compose_into_one_bootstrap() {
541        let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
542        reset_for_tests();
543        register_crate_a();
544        register_crate_b();
545
546        let validated = bootstrap_default_memory_manager().expect("bootstrap");
547
548        assert_eq!(validated.declarations().len(), 3);
549        assert!(
550            validated
551                .declarations()
552                .iter()
553                .any(|declaration| declaration.stable_key().as_str() == "crate_a.users.v1")
554        );
555        assert!(
556            validated
557                .declarations()
558                .iter()
559                .any(|declaration| declaration.stable_key().as_str() == "crate_b.orders.v1")
560        );
561    }
562
563    #[test]
564    fn conflicting_ranges_fail() {
565        let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
566        reset_for_tests();
567        register_static_memory_manager_range(
568            100,
569            110,
570            "crate_a",
571            MemoryManagerRangeMode::Reserved,
572            None,
573        )
574        .expect("crate A range");
575        register_static_memory_manager_range(
576            105,
577            119,
578            "crate_b",
579            MemoryManagerRangeMode::Reserved,
580            None,
581        )
582        .expect("crate B range");
583
584        let err = bootstrap_default_memory_manager().expect_err("overlap must fail");
585        assert!(matches!(
586            err,
587            RuntimeBootstrapError::Range(
588                MemoryManagerRangeAuthorityError::OverlappingRanges { .. }
589            )
590        ));
591    }
592
593    #[test]
594    fn duplicate_stable_keys_fail() {
595        let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
596        reset_for_tests();
597        register_static_memory_manager_declaration(100, "crate_a", "users", "app.users.v1")
598            .expect("first declaration");
599        register_static_memory_manager_declaration(101, "crate_b", "users", "app.users.v1")
600            .expect("second declaration");
601
602        let err = bootstrap_default_memory_manager().expect_err("duplicate key must fail");
603        assert!(matches!(
604            err,
605            RuntimeBootstrapError::Registry(StaticMemoryDeclarationError::Declaration(
606                crate::DeclarationSnapshotError::DuplicateStableKey(_)
607            ))
608        ));
609    }
610
611    #[test]
612    fn duplicate_memory_manager_ids_fail() {
613        let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
614        reset_for_tests();
615        register_static_memory_manager_declaration(100, "crate_a", "users", "crate_a.users.v1")
616            .expect("first declaration");
617        register_static_memory_manager_declaration(100, "crate_b", "orders", "crate_b.orders.v1")
618            .expect("second declaration");
619
620        let err = bootstrap_default_memory_manager().expect_err("duplicate slot must fail");
621        assert!(matches!(
622            err,
623            RuntimeBootstrapError::Registry(StaticMemoryDeclarationError::Declaration(
624                crate::DeclarationSnapshotError::DuplicateSlot(_)
625            ))
626        ));
627    }
628
629    #[test]
630    fn out_of_range_memory_declaration_fails_when_ranges_are_declared() {
631        let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
632        reset_for_tests();
633        register_static_memory_manager_range(
634            100,
635            109,
636            "crate_a",
637            MemoryManagerRangeMode::Reserved,
638            None,
639        )
640        .expect("crate A range");
641        register_static_memory_manager_declaration(120, "crate_a", "users", "crate_a.users.v1")
642            .expect("out-of-range declaration");
643
644        let err = bootstrap_default_memory_manager().expect_err("out of range must fail");
645        assert!(matches!(
646            err,
647            RuntimeBootstrapError::Validation(crate::AllocationValidationError::Policy(
648                RuntimePolicyError::Range(MemoryManagerRangeAuthorityError::UnclaimedId {
649                    id: 120
650                })
651            ))
652        ));
653    }
654
655    #[test]
656    fn late_registration_after_bootstrap_fails() {
657        let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
658        reset_for_tests();
659        register_static_memory_manager_declaration(100, "crate_a", "users", "crate_a.users.v1")
660            .expect("declaration");
661        bootstrap_default_memory_manager().expect("bootstrap");
662
663        let err = register_static_memory_manager_declaration(
664            101,
665            "crate_a",
666            "orders",
667            "crate_a.orders.v1",
668        )
669        .expect_err("late registration must fail");
670        assert_eq!(err, StaticMemoryDeclarationError::RegistrySealed);
671    }
672
673    #[test]
674    fn late_eager_init_registration_after_bootstrap_fails() {
675        let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
676        reset_for_tests();
677        register_static_memory_manager_declaration(100, "crate_a", "users", "crate_a.users.v1")
678            .expect("declaration");
679        bootstrap_default_memory_manager().expect("bootstrap");
680
681        let err = std::panic::catch_unwind(|| defer_eager_init(mark_eager_init))
682            .expect_err("late eager-init registration must fail");
683
684        let message = err
685            .downcast_ref::<String>()
686            .map(String::as_str)
687            .or_else(|| err.downcast_ref::<&str>().copied())
688            .expect("panic message");
689        assert!(message.contains("after runtime bootstrap"));
690    }
691
692    #[test]
693    fn eager_init_runs_before_snapshot_seal() {
694        let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
695        reset_for_tests();
696        EAGER_INIT_RAN.store(false, Ordering::SeqCst);
697        register_static_memory_manager_range(
698            100,
699            109,
700            "crate_a",
701            MemoryManagerRangeMode::Reserved,
702            None,
703        )
704        .expect("crate A range");
705        defer_eager_init(mark_eager_init);
706
707        let validated = bootstrap_default_memory_manager().expect("bootstrap");
708
709        assert!(EAGER_INIT_RAN.load(Ordering::SeqCst));
710        assert!(
711            validated
712                .declarations()
713                .iter()
714                .any(|declaration| declaration.stable_key().as_str() == "crate_a.audit.v1")
715        );
716    }
717
718    #[test]
719    fn direct_user_can_bootstrap_and_open_without_canic() {
720        let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
721        reset_for_tests();
722        register_static_memory_manager_range(
723            120,
724            129,
725            "icydb",
726            MemoryManagerRangeMode::Reserved,
727            None,
728        )
729        .expect("icydb range");
730        register_static_memory_manager_declaration(120, "icydb", "users", "icydb.users.data.v1")
731            .expect("icydb declaration");
732
733        bootstrap_default_memory_manager().expect("bootstrap");
734        open_default_memory_manager_memory("icydb.users.data.v1", 120).expect("open memory");
735    }
736}