Skip to main content

ic_memory/
runtime.rs

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