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]
381 pub fn stable_data_allocation(&self, memory_namespace: &str) -> StableMemoryAllocation {
382 self.stable_allocation(memory_namespace, StoreMemoryRole::Data)
383 }
384
385 #[must_use]
388 pub fn stable_data_allocation_with_schema_metadata(
389 &self,
390 memory_namespace: &str,
391 schema_metadata: StableMemoryAllocationMetadata,
392 ) -> StableMemoryAllocation {
393 self.stable_allocation_with_schema_metadata(
394 memory_namespace,
395 StoreMemoryRole::Data,
396 schema_metadata,
397 )
398 }
399
400 #[must_use]
401 pub fn stable_index_allocation(&self, memory_namespace: &str) -> StableMemoryAllocation {
402 self.stable_allocation(memory_namespace, StoreMemoryRole::Index)
403 }
404
405 #[must_use]
408 pub fn stable_index_allocation_with_schema_metadata(
409 &self,
410 memory_namespace: &str,
411 schema_metadata: StableMemoryAllocationMetadata,
412 ) -> StableMemoryAllocation {
413 self.stable_allocation_with_schema_metadata(
414 memory_namespace,
415 StoreMemoryRole::Index,
416 schema_metadata,
417 )
418 }
419
420 #[must_use]
421 pub fn stable_schema_allocation(&self, memory_namespace: &str) -> StableMemoryAllocation {
422 self.stable_allocation(memory_namespace, StoreMemoryRole::Schema)
423 }
424
425 #[must_use]
427 pub fn journal_allocation(&self, memory_namespace: &str) -> StableMemoryAllocation {
428 StableMemoryAllocation::without_schema_metadata(stable_store_memory_key(
429 memory_namespace,
430 self.stable_store_key(),
431 "journal",
432 ))
433 }
434
435 #[must_use]
438 pub fn stable_schema_allocation_with_schema_metadata(
439 &self,
440 memory_namespace: &str,
441 schema_metadata: StableMemoryAllocationMetadata,
442 ) -> StableMemoryAllocation {
443 self.stable_allocation_with_schema_metadata(
444 memory_namespace,
445 StoreMemoryRole::Schema,
446 schema_metadata,
447 )
448 }
449
450 #[must_use]
451 pub fn stable_allocation(
452 &self,
453 memory_namespace: &str,
454 role: StoreMemoryRole,
455 ) -> StableMemoryAllocation {
456 StableMemoryAllocation::without_schema_metadata(stable_store_memory_key(
457 memory_namespace,
458 self.stable_store_key(),
459 role.as_str(),
460 ))
461 }
462
463 fn stable_allocation_with_schema_metadata(
464 &self,
465 memory_namespace: &str,
466 role: StoreMemoryRole,
467 schema_metadata: StableMemoryAllocationMetadata,
468 ) -> StableMemoryAllocation {
469 StableMemoryAllocation::with_schema_metadata(
470 stable_store_memory_key(memory_namespace, self.stable_store_key(), role.as_str()),
471 schema_metadata,
472 )
473 }
474}
475
476#[derive(Clone, Copy, Debug, Eq, PartialEq)]
477pub enum StoreMemoryRole {
478 Data,
479 Index,
480 Schema,
481}
482
483impl StoreMemoryRole {
484 #[must_use]
485 pub const fn as_str(self) -> &'static str {
486 match self {
487 Self::Data => "data",
488 Self::Index => "index",
489 Self::Schema => "schema",
490 }
491 }
492}
493
494#[derive(Clone, Debug, Eq, PartialEq)]
499pub struct StableMemoryAllocationMetadata {
500 version: Option<u32>,
501 fingerprint_method_version: Option<u8>,
502 fingerprint: Option<String>,
503}
504
505impl StableMemoryAllocationMetadata {
506 const fn new(
507 schema_version: Option<u32>,
508 schema_fingerprint_method_version: Option<u8>,
509 schema_fingerprint: Option<String>,
510 ) -> Self {
511 Self {
512 version: schema_version,
513 fingerprint_method_version: schema_fingerprint_method_version,
514 fingerprint: schema_fingerprint,
515 }
516 }
517
518 #[must_use]
520 pub const fn from_accepted_schema_contract(
521 schema_version: u32,
522 schema_fingerprint_method_version: u8,
523 schema_fingerprint: String,
524 ) -> Self {
525 Self::new(
526 Some(schema_version),
527 Some(schema_fingerprint_method_version),
528 Some(schema_fingerprint),
529 )
530 }
531
532 #[must_use]
535 pub const fn absent() -> Self {
536 Self::new(None, None, None)
537 }
538
539 #[must_use]
541 pub const fn schema_version(&self) -> Option<u32> {
542 self.version
543 }
544
545 #[must_use]
547 pub const fn schema_fingerprint_method_version(&self) -> Option<u8> {
548 self.fingerprint_method_version
549 }
550
551 #[must_use]
553 pub const fn schema_fingerprint(&self) -> Option<&str> {
554 match &self.fingerprint {
555 Some(value) => Some(value.as_str()),
556 None => None,
557 }
558 }
559}
560
561#[derive(Clone, Debug, Eq, PartialEq)]
568pub struct StableMemoryAllocation {
569 stable_key: String,
570 schema_metadata: StableMemoryAllocationMetadata,
571}
572
573impl StableMemoryAllocation {
574 #[must_use]
576 pub const fn without_schema_metadata(stable_key: String) -> Self {
577 Self::with_schema_metadata(stable_key, StableMemoryAllocationMetadata::absent())
578 }
579
580 #[must_use]
585 pub const fn with_schema_metadata(
586 stable_key: String,
587 schema_metadata: StableMemoryAllocationMetadata,
588 ) -> Self {
589 Self {
590 stable_key,
591 schema_metadata,
592 }
593 }
594
595 #[must_use]
597 pub const fn stable_key(&self) -> &str {
598 self.stable_key.as_str()
599 }
600
601 #[must_use]
603 pub const fn schema_metadata(&self) -> &StableMemoryAllocationMetadata {
604 &self.schema_metadata
605 }
606
607 #[must_use]
609 pub const fn schema_version(&self) -> Option<u32> {
610 self.schema_metadata.schema_version()
611 }
612
613 #[must_use]
615 pub const fn schema_fingerprint_method_version(&self) -> Option<u8> {
616 self.schema_metadata.schema_fingerprint_method_version()
617 }
618
619 #[must_use]
621 pub const fn schema_fingerprint(&self) -> Option<&str> {
622 self.schema_metadata.schema_fingerprint()
623 }
624
625 #[must_use]
630 pub fn same_identity_as(&self, other: &Self) -> bool {
631 self.stable_key == other.stable_key
632 }
633}
634
635#[must_use]
636pub fn stable_memory_key(memory_namespace: &str, allocation: &str, role: &str) -> String {
637 format!("icydb.{memory_namespace}.{allocation}.{role}.v1")
638}
639
640#[must_use]
641fn stable_store_memory_key(memory_namespace: &str, store_key: &str, role: &str) -> String {
642 format!("icydb.{memory_namespace}.store.{store_key}.{role}.v1")
644}
645
646impl MacroNode for Store {
647 fn as_any(&self) -> &dyn std::any::Any {
648 self
649 }
650}
651
652impl ValidateNode for Store {
653 fn validate(&self) -> Result<(), ErrorTree> {
654 let mut errs = ErrorTree::new();
655
656 {
657 let schema = schema_read();
658
659 match schema.cast_node::<Canister>(self.canister()) {
660 Ok(canister) => match self.storage() {
661 StoreStorage::Heap(_) => {}
662 StoreStorage::Journaled(config) => {
663 validate_journaled_memory_config(&mut errs, self, *config, canister);
664 }
665 },
666 Err(e) => errs.add(e),
667 }
668 }
669
670 errs.result()
671 }
672}
673
674fn validate_journaled_memory_config(
675 errs: &mut ErrorTree,
676 store: &Store,
677 config: StoreJournaledMemoryConfig,
678 canister: &Canister,
679) {
680 validate_stable_key_segment(errs, "store key", config.key());
681 for allocation in [
682 store.stable_data_allocation(canister.memory_namespace()),
683 store.stable_index_allocation(canister.memory_namespace()),
684 store.stable_schema_allocation(canister.memory_namespace()),
685 store.journal_allocation(canister.memory_namespace()),
686 ] {
687 validate_stable_key(errs, "store allocation key", allocation.stable_key());
688 }
689}
690
691impl VisitableNode for Store {
692 fn route_key(&self) -> String {
693 self.def().path()
694 }
695
696 fn drive<V: Visitor>(&self, v: &mut V) {
697 self.def().accept(v);
698 }
699}
700
701#[cfg(test)]
702mod tests {
703 use crate::{
704 build::schema_write,
705 node::{Canister, SchemaNode},
706 };
707
708 use super::*;
709
710 fn insert_canister(path_module: &'static str, ident: &'static str) {
711 schema_write().insert_node(SchemaNode::Canister(Canister::new(
712 Def::new(path_module, ident),
713 "test_db",
714 None,
715 )));
716 }
717
718 #[test]
719 fn store_allocations_default_to_absent_schema_metadata() {
720 let store = Store::new_journaled(
721 Def::new("demo::rpg", "CharacterStore"),
722 "demo::rpg::Canister",
723 StoreJournaledMemoryConfig::new("store_110"),
724 );
725
726 for allocation in [
727 store.stable_data_allocation("demo_rpg"),
728 store.stable_index_allocation("demo_rpg"),
729 store.stable_schema_allocation("demo_rpg"),
730 store.journal_allocation("demo_rpg"),
731 ] {
732 assert_eq!(allocation.schema_version(), None);
733 assert_eq!(allocation.schema_fingerprint_method_version(), None);
734 assert_eq!(allocation.schema_fingerprint(), None);
735 assert_eq!(
736 allocation.schema_metadata(),
737 &StableMemoryAllocationMetadata::absent()
738 );
739 }
740 }
741
742 #[test]
743 fn allocation_metadata_is_role_specific_and_diagnostic_only() {
744 let store = Store::new_journaled(
745 Def::new("demo::rpg", "CharacterStore"),
746 "demo::rpg::Canister",
747 StoreJournaledMemoryConfig::new("store_110"),
748 );
749 let data = store.stable_data_allocation_with_schema_metadata(
750 "demo_rpg",
751 StableMemoryAllocationMetadata::from_accepted_schema_contract(
752 7,
753 1,
754 "data-row-layout".to_string(),
755 ),
756 );
757 let index = store.stable_index_allocation_with_schema_metadata(
758 "demo_rpg",
759 StableMemoryAllocationMetadata::from_accepted_schema_contract(
760 8,
761 1,
762 "index-catalog".to_string(),
763 ),
764 );
765 let schema = store.stable_schema_allocation_with_schema_metadata(
766 "demo_rpg",
767 StableMemoryAllocationMetadata::from_accepted_schema_contract(
768 10,
769 1,
770 "schema-catalog".to_string(),
771 ),
772 );
773 let data_after_reconcile = store.stable_data_allocation_with_schema_metadata(
774 "demo_rpg",
775 StableMemoryAllocationMetadata::from_accepted_schema_contract(
776 9,
777 1,
778 "data-row-layout-changed".to_string(),
779 ),
780 );
781
782 assert_eq!(data.schema_version(), Some(7));
783 assert_eq!(data.schema_fingerprint_method_version(), Some(1));
784 assert_eq!(data.schema_fingerprint(), Some("data-row-layout"));
785 assert_eq!(index.schema_version(), Some(8));
786 assert_eq!(index.schema_fingerprint_method_version(), Some(1));
787 assert_eq!(index.schema_fingerprint(), Some("index-catalog"));
788 assert_eq!(schema.schema_version(), Some(10));
789 assert_eq!(schema.schema_fingerprint_method_version(), Some(1));
790 assert_eq!(schema.schema_fingerprint(), Some("schema-catalog"));
791 assert!(data.same_identity_as(&data_after_reconcile));
792 assert!(!data.same_identity_as(&index));
793 assert!(!data.same_identity_as(&schema));
794 }
795
796 #[test]
797 fn store_owns_explicit_heap_storage_config() {
798 insert_canister("store_heap_config", "Canister");
799 let store = Store::new_heap(
800 Def::new("store_heap_config", "Store"),
801 "store_heap_config::Canister",
802 StoreHeapConfig::new(),
803 );
804
805 assert!(store.is_heap_storage());
806 assert!(store.validate().is_ok());
807 }
808
809 #[test]
810 fn heap_store_storage_capabilities_describe_volatile_contract() {
811 let store = Store::new_heap(
812 Def::new("store_heap_capabilities", "Store"),
813 "store_heap_capabilities::Canister",
814 StoreHeapConfig::new(),
815 );
816 let capabilities = store.storage_capabilities();
817
818 assert_eq!(capabilities.storage_mode(), StoreStorageMode::Heap);
819 assert_eq!(
820 capabilities.allocation_identity(),
821 AllocationIdentityCapability::Absent,
822 );
823 assert_eq!(capabilities.durability(), StoreDurability::Volatile);
824 assert_eq!(capabilities.recovery(), StoreRecoveryCapability::None);
825 assert_eq!(
826 capabilities.commit_participation(),
827 CommitParticipation::LiveOnly,
828 );
829 assert_eq!(
830 capabilities.schema_metadata(),
831 SchemaMetadataCapability::LiveRebuiltMetadata,
832 );
833 assert_eq!(
834 capabilities.relation_source(),
835 RelationSourceCapability::LiveSource,
836 );
837 assert_eq!(
838 capabilities.relation_target(),
839 RelationTargetCapability::VolatileTarget,
840 );
841 assert_eq!(
842 capabilities.live_validation(),
843 LiveValidationCapability::Supported,
844 );
845 assert!(!capabilities.has_allocation_identity());
846 assert!(!capabilities.participates_in_durable_commit());
847 assert!(capabilities.is_volatile());
848 }
849
850 #[test]
851 fn store_owns_explicit_journaled_storage_config() {
852 insert_canister("store_journaled_config", "Canister");
853 let store = Store::new_journaled(
854 Def::new("store_journaled_config", "Store"),
855 "store_journaled_config::Canister",
856 StoreJournaledMemoryConfig::new("store_110"),
857 );
858
859 assert!(store.is_journaled_storage());
860 assert!(!store.is_heap_storage());
861 let journaled = store
862 .journaled_memory_config()
863 .expect("journaled model stores four-role config explicitly");
864 assert_eq!(journaled.key(), "store_110");
865 assert_eq!(store.stable_store_key(), "store_110");
866 assert!(store.validate().is_ok());
867 }
868
869 #[test]
870 fn store_keys_validate_segments_and_complete_role_key_lengths() {
871 let canister = Canister::new(Def::new("bounded_key", "Canister"), "app", None);
872 let admitted = "a".repeat(101);
874 let oversized = "a".repeat(102);
875 for (key, accepted) in [
876 ("transfers", true),
877 ("", false),
878 ("Transfers", false),
879 ("1store", false),
880 ("store.name", false),
881 (admitted.as_str(), true),
882 (oversized.as_str(), false),
883 ] {
884 let config =
885 StoreJournaledMemoryConfig::new(Box::leak(key.to_string().into_boxed_str()));
886 let store = Store::new_journaled(
887 Def::new("bounded_key", "Store"),
888 "bounded_key::Canister",
889 config,
890 );
891 let mut errors = ErrorTree::new();
892 validate_journaled_memory_config(&mut errors, &store, config, &canister);
893 assert_eq!(errors.result().is_ok(), accepted);
894 }
895 }
896
897 #[test]
898 fn journaled_store_storage_capabilities_describe_cached_stable_contract() {
899 let store = Store::new_journaled(
900 Def::new("store_journaled_capabilities", "Store"),
901 "store_journaled_capabilities::Canister",
902 StoreJournaledMemoryConfig::new("store_110"),
903 );
904 let capabilities = store.storage_capabilities();
905
906 assert_eq!(capabilities.storage_mode(), StoreStorageMode::Journaled);
907 assert_eq!(
908 capabilities.allocation_identity(),
909 AllocationIdentityCapability::Present,
910 );
911 assert_eq!(capabilities.durability(), StoreDurability::Durable);
912 assert_eq!(
913 capabilities.recovery(),
914 StoreRecoveryCapability::StableBasePlusJournalReplay,
915 );
916 assert_eq!(
917 capabilities.commit_participation(),
918 CommitParticipation::Durable,
919 );
920 assert_eq!(
921 capabilities.schema_metadata(),
922 SchemaMetadataCapability::CanonicalStableHistoryPlusJournalTail,
923 );
924 assert_eq!(
925 capabilities.relation_source(),
926 RelationSourceCapability::DurableSource,
927 );
928 assert_eq!(
929 capabilities.relation_target(),
930 RelationTargetCapability::DurableTarget,
931 );
932 assert_eq!(
933 capabilities.live_validation(),
934 LiveValidationCapability::Supported,
935 );
936 assert!(capabilities.has_allocation_identity());
937 assert!(capabilities.participates_in_durable_commit());
938 assert!(!capabilities.is_volatile());
939 }
940
941 #[test]
942 fn journaled_store_allocations_use_role_named_stable_keys() {
943 let store = Store::new_journaled(
944 Def::new("demo::rpg", "CharacterStore"),
945 "demo::rpg::Canister",
946 StoreJournaledMemoryConfig::new("store_110"),
947 );
948
949 assert_eq!(
950 store.stable_data_allocation("demo_rpg").stable_key(),
951 "icydb.demo_rpg.store.store_110.data.v1",
952 );
953 assert_eq!(
954 store.stable_index_allocation("demo_rpg").stable_key(),
955 "icydb.demo_rpg.store.store_110.index.v1",
956 );
957 assert_eq!(
958 store.stable_schema_allocation("demo_rpg").stable_key(),
959 "icydb.demo_rpg.store.store_110.schema.v1",
960 );
961 assert_eq!(
962 store.journal_allocation("demo_rpg").stable_key(),
963 "icydb.demo_rpg.store.store_110.journal.v1",
964 );
965
966 let renamed = Store::new_journaled(
968 Def::new("renamed::module", "RenamedStore"),
969 "renamed::module::RenamedCanister",
970 StoreJournaledMemoryConfig::new("store_110"),
971 );
972 for (before, after) in [
973 (
974 store.stable_data_allocation("demo_rpg"),
975 renamed.stable_data_allocation("demo_rpg"),
976 ),
977 (
978 store.stable_index_allocation("demo_rpg"),
979 renamed.stable_index_allocation("demo_rpg"),
980 ),
981 (
982 store.stable_schema_allocation("demo_rpg"),
983 renamed.stable_schema_allocation("demo_rpg"),
984 ),
985 (
986 store.journal_allocation("demo_rpg"),
987 renamed.journal_allocation("demo_rpg"),
988 ),
989 ] {
990 assert_eq!(before.stable_key(), after.stable_key());
991 }
992 }
993
994 #[test]
995 fn storage_capabilities_are_not_allocation_identity() {
996 let store_a = Store::new_journaled(
997 Def::new("demo::rpg", "CharacterStore"),
998 "demo::rpg::Canister",
999 StoreJournaledMemoryConfig::new("store_110"),
1000 );
1001 let store_b = Store::new_journaled(
1002 Def::new("demo::rpg", "InventoryStore"),
1003 "demo::rpg::Canister",
1004 StoreJournaledMemoryConfig::new("store_120"),
1005 );
1006
1007 assert_eq!(
1008 store_a.storage_capabilities(),
1009 store_b.storage_capabilities()
1010 );
1011 assert_ne!(
1012 store_a.stable_data_allocation("demo_rpg"),
1013 store_b.stable_data_allocation("demo_rpg"),
1014 "stable allocation identity must remain separate from capabilities",
1015 );
1016 }
1017
1018 #[test]
1019 fn capability_consumers_use_axes_not_storage_mode() {
1020 const fn commit_label(capabilities: StoreStorageCapabilities) -> &'static str {
1021 match capabilities.commit_participation() {
1022 CommitParticipation::Durable => "durable",
1023 CommitParticipation::LiveOnly => "live-only",
1024 }
1025 }
1026
1027 let future_durable_heap_mode = StoreStorageCapabilities {
1028 storage_mode: StoreStorageMode::Heap,
1029 allocation_identity: AllocationIdentityCapability::Present,
1030 durability: StoreDurability::Durable,
1031 recovery: StoreRecoveryCapability::StableBasePlusJournalReplay,
1032 commit_participation: CommitParticipation::Durable,
1033 schema_metadata: SchemaMetadataCapability::CanonicalStableHistoryPlusJournalTail,
1034 relation_source: RelationSourceCapability::DurableSource,
1035 relation_target: RelationTargetCapability::DurableTarget,
1036 live_validation: LiveValidationCapability::Supported,
1037 };
1038
1039 assert_eq!(commit_label(future_durable_heap_mode), "durable");
1040 assert!(future_durable_heap_mode.participates_in_durable_commit());
1041 assert_eq!(
1042 future_durable_heap_mode.storage_mode(),
1043 StoreStorageMode::Heap,
1044 "the diagnostic storage mode must not drive commit policy",
1045 );
1046 }
1047}