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#[derive(Debug, thiserror::Error)]
49pub enum RuntimeBootstrapError<P> {
50 #[error(transparent)]
52 Registry(#[from] StaticMemoryDeclarationError),
53 #[error(transparent)]
55 Range(#[from] MemoryManagerRangeAuthorityError),
56 #[error(transparent)]
58 LedgerIntegrity(#[from] crate::LedgerIntegrityError),
59 #[error(transparent)]
61 LedgerCommit(#[from] crate::LedgerCommitError),
62 #[error(transparent)]
64 StableCellLedger(#[from] StableCellLedgerError),
65 #[error(transparent)]
67 Validation(#[from] crate::AllocationValidationError<RuntimePolicyError<P>>),
68 #[error(transparent)]
70 Staging(#[from] crate::AllocationStageError),
71 #[error("ic-memory runtime lock poisoned")]
73 RuntimeLockPoisoned,
74}
75
76#[derive(Clone, Debug, Eq, thiserror::Error, PartialEq)]
81pub enum RuntimeOpenError {
82 #[error("ic-memory runtime has not completed bootstrap validation")]
84 NotBootstrapped,
85 #[error("ic-memory runtime lock poisoned")]
87 RuntimeLockPoisoned,
88 #[error(transparent)]
90 StableKey(#[from] crate::StableKeyError),
91 #[error("stable key '{0}' was not validated by ic-memory runtime bootstrap")]
93 StableKeyNotValidated(String),
94 #[error(transparent)]
96 MemoryManagerSlot(#[from] MemoryManagerSlotError),
97 #[error(
99 "stable key '{stable_key}' is validated for MemoryManager ID {validated_id}, not requested ID {requested_id}"
100 )]
101 MemoryIdMismatch {
102 stable_key: String,
104 validated_id: u8,
106 requested_id: u8,
108 },
109}
110
111#[derive(Clone, Debug, Eq, thiserror::Error, PartialEq)]
116pub enum RuntimePolicyError<P> {
117 #[error(transparent)]
119 Range(#[from] MemoryManagerRangeAuthorityError),
120 #[error("runtime declaration metadata is missing for stable key '{0}'")]
122 MissingDeclarationMetadata(String),
123 #[error("stable key '{stable_key}' is reserved to authority '{expected_authority}'")]
125 ReservedStableKeyAuthority {
126 stable_key: String,
128 expected_authority: &'static str,
130 },
131 #[error(transparent)]
133 Custom(P),
134}
135
136pub 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#[must_use]
150pub fn is_default_memory_manager_bootstrapped() -> bool {
151 BOOTSTRAPPED.load(Ordering::SeqCst)
152}
153
154pub 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
166pub fn bootstrap_default_memory_manager()
168-> Result<ValidatedAllocations, RuntimeBootstrapError<Infallible>> {
169 bootstrap_default_memory_manager_with_policy(&NoopPolicy)
170}
171
172pub 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(®istered_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
232pub 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 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}