1use crate::node::{validate_stable_key, validate_stable_key_segment};
2use crate::prelude::*;
3
4#[derive(Clone, Debug, Serialize)]
14pub struct Store {
15 def: Def,
16 canister: &'static str,
17 storage: StoreStorage,
18}
19
20#[derive(Clone, Debug, Serialize)]
31pub enum StoreStorage {
32 Heap(StoreHeapConfig),
34 Journaled(StoreJournaledMemoryConfig),
37}
38
39impl StoreStorage {
40 #[must_use]
42 pub const fn journaled_memory_config(&self) -> Option<&StoreJournaledMemoryConfig> {
43 match self {
44 Self::Journaled(config) => Some(config),
45 Self::Heap(_) => None,
46 }
47 }
48
49 #[must_use]
51 pub const fn storage_capabilities(&self) -> StoreStorageCapabilities {
52 match self {
53 Self::Heap(_) => StoreStorageCapabilities::heap(),
54 Self::Journaled(_) => StoreStorageCapabilities::journaled(),
55 }
56 }
57}
58
59#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
63pub enum StoreStorageMode {
64 Heap,
66 Journaled,
68}
69
70#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
72pub enum AllocationIdentityCapability {
73 Present,
75 Absent,
77}
78
79#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
81pub enum StoreDurability {
82 Durable,
84 Volatile,
86}
87
88#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
90pub enum StoreRecoveryCapability {
91 StableBasePlusJournalReplay,
94 None,
96}
97
98#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
100pub enum CommitParticipation {
101 Durable,
103 LiveOnly,
105}
106
107#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
109pub enum SchemaMetadataCapability {
110 LiveRebuiltMetadata,
113 CanonicalStableHistoryPlusJournalTail,
115}
116
117#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
119pub enum RelationSourceCapability {
120 DurableSource,
122 LiveSource,
124}
125
126#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
128pub enum RelationTargetCapability {
129 DurableTarget,
131 VolatileTarget,
133}
134
135#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
137pub enum LiveValidationCapability {
138 Supported,
140}
141
142#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
148pub struct StoreStorageCapabilities {
149 storage_mode: StoreStorageMode,
150 allocation_identity: AllocationIdentityCapability,
151 durability: StoreDurability,
152 recovery: StoreRecoveryCapability,
153 commit_participation: CommitParticipation,
154 schema_metadata: SchemaMetadataCapability,
155 relation_source: RelationSourceCapability,
156 relation_target: RelationTargetCapability,
157 live_validation: LiveValidationCapability,
158}
159
160impl StoreStorageCapabilities {
161 #[must_use]
163 pub const fn heap() -> Self {
164 Self {
165 storage_mode: StoreStorageMode::Heap,
166 allocation_identity: AllocationIdentityCapability::Absent,
167 durability: StoreDurability::Volatile,
168 recovery: StoreRecoveryCapability::None,
169 commit_participation: CommitParticipation::LiveOnly,
170 schema_metadata: SchemaMetadataCapability::LiveRebuiltMetadata,
171 relation_source: RelationSourceCapability::LiveSource,
172 relation_target: RelationTargetCapability::VolatileTarget,
173 live_validation: LiveValidationCapability::Supported,
174 }
175 }
176
177 #[must_use]
179 pub const fn journaled() -> Self {
180 Self {
181 storage_mode: StoreStorageMode::Journaled,
182 allocation_identity: AllocationIdentityCapability::Present,
183 durability: StoreDurability::Durable,
184 recovery: StoreRecoveryCapability::StableBasePlusJournalReplay,
185 commit_participation: CommitParticipation::Durable,
186 schema_metadata: SchemaMetadataCapability::CanonicalStableHistoryPlusJournalTail,
187 relation_source: RelationSourceCapability::DurableSource,
188 relation_target: RelationTargetCapability::DurableTarget,
189 live_validation: LiveValidationCapability::Supported,
190 }
191 }
192
193 #[must_use]
195 pub const fn storage_mode(self) -> StoreStorageMode {
196 self.storage_mode
197 }
198
199 #[must_use]
201 pub const fn allocation_identity(self) -> AllocationIdentityCapability {
202 self.allocation_identity
203 }
204
205 #[must_use]
207 pub const fn durability(self) -> StoreDurability {
208 self.durability
209 }
210
211 #[must_use]
213 pub const fn recovery(self) -> StoreRecoveryCapability {
214 self.recovery
215 }
216
217 #[must_use]
219 pub const fn commit_participation(self) -> CommitParticipation {
220 self.commit_participation
221 }
222
223 #[must_use]
225 pub const fn schema_metadata(self) -> SchemaMetadataCapability {
226 self.schema_metadata
227 }
228
229 #[must_use]
231 pub const fn relation_source(self) -> RelationSourceCapability {
232 self.relation_source
233 }
234
235 #[must_use]
237 pub const fn relation_target(self) -> RelationTargetCapability {
238 self.relation_target
239 }
240
241 #[must_use]
243 pub const fn live_validation(self) -> LiveValidationCapability {
244 self.live_validation
245 }
246
247 #[must_use]
249 pub const fn has_allocation_identity(self) -> bool {
250 matches!(
251 self.allocation_identity,
252 AllocationIdentityCapability::Present
253 )
254 }
255
256 #[must_use]
258 pub const fn participates_in_durable_commit(self) -> bool {
259 matches!(self.commit_participation, CommitParticipation::Durable)
260 }
261
262 #[must_use]
264 pub const fn is_volatile(self) -> bool {
265 matches!(self.durability, StoreDurability::Volatile)
266 }
267}
268
269#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize)]
271pub struct StoreHeapConfig;
272
273impl StoreHeapConfig {
274 #[must_use]
276 pub const fn new() -> Self {
277 Self
278 }
279}
280
281#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
284pub struct StoreJournaledMemoryConfig {
285 key: &'static str,
286}
287
288impl StoreJournaledMemoryConfig {
289 #[must_use]
291 pub const fn new(key: &'static str) -> Self {
292 Self { key }
293 }
294
295 #[must_use]
297 pub const fn key(self) -> &'static str {
298 self.key
299 }
300}
301
302impl Store {
303 #[must_use]
305 pub const fn new_heap(def: Def, canister: &'static str, heap: StoreHeapConfig) -> Self {
306 Self {
307 def,
308 canister,
309 storage: StoreStorage::Heap(heap),
310 }
311 }
312
313 #[must_use]
315 pub const fn new_journaled(
316 def: Def,
317 canister: &'static str,
318 journaled: StoreJournaledMemoryConfig,
319 ) -> Self {
320 Self {
321 def,
322 canister,
323 storage: StoreStorage::Journaled(journaled),
324 }
325 }
326
327 #[must_use]
328 pub const fn def(&self) -> &Def {
329 &self.def
330 }
331
332 #[must_use]
333 pub const fn canister(&self) -> &'static str {
334 self.canister
335 }
336
337 #[must_use]
339 pub const fn storage(&self) -> &StoreStorage {
340 &self.storage
341 }
342
343 #[must_use]
345 pub const fn is_heap_storage(&self) -> bool {
346 matches!(self.storage, StoreStorage::Heap(_))
347 }
348
349 #[must_use]
351 pub const fn is_journaled_storage(&self) -> bool {
352 matches!(self.storage, StoreStorage::Journaled(_))
353 }
354
355 #[must_use]
358 pub const fn journaled_memory_config(&self) -> Option<&StoreJournaledMemoryConfig> {
359 self.storage.journaled_memory_config()
360 }
361
362 #[must_use]
364 pub const fn storage_capabilities(&self) -> StoreStorageCapabilities {
365 self.storage.storage_capabilities()
366 }
367
368 #[must_use]
374 pub const fn stable_store_key(&self) -> &'static str {
375 match self.storage {
376 StoreStorage::Journaled(config) => config.key(),
377 StoreStorage::Heap(_) => panic!("heap stores do not have a stable store key"),
378 }
379 }
380
381 #[must_use]
382 pub fn stable_data_allocation(&self, memory_namespace: &str) -> StableMemoryAllocation {
383 self.stable_allocation(memory_namespace, StoreMemoryRole::Data)
384 }
385
386 #[must_use]
389 pub fn stable_data_allocation_with_schema_metadata(
390 &self,
391 memory_namespace: &str,
392 schema_metadata: StableMemoryAllocationMetadata,
393 ) -> StableMemoryAllocation {
394 self.stable_allocation_with_schema_metadata(
395 memory_namespace,
396 StoreMemoryRole::Data,
397 schema_metadata,
398 )
399 }
400
401 #[must_use]
402 pub fn stable_index_allocation(&self, memory_namespace: &str) -> StableMemoryAllocation {
403 self.stable_allocation(memory_namespace, StoreMemoryRole::Index)
404 }
405
406 #[must_use]
409 pub fn stable_index_allocation_with_schema_metadata(
410 &self,
411 memory_namespace: &str,
412 schema_metadata: StableMemoryAllocationMetadata,
413 ) -> StableMemoryAllocation {
414 self.stable_allocation_with_schema_metadata(
415 memory_namespace,
416 StoreMemoryRole::Index,
417 schema_metadata,
418 )
419 }
420
421 #[must_use]
422 pub fn stable_schema_allocation(&self, memory_namespace: &str) -> StableMemoryAllocation {
423 self.stable_allocation(memory_namespace, StoreMemoryRole::Schema)
424 }
425
426 #[must_use]
428 pub fn journal_allocation(&self, memory_namespace: &str) -> StableMemoryAllocation {
429 StableMemoryAllocation::without_schema_metadata(stable_store_memory_key(
430 memory_namespace,
431 self.stable_store_key(),
432 "journal",
433 ))
434 }
435
436 #[must_use]
439 pub fn stable_schema_allocation_with_schema_metadata(
440 &self,
441 memory_namespace: &str,
442 schema_metadata: StableMemoryAllocationMetadata,
443 ) -> StableMemoryAllocation {
444 self.stable_allocation_with_schema_metadata(
445 memory_namespace,
446 StoreMemoryRole::Schema,
447 schema_metadata,
448 )
449 }
450
451 #[must_use]
452 pub fn stable_allocation(
453 &self,
454 memory_namespace: &str,
455 role: StoreMemoryRole,
456 ) -> StableMemoryAllocation {
457 StableMemoryAllocation::without_schema_metadata(stable_store_memory_key(
458 memory_namespace,
459 self.stable_store_key(),
460 role.as_str(),
461 ))
462 }
463
464 fn stable_allocation_with_schema_metadata(
465 &self,
466 memory_namespace: &str,
467 role: StoreMemoryRole,
468 schema_metadata: StableMemoryAllocationMetadata,
469 ) -> StableMemoryAllocation {
470 StableMemoryAllocation::with_schema_metadata(
471 stable_store_memory_key(memory_namespace, self.stable_store_key(), role.as_str()),
472 schema_metadata,
473 )
474 }
475}
476
477#[derive(Clone, Copy, Debug, Eq, PartialEq)]
478pub enum StoreMemoryRole {
479 Data,
480 Index,
481 Schema,
482}
483
484impl StoreMemoryRole {
485 #[must_use]
486 pub const fn as_str(self) -> &'static str {
487 match self {
488 Self::Data => "data",
489 Self::Index => "index",
490 Self::Schema => "schema",
491 }
492 }
493}
494
495#[derive(Clone, Debug, Eq, PartialEq)]
500pub struct StableMemoryAllocationMetadata {
501 version: Option<u32>,
502 fingerprint_method_version: Option<u8>,
503 fingerprint: Option<String>,
504}
505
506impl StableMemoryAllocationMetadata {
507 const fn new(
508 schema_version: Option<u32>,
509 schema_fingerprint_method_version: Option<u8>,
510 schema_fingerprint: Option<String>,
511 ) -> Self {
512 Self {
513 version: schema_version,
514 fingerprint_method_version: schema_fingerprint_method_version,
515 fingerprint: schema_fingerprint,
516 }
517 }
518
519 #[must_use]
521 pub const fn from_accepted_schema_contract(
522 schema_version: u32,
523 schema_fingerprint_method_version: u8,
524 schema_fingerprint: String,
525 ) -> Self {
526 Self::new(
527 Some(schema_version),
528 Some(schema_fingerprint_method_version),
529 Some(schema_fingerprint),
530 )
531 }
532
533 #[must_use]
536 pub const fn absent() -> Self {
537 Self::new(None, None, None)
538 }
539
540 #[must_use]
542 pub const fn schema_version(&self) -> Option<u32> {
543 self.version
544 }
545
546 #[must_use]
548 pub const fn schema_fingerprint_method_version(&self) -> Option<u8> {
549 self.fingerprint_method_version
550 }
551
552 #[must_use]
554 pub const fn schema_fingerprint(&self) -> Option<&str> {
555 match &self.fingerprint {
556 Some(value) => Some(value.as_str()),
557 None => None,
558 }
559 }
560}
561
562#[derive(Clone, Debug, Eq, PartialEq)]
569pub struct StableMemoryAllocation {
570 stable_key: String,
571 schema_metadata: StableMemoryAllocationMetadata,
572}
573
574impl StableMemoryAllocation {
575 #[must_use]
577 pub const fn without_schema_metadata(stable_key: String) -> Self {
578 Self::with_schema_metadata(stable_key, StableMemoryAllocationMetadata::absent())
579 }
580
581 #[must_use]
586 pub const fn with_schema_metadata(
587 stable_key: String,
588 schema_metadata: StableMemoryAllocationMetadata,
589 ) -> Self {
590 Self {
591 stable_key,
592 schema_metadata,
593 }
594 }
595
596 #[must_use]
598 pub const fn stable_key(&self) -> &str {
599 self.stable_key.as_str()
600 }
601
602 #[must_use]
604 pub const fn schema_metadata(&self) -> &StableMemoryAllocationMetadata {
605 &self.schema_metadata
606 }
607
608 #[must_use]
610 pub const fn schema_version(&self) -> Option<u32> {
611 self.schema_metadata.schema_version()
612 }
613
614 #[must_use]
616 pub const fn schema_fingerprint_method_version(&self) -> Option<u8> {
617 self.schema_metadata.schema_fingerprint_method_version()
618 }
619
620 #[must_use]
622 pub const fn schema_fingerprint(&self) -> Option<&str> {
623 self.schema_metadata.schema_fingerprint()
624 }
625
626 #[must_use]
631 pub fn same_identity_as(&self, other: &Self) -> bool {
632 self.stable_key == other.stable_key
633 }
634}
635
636#[must_use]
637pub fn stable_memory_key(memory_namespace: &str, allocation: &str, role: &str) -> String {
638 format!("icydb.{memory_namespace}.{allocation}.{role}.v1")
639}
640
641#[must_use]
642fn stable_store_memory_key(memory_namespace: &str, store_key: &str, role: &str) -> String {
643 format!("icydb.{memory_namespace}.store.{store_key}.{role}.v1")
645}
646
647impl MacroNode for Store {
648 fn as_any(&self) -> &dyn std::any::Any {
649 self
650 }
651}
652
653impl ValidateNode for Store {
654 fn validate(&self) -> Result<(), ErrorTree> {
655 let mut errs = ErrorTree::new();
656
657 {
658 let schema = schema_read();
659
660 match schema.cast_node::<Canister>(self.canister()) {
661 Ok(canister) => match self.storage() {
662 StoreStorage::Heap(_) => {}
663 StoreStorage::Journaled(config) => {
664 validate_journaled_memory_config(&mut errs, self, *config, canister);
665 }
666 },
667 Err(e) => errs.add(e),
668 }
669 }
670
671 errs.result()
672 }
673}
674
675fn validate_journaled_memory_config(
676 errs: &mut ErrorTree,
677 store: &Store,
678 config: StoreJournaledMemoryConfig,
679 canister: &Canister,
680) {
681 validate_stable_key_segment(errs, "store key", config.key());
682 for allocation in [
683 store.stable_data_allocation(canister.memory_namespace()),
684 store.stable_index_allocation(canister.memory_namespace()),
685 store.stable_schema_allocation(canister.memory_namespace()),
686 store.journal_allocation(canister.memory_namespace()),
687 ] {
688 validate_stable_key(errs, "store allocation key", allocation.stable_key());
689 }
690}
691
692impl VisitableNode for Store {
693 fn route_key(&self) -> String {
694 self.def().path()
695 }
696
697 fn drive<V: Visitor>(&self, v: &mut V) {
698 self.def().accept(v);
699 }
700}
701
702#[cfg(test)]
703mod tests {
704 use crate::{
705 build::schema_write,
706 node::{Canister, SchemaNode},
707 };
708
709 use super::*;
710
711 fn insert_canister(path_module: &'static str, ident: &'static str) {
712 schema_write().insert_node(SchemaNode::Canister(Canister::new(
713 Def::new(path_module, ident),
714 "test_db",
715 None,
716 )));
717 }
718
719 #[test]
720 fn store_allocations_default_to_absent_schema_metadata() {
721 let store = Store::new_journaled(
722 Def::new("demo::rpg", "CharacterStore"),
723 "demo::rpg::Canister",
724 StoreJournaledMemoryConfig::new("store_110"),
725 );
726
727 for allocation in [
728 store.stable_data_allocation("demo_rpg"),
729 store.stable_index_allocation("demo_rpg"),
730 store.stable_schema_allocation("demo_rpg"),
731 store.journal_allocation("demo_rpg"),
732 ] {
733 assert_eq!(allocation.schema_version(), None);
734 assert_eq!(allocation.schema_fingerprint_method_version(), None);
735 assert_eq!(allocation.schema_fingerprint(), None);
736 assert_eq!(
737 allocation.schema_metadata(),
738 &StableMemoryAllocationMetadata::absent()
739 );
740 }
741 }
742
743 #[test]
744 fn allocation_metadata_is_role_specific_and_diagnostic_only() {
745 let store = Store::new_journaled(
746 Def::new("demo::rpg", "CharacterStore"),
747 "demo::rpg::Canister",
748 StoreJournaledMemoryConfig::new("store_110"),
749 );
750 let data = store.stable_data_allocation_with_schema_metadata(
751 "demo_rpg",
752 StableMemoryAllocationMetadata::from_accepted_schema_contract(
753 7,
754 1,
755 "data-row-layout".to_string(),
756 ),
757 );
758 let index = store.stable_index_allocation_with_schema_metadata(
759 "demo_rpg",
760 StableMemoryAllocationMetadata::from_accepted_schema_contract(
761 8,
762 1,
763 "index-catalog".to_string(),
764 ),
765 );
766 let schema = store.stable_schema_allocation_with_schema_metadata(
767 "demo_rpg",
768 StableMemoryAllocationMetadata::from_accepted_schema_contract(
769 10,
770 1,
771 "schema-catalog".to_string(),
772 ),
773 );
774 let data_after_reconcile = store.stable_data_allocation_with_schema_metadata(
775 "demo_rpg",
776 StableMemoryAllocationMetadata::from_accepted_schema_contract(
777 9,
778 1,
779 "data-row-layout-changed".to_string(),
780 ),
781 );
782
783 assert_eq!(data.schema_version(), Some(7));
784 assert_eq!(data.schema_fingerprint_method_version(), Some(1));
785 assert_eq!(data.schema_fingerprint(), Some("data-row-layout"));
786 assert_eq!(index.schema_version(), Some(8));
787 assert_eq!(index.schema_fingerprint_method_version(), Some(1));
788 assert_eq!(index.schema_fingerprint(), Some("index-catalog"));
789 assert_eq!(schema.schema_version(), Some(10));
790 assert_eq!(schema.schema_fingerprint_method_version(), Some(1));
791 assert_eq!(schema.schema_fingerprint(), Some("schema-catalog"));
792 assert!(data.same_identity_as(&data_after_reconcile));
793 assert!(!data.same_identity_as(&index));
794 assert!(!data.same_identity_as(&schema));
795 }
796
797 #[test]
798 fn store_owns_explicit_heap_storage_config() {
799 insert_canister("store_heap_config", "Canister");
800 let store = Store::new_heap(
801 Def::new("store_heap_config", "Store"),
802 "store_heap_config::Canister",
803 StoreHeapConfig::new(),
804 );
805
806 assert!(store.is_heap_storage());
807 assert!(store.validate().is_ok());
808 }
809
810 #[test]
811 fn heap_store_storage_capabilities_describe_volatile_contract() {
812 let store = Store::new_heap(
813 Def::new("store_heap_capabilities", "Store"),
814 "store_heap_capabilities::Canister",
815 StoreHeapConfig::new(),
816 );
817 let capabilities = store.storage_capabilities();
818
819 assert_eq!(capabilities.storage_mode(), StoreStorageMode::Heap);
820 assert_eq!(
821 capabilities.allocation_identity(),
822 AllocationIdentityCapability::Absent,
823 );
824 assert_eq!(capabilities.durability(), StoreDurability::Volatile);
825 assert_eq!(capabilities.recovery(), StoreRecoveryCapability::None);
826 assert_eq!(
827 capabilities.commit_participation(),
828 CommitParticipation::LiveOnly,
829 );
830 assert_eq!(
831 capabilities.schema_metadata(),
832 SchemaMetadataCapability::LiveRebuiltMetadata,
833 );
834 assert_eq!(
835 capabilities.relation_source(),
836 RelationSourceCapability::LiveSource,
837 );
838 assert_eq!(
839 capabilities.relation_target(),
840 RelationTargetCapability::VolatileTarget,
841 );
842 assert_eq!(
843 capabilities.live_validation(),
844 LiveValidationCapability::Supported,
845 );
846 assert!(!capabilities.has_allocation_identity());
847 assert!(!capabilities.participates_in_durable_commit());
848 assert!(capabilities.is_volatile());
849 }
850
851 #[test]
852 fn store_owns_explicit_journaled_storage_config() {
853 insert_canister("store_journaled_config", "Canister");
854 let store = Store::new_journaled(
855 Def::new("store_journaled_config", "Store"),
856 "store_journaled_config::Canister",
857 StoreJournaledMemoryConfig::new("store_110"),
858 );
859
860 assert!(store.is_journaled_storage());
861 assert!(!store.is_heap_storage());
862 let journaled = store
863 .journaled_memory_config()
864 .expect("journaled model stores four-role config explicitly");
865 assert_eq!(journaled.key(), "store_110");
866 assert_eq!(store.stable_store_key(), "store_110");
867 assert!(store.validate().is_ok());
868 }
869
870 #[test]
871 fn store_keys_validate_segments_and_complete_role_key_lengths() {
872 let canister = Canister::new(Def::new("bounded_key", "Canister"), "app", None);
873 let admitted = "a".repeat(101);
875 let oversized = "a".repeat(102);
876 for (key, accepted) in [
877 ("transfers", true),
878 ("", false),
879 ("Transfers", false),
880 ("1store", false),
881 ("store.name", false),
882 (admitted.as_str(), true),
883 (oversized.as_str(), false),
884 ] {
885 let config =
886 StoreJournaledMemoryConfig::new(Box::leak(key.to_string().into_boxed_str()));
887 let store = Store::new_journaled(
888 Def::new("bounded_key", "Store"),
889 "bounded_key::Canister",
890 config,
891 );
892 let mut errors = ErrorTree::new();
893 validate_journaled_memory_config(&mut errors, &store, config, &canister);
894 assert_eq!(errors.result().is_ok(), accepted);
895 }
896 }
897
898 #[test]
899 fn journaled_store_storage_capabilities_describe_cached_stable_contract() {
900 let store = Store::new_journaled(
901 Def::new("store_journaled_capabilities", "Store"),
902 "store_journaled_capabilities::Canister",
903 StoreJournaledMemoryConfig::new("store_110"),
904 );
905 let capabilities = store.storage_capabilities();
906
907 assert_eq!(capabilities.storage_mode(), StoreStorageMode::Journaled);
908 assert_eq!(
909 capabilities.allocation_identity(),
910 AllocationIdentityCapability::Present,
911 );
912 assert_eq!(capabilities.durability(), StoreDurability::Durable);
913 assert_eq!(
914 capabilities.recovery(),
915 StoreRecoveryCapability::StableBasePlusJournalReplay,
916 );
917 assert_eq!(
918 capabilities.commit_participation(),
919 CommitParticipation::Durable,
920 );
921 assert_eq!(
922 capabilities.schema_metadata(),
923 SchemaMetadataCapability::CanonicalStableHistoryPlusJournalTail,
924 );
925 assert_eq!(
926 capabilities.relation_source(),
927 RelationSourceCapability::DurableSource,
928 );
929 assert_eq!(
930 capabilities.relation_target(),
931 RelationTargetCapability::DurableTarget,
932 );
933 assert_eq!(
934 capabilities.live_validation(),
935 LiveValidationCapability::Supported,
936 );
937 assert!(capabilities.has_allocation_identity());
938 assert!(capabilities.participates_in_durable_commit());
939 assert!(!capabilities.is_volatile());
940 }
941
942 #[test]
943 fn journaled_store_allocations_use_role_named_stable_keys() {
944 let store = Store::new_journaled(
945 Def::new("demo::rpg", "CharacterStore"),
946 "demo::rpg::Canister",
947 StoreJournaledMemoryConfig::new("store_110"),
948 );
949
950 assert_eq!(
951 store.stable_data_allocation("demo_rpg").stable_key(),
952 "icydb.demo_rpg.store.store_110.data.v1",
953 );
954 assert_eq!(
955 store.stable_index_allocation("demo_rpg").stable_key(),
956 "icydb.demo_rpg.store.store_110.index.v1",
957 );
958 assert_eq!(
959 store.stable_schema_allocation("demo_rpg").stable_key(),
960 "icydb.demo_rpg.store.store_110.schema.v1",
961 );
962 assert_eq!(
963 store.journal_allocation("demo_rpg").stable_key(),
964 "icydb.demo_rpg.store.store_110.journal.v1",
965 );
966
967 let renamed = Store::new_journaled(
969 Def::new("renamed::module", "RenamedStore"),
970 "renamed::module::RenamedCanister",
971 StoreJournaledMemoryConfig::new("store_110"),
972 );
973 for (before, after) in [
974 (
975 store.stable_data_allocation("demo_rpg"),
976 renamed.stable_data_allocation("demo_rpg"),
977 ),
978 (
979 store.stable_index_allocation("demo_rpg"),
980 renamed.stable_index_allocation("demo_rpg"),
981 ),
982 (
983 store.stable_schema_allocation("demo_rpg"),
984 renamed.stable_schema_allocation("demo_rpg"),
985 ),
986 (
987 store.journal_allocation("demo_rpg"),
988 renamed.journal_allocation("demo_rpg"),
989 ),
990 ] {
991 assert_eq!(before.stable_key(), after.stable_key());
992 }
993 }
994
995 #[test]
996 fn storage_capabilities_are_not_allocation_identity() {
997 let store_a = Store::new_journaled(
998 Def::new("demo::rpg", "CharacterStore"),
999 "demo::rpg::Canister",
1000 StoreJournaledMemoryConfig::new("store_110"),
1001 );
1002 let store_b = Store::new_journaled(
1003 Def::new("demo::rpg", "InventoryStore"),
1004 "demo::rpg::Canister",
1005 StoreJournaledMemoryConfig::new("store_120"),
1006 );
1007
1008 assert_eq!(
1009 store_a.storage_capabilities(),
1010 store_b.storage_capabilities()
1011 );
1012 assert_ne!(
1013 store_a.stable_data_allocation("demo_rpg"),
1014 store_b.stable_data_allocation("demo_rpg"),
1015 "stable allocation identity must remain separate from capabilities",
1016 );
1017 }
1018
1019 #[test]
1020 fn capability_consumers_use_axes_not_storage_mode() {
1021 const fn commit_label(capabilities: StoreStorageCapabilities) -> &'static str {
1022 match capabilities.commit_participation() {
1023 CommitParticipation::Durable => "durable",
1024 CommitParticipation::LiveOnly => "live-only",
1025 }
1026 }
1027
1028 let future_durable_heap_mode = StoreStorageCapabilities {
1029 storage_mode: StoreStorageMode::Heap,
1030 allocation_identity: AllocationIdentityCapability::Present,
1031 durability: StoreDurability::Durable,
1032 recovery: StoreRecoveryCapability::StableBasePlusJournalReplay,
1033 commit_participation: CommitParticipation::Durable,
1034 schema_metadata: SchemaMetadataCapability::CanonicalStableHistoryPlusJournalTail,
1035 relation_source: RelationSourceCapability::DurableSource,
1036 relation_target: RelationTargetCapability::DurableTarget,
1037 live_validation: LiveValidationCapability::Supported,
1038 };
1039
1040 assert_eq!(commit_label(future_durable_heap_mode), "durable");
1041 assert!(future_durable_heap_mode.participates_in_durable_commit());
1042 assert_eq!(
1043 future_durable_heap_mode.storage_mode(),
1044 StoreStorageMode::Heap,
1045 "the diagnostic storage mode must not drive commit policy",
1046 );
1047 }
1048}