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)]
30pub enum StoreStorage {
31 Heap(StoreHeapConfig),
33 Journaled(StoreJournaledMemoryConfig),
36}
37
38impl StoreStorage {
39 #[must_use]
41 pub const fn journaled_memory_config(&self) -> Option<&StoreJournaledMemoryConfig> {
42 match self {
43 Self::Journaled(config) => Some(config),
44 Self::Heap(_) => None,
45 }
46 }
47
48 #[must_use]
50 pub const fn storage_capabilities(&self) -> StoreStorageCapabilities {
51 match self {
52 Self::Heap(_) => StoreStorageCapabilities::heap(),
53 Self::Journaled(_) => StoreStorageCapabilities::journaled(),
54 }
55 }
56}
57
58#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
62pub enum StoreStorageMode {
63 Heap,
65 Journaled,
67}
68
69#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
71pub enum AllocationIdentityCapability {
72 Present,
74 Absent,
76}
77
78#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
80pub enum StoreDurability {
81 Durable,
83 Volatile,
85}
86
87#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
89pub enum StoreRecoveryCapability {
90 StableBasePlusJournalReplay,
93 None,
95}
96
97#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
99pub enum CommitParticipation {
100 Durable,
102 LiveOnly,
104}
105
106#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
108pub enum SchemaMetadataCapability {
109 LiveRebuiltMetadata,
112 CanonicalStableHistoryPlusJournalTail,
114}
115
116#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
118pub enum RelationSourceCapability {
119 DurableSource,
121 LiveSource,
123}
124
125#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
127pub enum RelationTargetCapability {
128 DurableTarget,
130 VolatileTarget,
132}
133
134#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
136pub enum LiveValidationCapability {
137 Supported,
139}
140
141#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
147pub struct StoreStorageCapabilities {
148 storage_mode: StoreStorageMode,
149 allocation_identity: AllocationIdentityCapability,
150 durability: StoreDurability,
151 recovery: StoreRecoveryCapability,
152 commit_participation: CommitParticipation,
153 schema_metadata: SchemaMetadataCapability,
154 relation_source: RelationSourceCapability,
155 relation_target: RelationTargetCapability,
156 live_validation: LiveValidationCapability,
157}
158
159impl StoreStorageCapabilities {
160 #[must_use]
162 pub const fn heap() -> Self {
163 Self {
164 storage_mode: StoreStorageMode::Heap,
165 allocation_identity: AllocationIdentityCapability::Absent,
166 durability: StoreDurability::Volatile,
167 recovery: StoreRecoveryCapability::None,
168 commit_participation: CommitParticipation::LiveOnly,
169 schema_metadata: SchemaMetadataCapability::LiveRebuiltMetadata,
170 relation_source: RelationSourceCapability::LiveSource,
171 relation_target: RelationTargetCapability::VolatileTarget,
172 live_validation: LiveValidationCapability::Supported,
173 }
174 }
175
176 #[must_use]
178 pub const fn journaled() -> Self {
179 Self {
180 storage_mode: StoreStorageMode::Journaled,
181 allocation_identity: AllocationIdentityCapability::Present,
182 durability: StoreDurability::Durable,
183 recovery: StoreRecoveryCapability::StableBasePlusJournalReplay,
184 commit_participation: CommitParticipation::Durable,
185 schema_metadata: SchemaMetadataCapability::CanonicalStableHistoryPlusJournalTail,
186 relation_source: RelationSourceCapability::DurableSource,
187 relation_target: RelationTargetCapability::DurableTarget,
188 live_validation: LiveValidationCapability::Supported,
189 }
190 }
191
192 #[must_use]
194 pub const fn storage_mode(self) -> StoreStorageMode {
195 self.storage_mode
196 }
197
198 #[must_use]
200 pub const fn allocation_identity(self) -> AllocationIdentityCapability {
201 self.allocation_identity
202 }
203
204 #[must_use]
206 pub const fn durability(self) -> StoreDurability {
207 self.durability
208 }
209
210 #[must_use]
212 pub const fn recovery(self) -> StoreRecoveryCapability {
213 self.recovery
214 }
215
216 #[must_use]
218 pub const fn commit_participation(self) -> CommitParticipation {
219 self.commit_participation
220 }
221
222 #[must_use]
224 pub const fn schema_metadata(self) -> SchemaMetadataCapability {
225 self.schema_metadata
226 }
227
228 #[must_use]
230 pub const fn relation_source(self) -> RelationSourceCapability {
231 self.relation_source
232 }
233
234 #[must_use]
236 pub const fn relation_target(self) -> RelationTargetCapability {
237 self.relation_target
238 }
239
240 #[must_use]
242 pub const fn live_validation(self) -> LiveValidationCapability {
243 self.live_validation
244 }
245
246 #[must_use]
248 pub const fn has_allocation_identity(self) -> bool {
249 matches!(
250 self.allocation_identity,
251 AllocationIdentityCapability::Present
252 )
253 }
254
255 #[must_use]
257 pub const fn participates_in_durable_commit(self) -> bool {
258 matches!(self.commit_participation, CommitParticipation::Durable)
259 }
260
261 #[must_use]
263 pub const fn is_volatile(self) -> bool {
264 matches!(self.durability, StoreDurability::Volatile)
265 }
266}
267
268#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize)]
270pub struct StoreHeapConfig;
271
272impl StoreHeapConfig {
273 #[must_use]
275 pub const fn new() -> Self {
276 Self
277 }
278}
279
280#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
283pub struct StoreJournaledMemoryConfig {
284 key: &'static str,
285}
286
287impl StoreJournaledMemoryConfig {
288 #[must_use]
290 pub const fn new(key: &'static str) -> Self {
291 Self { key }
292 }
293
294 #[must_use]
296 pub const fn key(self) -> &'static str {
297 self.key
298 }
299}
300
301impl Store {
302 #[must_use]
304 pub const fn new_heap(def: Def, canister: &'static str, heap: StoreHeapConfig) -> Self {
305 Self {
306 def,
307 canister,
308 storage: StoreStorage::Heap(heap),
309 }
310 }
311
312 #[must_use]
314 pub const fn new_journaled(
315 def: Def,
316 canister: &'static str,
317 journaled: StoreJournaledMemoryConfig,
318 ) -> Self {
319 Self {
320 def,
321 canister,
322 storage: StoreStorage::Journaled(journaled),
323 }
324 }
325
326 #[must_use]
327 pub const fn def(&self) -> &Def {
328 &self.def
329 }
330
331 #[must_use]
332 pub const fn canister(&self) -> &'static str {
333 self.canister
334 }
335
336 #[must_use]
338 pub const fn storage(&self) -> &StoreStorage {
339 &self.storage
340 }
341
342 #[must_use]
344 pub const fn is_heap_storage(&self) -> bool {
345 matches!(self.storage, StoreStorage::Heap(_))
346 }
347
348 #[must_use]
350 pub const fn is_journaled_storage(&self) -> bool {
351 matches!(self.storage, StoreStorage::Journaled(_))
352 }
353
354 #[must_use]
357 pub const fn journaled_memory_config(&self) -> Option<&StoreJournaledMemoryConfig> {
358 self.storage.journaled_memory_config()
359 }
360
361 #[must_use]
363 pub const fn storage_capabilities(&self) -> StoreStorageCapabilities {
364 self.storage.storage_capabilities()
365 }
366
367 #[must_use]
373 pub const fn stable_store_key(&self) -> &'static str {
374 match self.storage {
375 StoreStorage::Journaled(config) => config.key(),
376 StoreStorage::Heap(_) => panic!("heap stores do not have a stable store key"),
377 }
378 }
379
380 #[must_use]
386 pub fn stable_data_allocation(&self, memory_namespace: &str) -> StableMemoryAllocation {
387 self.stable_allocation(memory_namespace, StoreMemoryRole::Data)
388 }
389
390 #[must_use]
397 pub fn stable_data_allocation_with_schema_metadata(
398 &self,
399 memory_namespace: &str,
400 schema_metadata: StableMemoryAllocationMetadata,
401 ) -> StableMemoryAllocation {
402 self.stable_allocation_with_schema_metadata(
403 memory_namespace,
404 StoreMemoryRole::Data,
405 schema_metadata,
406 )
407 }
408
409 #[must_use]
415 pub fn stable_index_allocation(&self, memory_namespace: &str) -> StableMemoryAllocation {
416 self.stable_allocation(memory_namespace, StoreMemoryRole::Index)
417 }
418
419 #[must_use]
426 pub fn stable_index_allocation_with_schema_metadata(
427 &self,
428 memory_namespace: &str,
429 schema_metadata: StableMemoryAllocationMetadata,
430 ) -> StableMemoryAllocation {
431 self.stable_allocation_with_schema_metadata(
432 memory_namespace,
433 StoreMemoryRole::Index,
434 schema_metadata,
435 )
436 }
437
438 #[must_use]
444 pub fn stable_schema_allocation(&self, memory_namespace: &str) -> StableMemoryAllocation {
445 self.stable_allocation(memory_namespace, StoreMemoryRole::Schema)
446 }
447
448 #[must_use]
454 pub fn journal_allocation(&self, memory_namespace: &str) -> StableMemoryAllocation {
455 StableMemoryAllocation::without_schema_metadata(stable_store_memory_key(
456 memory_namespace,
457 self.stable_store_key(),
458 "journal",
459 ))
460 }
461
462 #[must_use]
469 pub fn stable_schema_allocation_with_schema_metadata(
470 &self,
471 memory_namespace: &str,
472 schema_metadata: StableMemoryAllocationMetadata,
473 ) -> StableMemoryAllocation {
474 self.stable_allocation_with_schema_metadata(
475 memory_namespace,
476 StoreMemoryRole::Schema,
477 schema_metadata,
478 )
479 }
480
481 #[must_use]
490 pub fn stable_allocation(
491 &self,
492 memory_namespace: &str,
493 role: StoreMemoryRole,
494 ) -> StableMemoryAllocation {
495 StableMemoryAllocation::without_schema_metadata(stable_store_memory_key(
496 memory_namespace,
497 self.stable_store_key(),
498 role.as_str(),
499 ))
500 }
501
502 fn stable_allocation_with_schema_metadata(
503 &self,
504 memory_namespace: &str,
505 role: StoreMemoryRole,
506 schema_metadata: StableMemoryAllocationMetadata,
507 ) -> StableMemoryAllocation {
508 StableMemoryAllocation::with_schema_metadata(
509 stable_store_memory_key(memory_namespace, self.stable_store_key(), role.as_str()),
510 schema_metadata,
511 )
512 }
513}
514
515#[derive(Clone, Copy, Debug, Eq, PartialEq)]
516pub enum StoreMemoryRole {
517 Data,
518 Index,
519 Schema,
520}
521
522impl StoreMemoryRole {
523 #[must_use]
524 pub const fn as_str(self) -> &'static str {
525 match self {
526 Self::Data => "data",
527 Self::Index => "index",
528 Self::Schema => "schema",
529 }
530 }
531}
532
533#[derive(Clone, Debug, Eq, PartialEq)]
538pub struct StableMemoryAllocationMetadata {
539 version: Option<u32>,
540 fingerprint_method_version: Option<u8>,
541 fingerprint: Option<String>,
542}
543
544impl StableMemoryAllocationMetadata {
545 const fn new(
546 schema_version: Option<u32>,
547 schema_fingerprint_method_version: Option<u8>,
548 schema_fingerprint: Option<String>,
549 ) -> Self {
550 Self {
551 version: schema_version,
552 fingerprint_method_version: schema_fingerprint_method_version,
553 fingerprint: schema_fingerprint,
554 }
555 }
556
557 #[must_use]
559 pub const fn from_accepted_schema_contract(
560 schema_version: u32,
561 schema_fingerprint_method_version: u8,
562 schema_fingerprint: String,
563 ) -> Self {
564 Self::new(
565 Some(schema_version),
566 Some(schema_fingerprint_method_version),
567 Some(schema_fingerprint),
568 )
569 }
570
571 #[must_use]
574 pub const fn absent() -> Self {
575 Self::new(None, None, None)
576 }
577
578 #[must_use]
580 pub const fn schema_version(&self) -> Option<u32> {
581 self.version
582 }
583
584 #[must_use]
586 pub const fn schema_fingerprint_method_version(&self) -> Option<u8> {
587 self.fingerprint_method_version
588 }
589
590 #[must_use]
592 pub const fn schema_fingerprint(&self) -> Option<&str> {
593 match &self.fingerprint {
594 Some(value) => Some(value.as_str()),
595 None => None,
596 }
597 }
598}
599
600#[derive(Clone, Debug, Eq, PartialEq)]
607pub struct StableMemoryAllocation {
608 stable_key: String,
609 schema_metadata: StableMemoryAllocationMetadata,
610}
611
612impl StableMemoryAllocation {
613 #[must_use]
615 pub const fn without_schema_metadata(stable_key: String) -> Self {
616 Self::with_schema_metadata(stable_key, StableMemoryAllocationMetadata::absent())
617 }
618
619 #[must_use]
624 pub const fn with_schema_metadata(
625 stable_key: String,
626 schema_metadata: StableMemoryAllocationMetadata,
627 ) -> Self {
628 Self {
629 stable_key,
630 schema_metadata,
631 }
632 }
633
634 #[must_use]
636 pub const fn stable_key(&self) -> &str {
637 self.stable_key.as_str()
638 }
639
640 #[must_use]
642 pub const fn schema_metadata(&self) -> &StableMemoryAllocationMetadata {
643 &self.schema_metadata
644 }
645
646 #[must_use]
648 pub const fn schema_version(&self) -> Option<u32> {
649 self.schema_metadata.schema_version()
650 }
651
652 #[must_use]
654 pub const fn schema_fingerprint_method_version(&self) -> Option<u8> {
655 self.schema_metadata.schema_fingerprint_method_version()
656 }
657
658 #[must_use]
660 pub const fn schema_fingerprint(&self) -> Option<&str> {
661 self.schema_metadata.schema_fingerprint()
662 }
663
664 #[must_use]
669 pub fn same_identity_as(&self, other: &Self) -> bool {
670 self.stable_key == other.stable_key
671 }
672}
673
674#[must_use]
675pub fn stable_memory_key(memory_namespace: &str, allocation: &str, role: &str) -> String {
676 format!("icydb.{memory_namespace}.{allocation}.{role}.v1")
677}
678
679#[must_use]
680fn stable_store_memory_key(memory_namespace: &str, store_key: &str, role: &str) -> String {
681 format!("icydb.{memory_namespace}.store.{store_key}.{role}.v1")
683}
684
685impl MacroNode for Store {
686 fn as_any(&self) -> &dyn std::any::Any {
687 self
688 }
689}
690
691impl ValidateNode for Store {
692 fn validate(&self) -> Result<(), ErrorTree> {
693 let mut errs = ErrorTree::new();
694
695 {
696 let schema = schema_read();
697
698 match schema.cast_node::<Canister>(self.canister()) {
699 Ok(canister) => match self.storage() {
700 StoreStorage::Heap(_) => {}
701 StoreStorage::Journaled(config) => {
702 validate_journaled_memory_config(&mut errs, self, *config, canister);
703 }
704 },
705 Err(e) => errs.add(e),
706 }
707 }
708
709 errs.result()
710 }
711}
712
713fn validate_journaled_memory_config(
714 errs: &mut ErrorTree,
715 store: &Store,
716 config: StoreJournaledMemoryConfig,
717 canister: &Canister,
718) {
719 validate_stable_key_segment(errs, "store key", config.key());
720 for allocation in [
721 store.stable_data_allocation(canister.memory_namespace()),
722 store.stable_index_allocation(canister.memory_namespace()),
723 store.stable_schema_allocation(canister.memory_namespace()),
724 store.journal_allocation(canister.memory_namespace()),
725 ] {
726 validate_stable_key(errs, "store allocation key", allocation.stable_key());
727 }
728}
729
730impl VisitableNode for Store {
731 fn route_key(&self) -> String {
732 self.def().path()
733 }
734
735 fn drive<V: Visitor>(&self, v: &mut V) {
736 self.def().accept(v);
737 }
738}
739
740#[cfg(test)]
741mod tests {
742 use crate::{
743 build::schema_write,
744 node::{Canister, SchemaNode},
745 };
746
747 use super::*;
748
749 fn insert_canister(path_module: &'static str, ident: &'static str) {
750 schema_write().insert_node(SchemaNode::Canister(Canister::new(
751 Def::new(path_module, ident),
752 "test_db",
753 None,
754 )));
755 }
756
757 #[test]
758 fn store_allocations_default_to_absent_schema_metadata() {
759 let store = Store::new_journaled(
760 Def::new("demo::rpg", "CharacterStore"),
761 "demo::rpg::Canister",
762 StoreJournaledMemoryConfig::new("store_110"),
763 );
764
765 for allocation in [
766 store.stable_data_allocation("demo_rpg"),
767 store.stable_index_allocation("demo_rpg"),
768 store.stable_schema_allocation("demo_rpg"),
769 store.journal_allocation("demo_rpg"),
770 ] {
771 assert_eq!(allocation.schema_version(), None);
772 assert_eq!(allocation.schema_fingerprint_method_version(), None);
773 assert_eq!(allocation.schema_fingerprint(), None);
774 assert_eq!(
775 allocation.schema_metadata(),
776 &StableMemoryAllocationMetadata::absent()
777 );
778 }
779 }
780
781 #[test]
782 fn allocation_metadata_is_role_specific_and_diagnostic_only() {
783 let store = Store::new_journaled(
784 Def::new("demo::rpg", "CharacterStore"),
785 "demo::rpg::Canister",
786 StoreJournaledMemoryConfig::new("store_110"),
787 );
788 let data = store.stable_data_allocation_with_schema_metadata(
789 "demo_rpg",
790 StableMemoryAllocationMetadata::from_accepted_schema_contract(
791 7,
792 1,
793 "data-row-layout".to_string(),
794 ),
795 );
796 let index = store.stable_index_allocation_with_schema_metadata(
797 "demo_rpg",
798 StableMemoryAllocationMetadata::from_accepted_schema_contract(
799 8,
800 1,
801 "index-catalog".to_string(),
802 ),
803 );
804 let schema = store.stable_schema_allocation_with_schema_metadata(
805 "demo_rpg",
806 StableMemoryAllocationMetadata::from_accepted_schema_contract(
807 10,
808 1,
809 "schema-catalog".to_string(),
810 ),
811 );
812 let data_after_reconcile = store.stable_data_allocation_with_schema_metadata(
813 "demo_rpg",
814 StableMemoryAllocationMetadata::from_accepted_schema_contract(
815 9,
816 1,
817 "data-row-layout-changed".to_string(),
818 ),
819 );
820
821 assert_eq!(data.schema_version(), Some(7));
822 assert_eq!(data.schema_fingerprint_method_version(), Some(1));
823 assert_eq!(data.schema_fingerprint(), Some("data-row-layout"));
824 assert_eq!(index.schema_version(), Some(8));
825 assert_eq!(index.schema_fingerprint_method_version(), Some(1));
826 assert_eq!(index.schema_fingerprint(), Some("index-catalog"));
827 assert_eq!(schema.schema_version(), Some(10));
828 assert_eq!(schema.schema_fingerprint_method_version(), Some(1));
829 assert_eq!(schema.schema_fingerprint(), Some("schema-catalog"));
830 assert!(data.same_identity_as(&data_after_reconcile));
831 assert!(!data.same_identity_as(&index));
832 assert!(!data.same_identity_as(&schema));
833 }
834
835 #[test]
836 fn store_owns_explicit_heap_storage_config() {
837 insert_canister("store_heap_config", "Canister");
838 let store = Store::new_heap(
839 Def::new("store_heap_config", "Store"),
840 "store_heap_config::Canister",
841 StoreHeapConfig::new(),
842 );
843
844 assert!(store.is_heap_storage());
845 assert!(store.validate().is_ok());
846 }
847
848 #[test]
849 fn heap_store_storage_capabilities_describe_volatile_contract() {
850 let store = Store::new_heap(
851 Def::new("store_heap_capabilities", "Store"),
852 "store_heap_capabilities::Canister",
853 StoreHeapConfig::new(),
854 );
855 let capabilities = store.storage_capabilities();
856
857 assert_eq!(capabilities.storage_mode(), StoreStorageMode::Heap);
858 assert_eq!(
859 capabilities.allocation_identity(),
860 AllocationIdentityCapability::Absent,
861 );
862 assert_eq!(capabilities.durability(), StoreDurability::Volatile);
863 assert_eq!(capabilities.recovery(), StoreRecoveryCapability::None);
864 assert_eq!(
865 capabilities.commit_participation(),
866 CommitParticipation::LiveOnly,
867 );
868 assert_eq!(
869 capabilities.schema_metadata(),
870 SchemaMetadataCapability::LiveRebuiltMetadata,
871 );
872 assert_eq!(
873 capabilities.relation_source(),
874 RelationSourceCapability::LiveSource,
875 );
876 assert_eq!(
877 capabilities.relation_target(),
878 RelationTargetCapability::VolatileTarget,
879 );
880 assert_eq!(
881 capabilities.live_validation(),
882 LiveValidationCapability::Supported,
883 );
884 assert!(!capabilities.has_allocation_identity());
885 assert!(!capabilities.participates_in_durable_commit());
886 assert!(capabilities.is_volatile());
887 }
888
889 #[test]
890 fn store_owns_explicit_journaled_storage_config() {
891 insert_canister("store_journaled_config", "Canister");
892 let store = Store::new_journaled(
893 Def::new("store_journaled_config", "Store"),
894 "store_journaled_config::Canister",
895 StoreJournaledMemoryConfig::new("store_110"),
896 );
897
898 assert!(store.is_journaled_storage());
899 assert!(!store.is_heap_storage());
900 let journaled = store
901 .journaled_memory_config()
902 .expect("journaled model stores four-role config explicitly");
903 assert_eq!(journaled.key(), "store_110");
904 assert_eq!(store.stable_store_key(), "store_110");
905 assert!(store.validate().is_ok());
906 }
907
908 #[test]
909 fn store_keys_validate_segments_and_complete_role_key_lengths() {
910 let canister = Canister::new(Def::new("bounded_key", "Canister"), "app", None);
911 let admitted = "a".repeat(101);
913 let oversized = "a".repeat(102);
914 for (key, accepted) in [
915 ("transfers", true),
916 ("", false),
917 ("Transfers", false),
918 ("1store", false),
919 ("store.name", false),
920 (admitted.as_str(), true),
921 (oversized.as_str(), false),
922 ] {
923 let config =
924 StoreJournaledMemoryConfig::new(Box::leak(key.to_string().into_boxed_str()));
925 let store = Store::new_journaled(
926 Def::new("bounded_key", "Store"),
927 "bounded_key::Canister",
928 config,
929 );
930 let mut errors = ErrorTree::new();
931 validate_journaled_memory_config(&mut errors, &store, config, &canister);
932 assert_eq!(errors.result().is_ok(), accepted);
933 }
934 }
935
936 #[test]
937 fn journaled_store_storage_capabilities_describe_cached_stable_contract() {
938 let store = Store::new_journaled(
939 Def::new("store_journaled_capabilities", "Store"),
940 "store_journaled_capabilities::Canister",
941 StoreJournaledMemoryConfig::new("store_110"),
942 );
943 let capabilities = store.storage_capabilities();
944
945 assert_eq!(capabilities.storage_mode(), StoreStorageMode::Journaled);
946 assert_eq!(
947 capabilities.allocation_identity(),
948 AllocationIdentityCapability::Present,
949 );
950 assert_eq!(capabilities.durability(), StoreDurability::Durable);
951 assert_eq!(
952 capabilities.recovery(),
953 StoreRecoveryCapability::StableBasePlusJournalReplay,
954 );
955 assert_eq!(
956 capabilities.commit_participation(),
957 CommitParticipation::Durable,
958 );
959 assert_eq!(
960 capabilities.schema_metadata(),
961 SchemaMetadataCapability::CanonicalStableHistoryPlusJournalTail,
962 );
963 assert_eq!(
964 capabilities.relation_source(),
965 RelationSourceCapability::DurableSource,
966 );
967 assert_eq!(
968 capabilities.relation_target(),
969 RelationTargetCapability::DurableTarget,
970 );
971 assert_eq!(
972 capabilities.live_validation(),
973 LiveValidationCapability::Supported,
974 );
975 assert!(capabilities.has_allocation_identity());
976 assert!(capabilities.participates_in_durable_commit());
977 assert!(!capabilities.is_volatile());
978 }
979
980 #[test]
981 fn journaled_store_allocations_use_role_named_stable_keys() {
982 let store = Store::new_journaled(
983 Def::new("demo::rpg", "CharacterStore"),
984 "demo::rpg::Canister",
985 StoreJournaledMemoryConfig::new("store_110"),
986 );
987
988 assert_eq!(
989 store.stable_data_allocation("demo_rpg").stable_key(),
990 "icydb.demo_rpg.store.store_110.data.v1",
991 );
992 assert_eq!(
993 store.stable_index_allocation("demo_rpg").stable_key(),
994 "icydb.demo_rpg.store.store_110.index.v1",
995 );
996 assert_eq!(
997 store.stable_schema_allocation("demo_rpg").stable_key(),
998 "icydb.demo_rpg.store.store_110.schema.v1",
999 );
1000 assert_eq!(
1001 store.journal_allocation("demo_rpg").stable_key(),
1002 "icydb.demo_rpg.store.store_110.journal.v1",
1003 );
1004
1005 let renamed = Store::new_journaled(
1007 Def::new("renamed::module", "RenamedStore"),
1008 "renamed::module::RenamedCanister",
1009 StoreJournaledMemoryConfig::new("store_110"),
1010 );
1011 for (before, after) in [
1012 (
1013 store.stable_data_allocation("demo_rpg"),
1014 renamed.stable_data_allocation("demo_rpg"),
1015 ),
1016 (
1017 store.stable_index_allocation("demo_rpg"),
1018 renamed.stable_index_allocation("demo_rpg"),
1019 ),
1020 (
1021 store.stable_schema_allocation("demo_rpg"),
1022 renamed.stable_schema_allocation("demo_rpg"),
1023 ),
1024 (
1025 store.journal_allocation("demo_rpg"),
1026 renamed.journal_allocation("demo_rpg"),
1027 ),
1028 ] {
1029 assert_eq!(before.stable_key(), after.stable_key());
1030 }
1031 }
1032
1033 #[test]
1034 fn storage_capabilities_are_not_allocation_identity() {
1035 let store_a = Store::new_journaled(
1036 Def::new("demo::rpg", "CharacterStore"),
1037 "demo::rpg::Canister",
1038 StoreJournaledMemoryConfig::new("store_110"),
1039 );
1040 let store_b = Store::new_journaled(
1041 Def::new("demo::rpg", "InventoryStore"),
1042 "demo::rpg::Canister",
1043 StoreJournaledMemoryConfig::new("store_120"),
1044 );
1045
1046 assert_eq!(
1047 store_a.storage_capabilities(),
1048 store_b.storage_capabilities()
1049 );
1050 assert_ne!(
1051 store_a.stable_data_allocation("demo_rpg"),
1052 store_b.stable_data_allocation("demo_rpg"),
1053 "stable allocation identity must remain separate from capabilities",
1054 );
1055 }
1056
1057 #[test]
1058 fn capability_consumers_use_axes_not_storage_mode() {
1059 const fn commit_label(capabilities: StoreStorageCapabilities) -> &'static str {
1060 match capabilities.commit_participation() {
1061 CommitParticipation::Durable => "durable",
1062 CommitParticipation::LiveOnly => "live-only",
1063 }
1064 }
1065
1066 let future_durable_heap_mode = StoreStorageCapabilities {
1067 storage_mode: StoreStorageMode::Heap,
1068 allocation_identity: AllocationIdentityCapability::Present,
1069 durability: StoreDurability::Durable,
1070 recovery: StoreRecoveryCapability::StableBasePlusJournalReplay,
1071 commit_participation: CommitParticipation::Durable,
1072 schema_metadata: SchemaMetadataCapability::CanonicalStableHistoryPlusJournalTail,
1073 relation_source: RelationSourceCapability::DurableSource,
1074 relation_target: RelationTargetCapability::DurableTarget,
1075 live_validation: LiveValidationCapability::Supported,
1076 };
1077
1078 assert_eq!(commit_label(future_durable_heap_mode), "durable");
1079 assert!(future_durable_heap_mode.participates_in_durable_commit());
1080 assert_eq!(
1081 future_durable_heap_mode.storage_mode(),
1082 StoreStorageMode::Heap,
1083 "the diagnostic storage mode must not drive commit policy",
1084 );
1085 }
1086}