Skip to main content

ic_memory/
runtime.rs

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