1use std::error::Error;
4use std::fmt;
5use std::num::{NonZeroU32, NonZeroU64};
6
7use crate::limits::{
8 MAX_ACTOR_ID_BYTES, MAX_CONTRACT_LINEAGE_BYTES, MAX_ENVIRONMENT_BYTES,
9 MAX_IDEMPOTENCY_KEY_BYTES, MAX_KEY_BYTES, MAX_TENANT_ID_BYTES,
10};
11
12#[derive(Clone, Copy, Debug, Eq, PartialEq)]
14pub struct ZeroNumericIdError;
15
16impl fmt::Display for ZeroNumericIdError {
17 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
18 formatter.write_str("numeric identifier must be nonzero")
19 }
20}
21
22impl Error for ZeroNumericIdError {}
23
24macro_rules! nonzero_id {
25 ($(#[$meta:meta])* $name:ident, $inner:ty, $nonzero:ty) => {
26 $(#[$meta])*
27 #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
28 pub struct $name($nonzero);
29 crate::inspect_owned_capacity_tuple_v1!($name, $nonzero);
30
31 impl $name {
32 #[must_use]
34 pub const fn new(value: $inner) -> Option<Self> {
35 match <$nonzero>::new(value) {
36 Some(value) => Some(Self(value)),
37 None => None,
38 }
39 }
40
41 #[must_use]
43 pub const fn get(self) -> $inner {
44 self.0.get()
45 }
46
47 #[must_use]
49 pub const fn to_be_bytes(self) -> [u8; size_of::<$inner>()] {
50 self.get().to_be_bytes()
51 }
52 }
53
54 impl TryFrom<$inner> for $name {
55 type Error = ZeroNumericIdError;
56
57 fn try_from(value: $inner) -> Result<Self, Self::Error> {
58 Self::new(value).ok_or(ZeroNumericIdError)
59 }
60 }
61
62 impl From<$name> for $inner {
63 fn from(value: $name) -> Self {
64 value.get()
65 }
66 }
67
68 impl fmt::Display for $name {
69 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
70 self.get().fmt(formatter)
71 }
72 }
73 };
74}
75
76macro_rules! allocatable_nonzero_id {
77 ($(#[$meta:meta])* $name:ident, $inner:ty, $nonzero:ty) => {
78 nonzero_id!($(#[$meta])* $name, $inner, $nonzero);
79
80 impl $name {
81 #[must_use]
83 pub const fn first() -> Self {
84 Self(<$nonzero>::MIN)
85 }
86
87 #[must_use]
89 pub const fn checked_next(self) -> Option<Self> {
90 match self.get().checked_add(1) {
91 Some(value) => Self::new(value),
92 None => None,
93 }
94 }
95 }
96 };
97}
98
99nonzero_id!(
100 ContractVersion,
102 u64,
103 NonZeroU64
104);
105nonzero_id!(
106 QueryModuleVersion,
108 u64,
109 NonZeroU64
110);
111allocatable_nonzero_id!(
112 CommitSequence,
114 u64,
115 NonZeroU64
116);
117allocatable_nonzero_id!(
118 EntityVersion,
120 u64,
121 NonZeroU64
122);
123
124allocatable_nonzero_id!(
125 EntityTypeId,
127 u32,
128 NonZeroU32
129);
130allocatable_nonzero_id!(
131 FieldId,
133 u32,
134 NonZeroU32
135);
136allocatable_nonzero_id!(
137 CommandId,
139 u32,
140 NonZeroU32
141);
142allocatable_nonzero_id!(
143 OutcomeId,
145 u32,
146 NonZeroU32
147);
148allocatable_nonzero_id!(
149 ProjectionId,
151 u32,
152 NonZeroU32
153);
154allocatable_nonzero_id!(
155 EventTypeId,
157 u32,
158 NonZeroU32
159);
160allocatable_nonzero_id!(
161 IndexId,
163 u32,
164 NonZeroU32
165);
166allocatable_nonzero_id!(
167 EnumTypeId,
169 u32,
170 NonZeroU32
171);
172allocatable_nonzero_id!(
173 VectorFieldId,
175 u32,
176 NonZeroU32
177);
178allocatable_nonzero_id!(
179 EnumVariantId,
181 u32,
182 NonZeroU32
183);
184nonzero_id!(
185 DigestKeyId,
187 u32,
188 NonZeroU32
189);
190allocatable_nonzero_id!(
191 AggregateTypeId,
193 u32,
194 NonZeroU32
195);
196allocatable_nonzero_id!(
197 InvariantId,
199 u32,
200 NonZeroU32
201);
202allocatable_nonzero_id!(
203 IndexEpoch,
205 u64,
206 NonZeroU64
207);
208
209#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
211pub enum IndexEpochPosition {
212 BeforeFirst,
214 Value(IndexEpoch),
216}
217
218allocatable_nonzero_id!(
219 AdministrationSequence,
221 u64,
222 NonZeroU64
223);
224
225#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
227pub enum FrontierPosition {
228 BeforeFirst,
230 AppliedThrough(CommitSequence),
232}
233
234#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
236pub struct EventId {
237 commit_sequence: CommitSequence,
238 event_ordinal: u32,
239}
240
241impl EventId {
242 #[must_use]
244 pub const fn new(commit_sequence: CommitSequence, event_ordinal: u32) -> Self {
245 Self {
246 commit_sequence,
247 event_ordinal,
248 }
249 }
250
251 #[must_use]
253 pub const fn from_be_bytes(bytes: [u8; 12]) -> Option<Self> {
254 let commit_sequence = match CommitSequence::new(u64::from_be_bytes([
255 bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
256 ])) {
257 Some(value) => value,
258 None => return None,
259 };
260 let event_ordinal = u32::from_be_bytes([bytes[8], bytes[9], bytes[10], bytes[11]]);
261 Some(Self::new(commit_sequence, event_ordinal))
262 }
263
264 #[must_use]
266 pub const fn commit_sequence(self) -> CommitSequence {
267 self.commit_sequence
268 }
269
270 #[must_use]
272 pub const fn event_ordinal(self) -> u32 {
273 self.event_ordinal
274 }
275
276 #[must_use]
278 pub const fn to_be_bytes(self) -> [u8; 12] {
279 let sequence = self.commit_sequence.to_be_bytes();
280 let ordinal = self.event_ordinal.to_be_bytes();
281 [
282 sequence[0],
283 sequence[1],
284 sequence[2],
285 sequence[3],
286 sequence[4],
287 sequence[5],
288 sequence[6],
289 sequence[7],
290 ordinal[0],
291 ordinal[1],
292 ordinal[2],
293 ordinal[3],
294 ]
295 }
296}
297
298macro_rules! hash_id {
299 ($(#[$meta:meta])* $name:ident) => {
300 $(#[$meta])*
301 #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
302 pub struct $name([u8; 32]);
303 crate::inspect_owned_capacity_tuple_v1!($name, [u8; 32]);
304
305 impl $name {
306 #[must_use]
308 pub const fn from_bytes(bytes: [u8; 32]) -> Self {
309 Self(bytes)
310 }
311
312 #[must_use]
314 pub const fn as_bytes(&self) -> &[u8; 32] {
315 &self.0
316 }
317
318 #[must_use]
320 pub const fn into_bytes(self) -> [u8; 32] {
321 self.0
322 }
323 }
324 };
325}
326
327hash_id!(
328 CanonicalValueHash
330);
331hash_id!(
332 PlanHash
334);
335hash_id!(
336 ProjectionPlanHash
338);
339hash_id!(
340 ProjectionProviderDescriptorHash
342);
343hash_id!(
344 ColumnarDefinitionSemanticsHashV1
346);
347hash_id!(
348 ColumnarProjectionSpecHashV1
350);
351hash_id!(
352 QueryPlanHash
354);
355hash_id!(
356 PartitionSetObservationHash
358);
359hash_id!(
360 QueryModuleHash
362);
363hash_id!(
364 ReactiveSourceHash
366);
367hash_id!(
368 ReactiveOperationHash
370);
371hash_id!(
372 ReactiveModuleHash
374);
375hash_id!(
376 EventConsumerIdentityHash
378);
379hash_id!(
380 ScheduledAttemptHash
382);
383hash_id!(
384 ApplicationInstallationPlanHash
386);
387hash_id!(
388 ApplicationInstallationReceiptHash
390);
391hash_id!(
392 AdapterConformanceManifestHash
394);
395hash_id!(
396 ApplicationManifestHash
398);
399hash_id!(
400 ApplicationSourceHash
402);
403hash_id!(
404 ApplicationLockHash
406);
407hash_id!(
408 GeneratedArtifactHash
410);
411hash_id!(
412 ApplicationRoleHash
414);
415hash_id!(
416 ApplicationRoleDefinitionHash
418);
419hash_id!(
420 QuerySourceHash
422);
423hash_id!(
424 QueryParameterHash
426);
427hash_id!(
428 ContractPlanRootHash
430);
431hash_id!(
432 SourceHash
434);
435hash_id!(
436 MigrationSourceHash
438);
439hash_id!(
440 ContractBundleHash
442);
443hash_id!(
444 MigrationBundleHash
446);
447hash_id!(
448 CanonicalInputHash
450);
451hash_id!(
452 EventHash
454);
455hash_id!(
456 EntityRecordHash
458);
459hash_id!(
460 EntityTransitionHash
462);
463hash_id!(
464 EntityKeyHash
466);
467hash_id!(
468 ConflictKeyHash
470);
471hash_id!(
472 PartitionKeyHash
474);
475hash_id!(
476 SchemaHash
478);
479hash_id!(
480 OfflineMaintenanceInputHash
482);
483hash_id!(
484 ContractMigrationInputHash
486);
487hash_id!(
488 ContractMigrationJournalHash
490);
491hash_id!(
492 ContractMigrationValidationDigest
494);
495hash_id!(
496 ApplicationExportPageHash
498);
499hash_id!(
500 ApplicationExportLedgerHash
502);
503hash_id!(
504 ApplicationExportManifestHash
506);
507hash_id!(
508 ApplicationExportReceiptHash
510);
511hash_id!(
512 ApplicationPortabilityManifestHash
514);
515hash_id!(
516 ApplicationReimportReceiptHash
518);
519
520#[derive(Clone, Copy, Debug, Eq, PartialEq)]
522pub enum UuidV7Error {
523 InvalidVariant,
525 InvalidVersion,
527}
528
529impl fmt::Display for UuidV7Error {
530 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
531 match self {
532 Self::InvalidVariant => formatter.write_str("identifier has an invalid UUID variant"),
533 Self::InvalidVersion => formatter.write_str("identifier is not UUID version 7"),
534 }
535 }
536}
537
538impl Error for UuidV7Error {}
539
540#[derive(Clone, Copy, Debug, Eq, PartialEq)]
542pub enum UuidV7ConstructionError {
543 UnixMillisecondsOutOfRange,
545}
546
547impl fmt::Display for UuidV7ConstructionError {
548 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
549 formatter.write_str("UUIDv7 Unix-millisecond timestamp is out of range")
550 }
551}
552
553impl Error for UuidV7ConstructionError {}
554
555const UUID_V7_MAX_UNIX_MILLISECONDS: u64 = 0xffff_ffff_ffff;
556
557const fn assemble_uuid_v7(
558 unix_milliseconds: u64,
559 random: [u8; 10],
560) -> Result<[u8; 16], UuidV7ConstructionError> {
561 if unix_milliseconds > UUID_V7_MAX_UNIX_MILLISECONDS {
562 return Err(UuidV7ConstructionError::UnixMillisecondsOutOfRange);
563 }
564
565 let timestamp = unix_milliseconds.to_be_bytes();
566 Ok([
567 timestamp[2],
568 timestamp[3],
569 timestamp[4],
570 timestamp[5],
571 timestamp[6],
572 timestamp[7],
573 0x70 | (random[0] & 0x0f),
574 random[1],
575 0x80 | (random[2] & 0x3f),
576 random[3],
577 random[4],
578 random[5],
579 random[6],
580 random[7],
581 random[8],
582 random[9],
583 ])
584}
585
586fn validate_uuid_v7(bytes: &[u8; 16]) -> Result<(), UuidV7Error> {
587 if bytes[8] & 0b1100_0000 != 0b1000_0000 {
588 return Err(UuidV7Error::InvalidVariant);
589 }
590 if bytes[6] >> 4 != 7 {
591 return Err(UuidV7Error::InvalidVersion);
592 }
593 Ok(())
594}
595
596fn fmt_uuid(bytes: &[u8; 16], formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
597 write!(
598 formatter,
599 "{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
600 bytes[0],
601 bytes[1],
602 bytes[2],
603 bytes[3],
604 bytes[4],
605 bytes[5],
606 bytes[6],
607 bytes[7],
608 bytes[8],
609 bytes[9],
610 bytes[10],
611 bytes[11],
612 bytes[12],
613 bytes[13],
614 bytes[14],
615 bytes[15]
616 )
617}
618
619macro_rules! uuid_v7_id {
620 ($(#[$meta:meta])* $name:ident) => {
621 $(#[$meta])*
622 #[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
623 pub struct $name([u8; 16]);
624 crate::inspect_owned_capacity_tuple_v1!($name, [u8; 16]);
625
626 impl $name {
627 pub const fn from_unix_milliseconds_and_random(
629 unix_milliseconds: u64,
630 random: [u8; 10],
631 ) -> Result<Self, UuidV7ConstructionError> {
632 match assemble_uuid_v7(unix_milliseconds, random) {
633 Ok(bytes) => Ok(Self(bytes)),
634 Err(error) => Err(error),
635 }
636 }
637
638 pub fn from_bytes(bytes: [u8; 16]) -> Result<Self, UuidV7Error> {
640 validate_uuid_v7(&bytes)?;
641 Ok(Self(bytes))
642 }
643
644 #[must_use]
646 pub const fn as_bytes(&self) -> &[u8; 16] {
647 &self.0
648 }
649
650 #[must_use]
652 pub const fn into_bytes(self) -> [u8; 16] {
653 self.0
654 }
655 }
656
657 impl fmt::Display for $name {
658 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
659 fmt_uuid(&self.0, formatter)
660 }
661 }
662
663 impl fmt::Debug for $name {
664 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
665 formatter.write_str(concat!(stringify!($name), "("))?;
666 fmt_uuid(&self.0, formatter)?;
667 formatter.write_str(")")
668 }
669 }
670 };
671}
672
673uuid_v7_id!(
674 RequestId
676);
677uuid_v7_id!(
678 AgentSessionId
680);
681uuid_v7_id!(
682 IncidentId
684);
685uuid_v7_id!(
686 DatabaseId
688);
689uuid_v7_id!(
690 CapabilityId
692);
693uuid_v7_id!(
694 ProvenanceId
696);
697uuid_v7_id!(
698 OfflineMaintenanceOperationId
700);
701uuid_v7_id!(
702 ReplicationFenceOperationId
704);
705uuid_v7_id!(
706 ReplicationPromotionOperationId
708);
709uuid_v7_id!(
710 ContractMigrationOperationId
712);
713uuid_v7_id!(
714 ApplicationInstallationCampaignId
716);
717uuid_v7_id!(
718 ApplicationExportOperationId
720);
721
722#[derive(Clone, Copy, Debug, Eq, PartialEq)]
724pub enum TextIdError {
725 Empty,
727 TooLong {
729 maximum: usize,
731 actual: usize,
733 },
734 InvalidCharacter {
736 index: usize,
738 },
739}
740
741impl fmt::Display for TextIdError {
742 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
743 match self {
744 Self::Empty => formatter.write_str("identifier must not be empty"),
745 Self::TooLong { maximum, actual } => write!(
746 formatter,
747 "identifier is {actual} bytes but the maximum is {maximum} bytes"
748 ),
749 Self::InvalidCharacter { index } => {
750 write!(
751 formatter,
752 "identifier has an invalid character at byte {index}"
753 )
754 }
755 }
756 }
757}
758
759#[derive(Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
767pub struct ContractLineage(crate::OwnedCapacityTextV1);
768impl fmt::Debug for ContractLineage {
769 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
770 formatter
771 .debug_tuple("ContractLineage")
772 .field(&self.0)
773 .finish()
774 }
775}
776
777impl crate::InspectOwnedCapacityV1 for ContractLineage {
778 fn inspect_owned_capacity(
779 &self,
780 inspection: &mut crate::OwnedCapacityInspectionV1,
781 ) -> Result<(), crate::OwnedCapacityInspectionErrorV1> {
782 let Self(value) = self;
783 inspection.visit(value)
784 }
785}
786
787impl ContractLineage {
788 pub fn new(value: impl Into<String>) -> Result<Self, TextIdError> {
790 let value = value.into();
791 validate_text_id(&value, MAX_CONTRACT_LINEAGE_BYTES)?;
792 Ok(Self(crate::OwnedCapacityTextV1::new(value)))
793 }
794
795 #[must_use]
797 pub fn as_str(&self) -> &str {
798 &self.0
799 }
800
801 #[must_use]
803 pub fn as_bytes(&self) -> &[u8] {
804 self.0.as_bytes()
805 }
806}
807
808#[derive(Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
810pub struct QueryModuleName(String);
811crate::inspect_owned_capacity_tuple_v1!(QueryModuleName, String);
812
813impl QueryModuleName {
814 pub const MAX_BYTES: usize = 256;
816
817 pub fn new(value: impl Into<String>) -> Result<Self, TextIdError> {
819 let value = value.into();
820 if value.is_empty() {
821 return Err(TextIdError::Empty);
822 }
823 if value.len() > Self::MAX_BYTES {
824 return Err(TextIdError::TooLong {
825 maximum: Self::MAX_BYTES,
826 actual: value.len(),
827 });
828 }
829 if let Some(index) = value.bytes().enumerate().find_map(|(index, byte)| {
830 (!(byte == b'_' || byte.is_ascii_alphabetic() || index > 0 && byte.is_ascii_digit()))
831 .then_some(index)
832 }) {
833 return Err(TextIdError::InvalidCharacter { index });
834 }
835 Ok(Self(value))
836 }
837
838 #[must_use]
840 pub fn as_str(&self) -> &str {
841 &self.0
842 }
843}
844
845impl fmt::Debug for QueryModuleName {
846 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
847 formatter
848 .debug_tuple("QueryModuleName")
849 .field(&self.0)
850 .finish()
851 }
852}
853
854impl fmt::Display for QueryModuleName {
855 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
856 self.0.fmt(formatter)
857 }
858}
859
860#[derive(Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
862pub struct QueryOperationName(String);
863crate::inspect_owned_capacity_tuple_v1!(QueryOperationName, String);
864
865impl QueryOperationName {
866 pub const MAX_BYTES: usize = 256;
868
869 pub fn new(value: impl Into<String>) -> Result<Self, TextIdError> {
871 let value = value.into();
872 if value.is_empty() {
873 return Err(TextIdError::Empty);
874 }
875 if value.len() > Self::MAX_BYTES {
876 return Err(TextIdError::TooLong {
877 maximum: Self::MAX_BYTES,
878 actual: value.len(),
879 });
880 }
881 if let Some(index) = value.bytes().enumerate().find_map(|(index, byte)| {
882 (!(byte == b'_' || byte.is_ascii_alphabetic() || index > 0 && byte.is_ascii_digit()))
883 .then_some(index)
884 }) {
885 return Err(TextIdError::InvalidCharacter { index });
886 }
887 Ok(Self(value))
888 }
889
890 #[must_use]
892 pub fn as_str(&self) -> &str {
893 &self.0
894 }
895}
896
897impl fmt::Debug for QueryOperationName {
898 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
899 formatter
900 .debug_tuple("QueryOperationName")
901 .field(&self.0)
902 .finish()
903 }
904}
905
906impl fmt::Display for QueryOperationName {
907 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
908 self.0.fmt(formatter)
909 }
910}
911
912#[derive(Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
914pub struct RowPolicyName(String);
915crate::inspect_owned_capacity_tuple_v1!(RowPolicyName, String);
916
917impl RowPolicyName {
918 pub const MAX_BYTES: usize = 256;
920
921 pub fn new(value: impl Into<String>) -> Result<Self, TextIdError> {
923 let value = value.into();
924 if value.is_empty() {
925 return Err(TextIdError::Empty);
926 }
927 if value.len() > Self::MAX_BYTES {
928 return Err(TextIdError::TooLong {
929 maximum: Self::MAX_BYTES,
930 actual: value.len(),
931 });
932 }
933 if let Some(index) = value.bytes().enumerate().find_map(|(index, byte)| {
934 (!(byte == b'_' || byte.is_ascii_alphabetic() || index > 0 && byte.is_ascii_digit()))
935 .then_some(index)
936 }) {
937 return Err(TextIdError::InvalidCharacter { index });
938 }
939 Ok(Self(value))
940 }
941
942 #[must_use]
944 pub fn as_str(&self) -> &str {
945 &self.0
946 }
947}
948
949impl fmt::Debug for RowPolicyName {
950 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
951 formatter
952 .debug_tuple("RowPolicyName")
953 .field(&self.0)
954 .finish()
955 }
956}
957
958impl fmt::Display for RowPolicyName {
959 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
960 self.0.fmt(formatter)
961 }
962}
963
964#[derive(Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
966pub struct ReactiveOperationName(String);
967crate::inspect_owned_capacity_tuple_v1!(ReactiveOperationName, String);
968
969impl ReactiveOperationName {
970 pub const MAX_BYTES: usize = 256;
972
973 pub fn new(value: impl Into<String>) -> Result<Self, TextIdError> {
975 let value = value.into();
976 if value.is_empty() {
977 return Err(TextIdError::Empty);
978 }
979 if value.len() > Self::MAX_BYTES {
980 return Err(TextIdError::TooLong {
981 maximum: Self::MAX_BYTES,
982 actual: value.len(),
983 });
984 }
985 if let Some(index) = value.bytes().enumerate().find_map(|(index, byte)| {
986 (!(byte == b'_' || byte.is_ascii_alphabetic() || index > 0 && byte.is_ascii_digit()))
987 .then_some(index)
988 }) {
989 return Err(TextIdError::InvalidCharacter { index });
990 }
991 Ok(Self(value))
992 }
993
994 #[must_use]
996 pub fn as_str(&self) -> &str {
997 &self.0
998 }
999}
1000
1001impl fmt::Debug for ReactiveOperationName {
1002 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1003 formatter
1004 .debug_tuple("ReactiveOperationName")
1005 .field(&self.0)
1006 .finish()
1007 }
1008}
1009
1010impl fmt::Display for ReactiveOperationName {
1011 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1012 self.0.fmt(formatter)
1013 }
1014}
1015
1016impl fmt::Display for ContractLineage {
1017 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1018 self.0.fmt(formatter)
1019 }
1020}
1021
1022#[derive(Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
1024pub struct Environment(crate::OwnedCapacityTextV1);
1025impl fmt::Debug for Environment {
1026 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1027 formatter.debug_tuple("Environment").field(&self.0).finish()
1028 }
1029}
1030
1031impl crate::InspectOwnedCapacityV1 for Environment {
1032 fn inspect_owned_capacity(
1033 &self,
1034 inspection: &mut crate::OwnedCapacityInspectionV1,
1035 ) -> Result<(), crate::OwnedCapacityInspectionErrorV1> {
1036 let Self(value) = self;
1037 inspection.visit(value)
1038 }
1039}
1040
1041impl Environment {
1042 pub fn new(value: impl Into<String>) -> Result<Self, TextIdError> {
1044 let value = value.into();
1045 validate_text_id(&value, MAX_ENVIRONMENT_BYTES)?;
1046
1047 if let Some(index) = value
1048 .bytes()
1049 .position(|byte| !byte.is_ascii_alphanumeric() && !matches!(byte, b'.' | b'_' | b'-'))
1050 {
1051 return Err(TextIdError::InvalidCharacter { index });
1052 }
1053
1054 Ok(Self(crate::OwnedCapacityTextV1::new(value)))
1055 }
1056
1057 #[must_use]
1059 pub fn as_str(&self) -> &str {
1060 &self.0
1061 }
1062
1063 #[must_use]
1065 pub fn as_bytes(&self) -> &[u8] {
1066 self.0.as_bytes()
1067 }
1068}
1069
1070impl fmt::Display for Environment {
1071 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1072 self.0.fmt(formatter)
1073 }
1074}
1075
1076#[derive(Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
1078pub struct TenantId(String);
1079crate::inspect_owned_capacity_tuple_v1!(TenantId, String);
1080
1081impl TenantId {
1082 pub fn new(value: impl Into<String>) -> Result<Self, TextIdError> {
1084 let value = value.into();
1085 validate_text_id(&value, MAX_TENANT_ID_BYTES)?;
1086 Ok(Self(value))
1087 }
1088
1089 #[must_use]
1091 pub fn as_str(&self) -> &str {
1092 &self.0
1093 }
1094
1095 #[must_use]
1097 pub fn as_bytes(&self) -> &[u8] {
1098 self.0.as_bytes()
1099 }
1100}
1101
1102impl fmt::Debug for TenantId {
1103 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1104 formatter.write_str("TenantId([REDACTED])")
1105 }
1106}
1107
1108#[derive(Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
1110pub enum TenantScope {
1111 Global,
1113 Tenant(TenantId),
1115}
1116
1117impl TenantScope {
1118 #[must_use]
1120 pub fn to_canonical_bytes(&self) -> Vec<u8> {
1121 match self {
1122 Self::Global => vec![0],
1123 Self::Tenant(tenant_id) => {
1124 let tenant_bytes = tenant_id.as_bytes();
1125 let mut bytes = Vec::with_capacity(1 + 4 + tenant_bytes.len());
1126 bytes.push(1);
1127 bytes.extend_from_slice(&(tenant_bytes.len() as u32).to_be_bytes());
1128 bytes.extend_from_slice(tenant_bytes);
1129 bytes
1130 }
1131 }
1132 }
1133}
1134
1135impl fmt::Debug for TenantScope {
1136 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1137 match self {
1138 Self::Global => formatter.write_str("TenantScope::Global"),
1139 Self::Tenant(_) => formatter.write_str("TenantScope::Tenant([REDACTED])"),
1140 }
1141 }
1142}
1143
1144impl Error for TextIdError {}
1145
1146fn validate_text_id(value: &str, maximum: usize) -> Result<(), TextIdError> {
1147 if value.is_empty() {
1148 return Err(TextIdError::Empty);
1149 }
1150 if value.len() > maximum {
1151 return Err(TextIdError::TooLong {
1152 maximum,
1153 actual: value.len(),
1154 });
1155 }
1156 Ok(())
1157}
1158
1159#[derive(Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
1161pub struct ActorId(crate::OwnedCapacityTextV1);
1162impl crate::InspectOwnedCapacityV1 for ActorId {
1163 fn inspect_owned_capacity(
1164 &self,
1165 inspection: &mut crate::OwnedCapacityInspectionV1,
1166 ) -> Result<(), crate::OwnedCapacityInspectionErrorV1> {
1167 let Self(value) = self;
1168 inspection.visit(value)
1169 }
1170}
1171
1172impl ActorId {
1173 pub fn new(value: impl Into<String>) -> Result<Self, TextIdError> {
1175 let value = value.into();
1176 validate_text_id(&value, MAX_ACTOR_ID_BYTES)?;
1177 Ok(Self(crate::OwnedCapacityTextV1::new(value)))
1178 }
1179
1180 #[must_use]
1182 pub fn as_str(&self) -> &str {
1183 &self.0
1184 }
1185}
1186
1187impl fmt::Debug for ActorId {
1188 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1189 formatter.write_str("ActorId([REDACTED])")
1190 }
1191}
1192
1193#[derive(Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
1195pub struct IdempotencyKey(String);
1196crate::inspect_owned_capacity_tuple_v1!(IdempotencyKey, String);
1197
1198impl IdempotencyKey {
1199 pub fn new(value: impl Into<String>) -> Result<Self, TextIdError> {
1201 let value = value.into();
1202 validate_text_id(&value, MAX_IDEMPOTENCY_KEY_BYTES)?;
1203 Ok(Self(value))
1204 }
1205
1206 #[must_use]
1208 pub fn expose_secret(&self) -> &str {
1209 &self.0
1210 }
1211}
1212
1213impl fmt::Debug for IdempotencyKey {
1214 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1215 formatter.write_str("IdempotencyKey([REDACTED])")
1216 }
1217}
1218
1219pub const ENTITY_KEY_V1_PREFIX: [u8; 2] = [0x45, 0x01];
1221
1222pub const CONFLICT_KEY_V1_PREFIX: [u8; 2] = [0x43, 0x01];
1224
1225pub const PARTITION_KEY_V1_PREFIX: [u8; 2] = [0x50, 0x01];
1227
1228pub const INDEX_ENTRY_KEY_V1_PREFIX: [u8; 2] = [0x49, 0x01];
1230
1231const TYPED_KEY_ENVELOPE_BYTES: usize = 6;
1232const INDEX_ENTRY_KEY_MIN_BYTES: usize = TYPED_KEY_ENVELOPE_BYTES + 4 + TYPED_KEY_ENVELOPE_BYTES;
1233
1234#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1236pub enum KeyBytesError {
1237 TooLong {
1239 actual: usize,
1241 maximum: usize,
1243 },
1244 TooShort {
1246 actual: usize,
1248 minimum: usize,
1250 },
1251 WrongPurpose {
1253 expected: u8,
1255 actual: u8,
1257 },
1258 UnsupportedVersion {
1260 version: u8,
1262 },
1263 ZeroTypeIdentity,
1265}
1266
1267impl fmt::Display for KeyBytesError {
1268 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1269 match self {
1270 Self::TooLong { actual, maximum } => {
1271 write!(
1272 formatter,
1273 "key is {actual} bytes but the maximum is {maximum} bytes"
1274 )
1275 }
1276 Self::TooShort { actual, minimum } => {
1277 write!(
1278 formatter,
1279 "key is {actual} bytes but the minimum envelope is {minimum} bytes"
1280 )
1281 }
1282 Self::WrongPurpose { expected, actual } => write!(
1283 formatter,
1284 "key purpose byte {actual} does not match expected purpose {expected}"
1285 ),
1286 Self::UnsupportedVersion { version } => {
1287 write!(formatter, "unsupported key encoding version {version}")
1288 }
1289 Self::ZeroTypeIdentity => formatter.write_str("key type identity must be nonzero"),
1290 }
1291 }
1292}
1293
1294impl Error for KeyBytesError {}
1295
1296macro_rules! bounded_key {
1297 ($(#[$meta:meta])* $name:ident, $type_id:ident, $type_id_method:ident, $prefix:ident, $minimum:expr) => {
1298 $(#[$meta])*
1299 #[derive(Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
1300 pub struct $name(Vec<u8>);
1301 crate::inspect_owned_capacity_tuple_v1!($name, Vec<u8>);
1302
1303 impl $name {
1304 pub fn new(bytes: Vec<u8>) -> Result<Self, KeyBytesError> {
1308 if bytes.len() > MAX_KEY_BYTES {
1309 return Err(KeyBytesError::TooLong {
1310 actual: bytes.len(),
1311 maximum: MAX_KEY_BYTES,
1312 });
1313 }
1314 if bytes.len() < $minimum {
1315 return Err(KeyBytesError::TooShort {
1316 actual: bytes.len(),
1317 minimum: $minimum,
1318 });
1319 }
1320 if bytes[0] != $prefix[0] {
1321 return Err(KeyBytesError::WrongPurpose {
1322 expected: $prefix[0],
1323 actual: bytes[0],
1324 });
1325 }
1326 if bytes[1] != $prefix[1] {
1327 return Err(KeyBytesError::UnsupportedVersion { version: bytes[1] });
1328 }
1329 if bytes[2..TYPED_KEY_ENVELOPE_BYTES] == [0; 4] {
1330 return Err(KeyBytesError::ZeroTypeIdentity);
1331 }
1332 Ok(Self(bytes))
1333 }
1334
1335 pub fn from_bytes(bytes: Vec<u8>) -> Result<Self, KeyBytesError> {
1337 Self::new(bytes)
1338 }
1339
1340 #[must_use]
1342 pub fn $type_id_method(&self) -> $type_id {
1343 let value = u32::from_be_bytes([
1344 self.0[2], self.0[3], self.0[4], self.0[5],
1345 ]);
1346 $type_id::new(value).expect("validated key contains a nonzero type identity")
1347 }
1348
1349 pub(crate) fn from_validated_bytes(bytes: Vec<u8>) -> Self {
1350 debug_assert!(bytes.len() >= $minimum);
1351 debug_assert!(bytes.len() <= MAX_KEY_BYTES);
1352 debug_assert_eq!(bytes[..2], $prefix);
1353 Self(bytes)
1354 }
1355
1356 #[must_use]
1358 pub fn as_bytes(&self) -> &[u8] {
1359 &self.0
1360 }
1361
1362 #[must_use]
1364 pub fn into_bytes(self) -> Vec<u8> {
1365 self.0
1366 }
1367 }
1368
1369 impl fmt::Debug for $name {
1370 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1371 formatter
1372 .debug_struct(stringify!($name))
1373 .field("bytes", &"[REDACTED]")
1374 .field("length", &self.0.len())
1375 .finish()
1376 }
1377 }
1378 };
1379}
1380
1381bounded_key!(
1382 EntityKey,
1384 EntityTypeId,
1385 entity_type_id,
1386 ENTITY_KEY_V1_PREFIX,
1387 TYPED_KEY_ENVELOPE_BYTES
1388);
1389bounded_key!(
1390 ConflictKey,
1392 AggregateTypeId,
1393 aggregate_type_id,
1394 CONFLICT_KEY_V1_PREFIX,
1395 TYPED_KEY_ENVELOPE_BYTES
1396);
1397bounded_key!(
1398 PartitionKey,
1400 AggregateTypeId,
1401 aggregate_type_id,
1402 PARTITION_KEY_V1_PREFIX,
1403 TYPED_KEY_ENVELOPE_BYTES
1404);
1405bounded_key!(
1406 IndexEntryKey,
1408 IndexId,
1409 index_id,
1410 INDEX_ENTRY_KEY_V1_PREFIX,
1411 INDEX_ENTRY_KEY_MIN_BYTES
1412);
1413
1414pub struct DigestKey([u8; 32]);
1416
1417impl DigestKey {
1418 #[must_use]
1420 pub const fn from_bytes(bytes: [u8; 32]) -> Self {
1421 Self(bytes)
1422 }
1423
1424 #[must_use]
1426 pub const fn expose_secret(&self) -> &[u8; 32] {
1427 &self.0
1428 }
1429}
1430
1431impl fmt::Debug for DigestKey {
1432 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1433 formatter.write_str("DigestKey([REDACTED])")
1434 }
1435}
1436
1437impl crate::InspectOwnedCapacityV1 for TenantScope {
1439 fn inspect_owned_capacity(
1440 &self,
1441 inspection: &mut crate::OwnedCapacityInspectionV1,
1442 ) -> Result<(), crate::OwnedCapacityInspectionErrorV1> {
1443 match self {
1444 Self::Global => {}
1445 Self::Tenant(field_0) => {
1446 inspection.visit(field_0)?;
1447 }
1448 }
1449 Ok(())
1450 }
1451}
1452
1453#[cfg(test)]
1454mod tests {
1455 use super::*;
1456 use proptest::prelude::*;
1457
1458 const UUID_V7: [u8; 16] = [
1459 0x01, 0x8f, 0x00, 0x00, 0x00, 0x00, 0x70, 0x01, 0x80, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
1460 0x03,
1461 ];
1462
1463 #[test]
1464 fn commit_sequence_next_is_checked() {
1465 let forty_one = CommitSequence::new(41).expect("nonzero sequence");
1466 assert_eq!(forty_one.checked_next(), CommitSequence::new(42));
1467 assert_eq!(
1468 CommitSequence::new(u64::MAX)
1469 .expect("nonzero sequence")
1470 .checked_next(),
1471 None
1472 );
1473 }
1474
1475 #[test]
1476 fn event_id_has_stable_big_endian_ordering() {
1477 let first = EventId::new(
1478 CommitSequence::new(0x0102_0304_0506_0708).expect("nonzero sequence"),
1479 0x090a_0b0c,
1480 );
1481 let next_event = EventId::new(first.commit_sequence(), 0x090a_0b0d);
1482 let next_commit = EventId::new(
1483 CommitSequence::new(0x0102_0304_0506_0709).expect("nonzero sequence"),
1484 0,
1485 );
1486
1487 assert_eq!(first.to_be_bytes(), [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]);
1488 assert_eq!(EventId::from_be_bytes(first.to_be_bytes()), Some(first));
1489 assert_eq!(EventId::from_be_bytes([0; 12]), None);
1490 assert!(first < next_event);
1491 assert!(next_event < next_commit);
1492 assert_eq!(first.event_ordinal(), 0x090a_0b0c);
1493 }
1494
1495 #[test]
1496 fn assigned_and_compiler_ids_are_one_based_and_exhaustion_is_checked() {
1497 macro_rules! assert_u64_id {
1498 ($type:ty) => {{
1499 assert_eq!(<$type>::new(0), None);
1500 assert_eq!(<$type>::first().get(), 1);
1501 assert_eq!(<$type>::first().checked_next().map(<$type>::get), Some(2));
1502 assert_eq!(
1503 <$type>::new(u64::MAX).expect("nonzero").checked_next(),
1504 None
1505 );
1506 assert_eq!(<$type>::try_from(0), Err(ZeroNumericIdError));
1507 }};
1508 }
1509
1510 macro_rules! assert_u32_id {
1511 ($type:ty) => {{
1512 assert_eq!(<$type>::new(0), None);
1513 assert_eq!(<$type>::first().get(), 1);
1514 assert_eq!(<$type>::first().checked_next().map(<$type>::get), Some(2));
1515 assert_eq!(
1516 <$type>::new(u32::MAX).expect("nonzero").checked_next(),
1517 None
1518 );
1519 assert_eq!(<$type>::try_from(0), Err(ZeroNumericIdError));
1520 }};
1521 }
1522
1523 assert_u64_id!(CommitSequence);
1524 assert_u64_id!(AdministrationSequence);
1525 assert_u64_id!(EntityVersion);
1526 assert_u64_id!(IndexEpoch);
1527 assert_u32_id!(EntityTypeId);
1528 assert_u32_id!(FieldId);
1529 assert_u32_id!(CommandId);
1530 assert_u32_id!(OutcomeId);
1531 assert_u32_id!(ProjectionId);
1532 assert_u32_id!(EventTypeId);
1533 assert_u32_id!(IndexId);
1534 assert_u32_id!(EnumTypeId);
1535 assert_u32_id!(EnumVariantId);
1536 assert_u32_id!(AggregateTypeId);
1537 assert_u32_id!(InvariantId);
1538
1539 assert_eq!(ContractVersion::new(0), None);
1540 assert_eq!(DigestKeyId::new(0), None);
1541 assert_eq!(ContractVersion::try_from(0), Err(ZeroNumericIdError));
1542 assert_eq!(DigestKeyId::try_from(0), Err(ZeroNumericIdError));
1543 }
1544
1545 #[test]
1546 fn uuid_v7_identifiers_validate_variant_and_version() {
1547 let mut invalid_version = UUID_V7;
1548 invalid_version[6] = 0x40;
1549 let mut invalid_variant = UUID_V7;
1550 invalid_variant[8] = 0xc0;
1551
1552 macro_rules! assert_decoder {
1553 ($type:ty) => {{
1554 let value = <$type>::from_bytes(UUID_V7).expect("valid UUIDv7");
1555 assert_eq!(value.as_bytes(), &UUID_V7);
1556 assert_eq!(
1557 <$type>::from_bytes(invalid_version),
1558 Err(UuidV7Error::InvalidVersion)
1559 );
1560 assert_eq!(
1561 <$type>::from_bytes(invalid_variant),
1562 Err(UuidV7Error::InvalidVariant)
1563 );
1564 }};
1565 }
1566
1567 assert_decoder!(RequestId);
1568 assert_decoder!(AgentSessionId);
1569 assert_decoder!(IncidentId);
1570 assert_decoder!(DatabaseId);
1571 assert_decoder!(CapabilityId);
1572 assert_decoder!(ProvenanceId);
1573
1574 let request = RequestId::from_bytes(UUID_V7).expect("valid UUIDv7");
1575 assert_eq!(request.to_string(), "018f0000-0000-7001-8002-000000000003");
1576 }
1577
1578 #[test]
1579 fn uuid_v7_pure_construction_matches_accepted_goldens_for_every_id_type() {
1580 const RANDOM: [u8; 10] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
1581 const PRIMARY: [u8; 16] = [
1582 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0x70, 0x01, 0x82, 0x03, 0x04, 0x05, 0x06, 0x07,
1583 0x08, 0x09,
1584 ];
1585 const ZERO: [u8; 16] = [0, 0, 0, 0, 0, 0, 0x70, 0, 0x80, 0, 0, 0, 0, 0, 0, 0];
1586 const MAXIMUM: [u8; 16] = [
1587 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xff, 0xbf, 0xff, 0xff, 0xff, 0xff, 0xff,
1588 0xff, 0xff,
1589 ];
1590
1591 macro_rules! assert_constructor {
1592 ($type:ty) => {{
1593 let primary = <$type>::from_unix_milliseconds_and_random(0x0123_4567_89ab, RANDOM)
1594 .expect("48-bit timestamp");
1595 assert_eq!(primary.into_bytes(), PRIMARY);
1596 assert_eq!(primary.to_string(), "01234567-89ab-7001-8203-040506070809");
1597 assert_eq!(
1598 <$type>::from_unix_milliseconds_and_random(0, [0; 10])
1599 .expect("lower bound")
1600 .into_bytes(),
1601 ZERO
1602 );
1603 assert_eq!(
1604 <$type>::from_unix_milliseconds_and_random(
1605 UUID_V7_MAX_UNIX_MILLISECONDS,
1606 [0xff; 10],
1607 )
1608 .expect("upper bound")
1609 .into_bytes(),
1610 MAXIMUM
1611 );
1612 assert_eq!(
1613 <$type>::from_unix_milliseconds_and_random(0x1_0000_0000_0000, [0xa5; 10]),
1614 Err(UuidV7ConstructionError::UnixMillisecondsOutOfRange)
1615 );
1616 }};
1617 }
1618
1619 assert_constructor!(RequestId);
1620 assert_constructor!(AgentSessionId);
1621 assert_constructor!(IncidentId);
1622 assert_constructor!(DatabaseId);
1623 assert_constructor!(CapabilityId);
1624 assert_constructor!(ProvenanceId);
1625
1626 let error = UuidV7ConstructionError::UnixMillisecondsOutOfRange;
1627 assert_eq!(
1628 error.to_string(),
1629 "UUIDv7 Unix-millisecond timestamp is out of range"
1630 );
1631 assert!(!format!("{error:?}").contains("a5"));
1632 }
1633
1634 proptest! {
1635 #[test]
1636 fn uuid_v7_construction_preserves_fields_and_round_trips(
1637 unix_milliseconds in 0u64..=UUID_V7_MAX_UNIX_MILLISECONDS,
1638 random in any::<[u8; 10]>(),
1639 ) {
1640 let id = RequestId::from_unix_milliseconds_and_random(unix_milliseconds, random)
1641 .expect("generated timestamp is in range");
1642 let bytes = id.into_bytes();
1643 let timestamp = unix_milliseconds.to_be_bytes();
1644 let canonical_text = id.to_string();
1645
1646 prop_assert_eq!(&bytes[..6], ×tamp[2..]);
1647 prop_assert_eq!(bytes[6] >> 4, 7);
1648 prop_assert_eq!(bytes[6] & 0x0f, random[0] & 0x0f);
1649 prop_assert_eq!(bytes[7], random[1]);
1650 prop_assert_eq!(bytes[8] >> 6, 0b10);
1651 prop_assert_eq!(bytes[8] & 0x3f, random[2] & 0x3f);
1652 prop_assert_eq!(&bytes[9..], &random[3..]);
1653 prop_assert_eq!(canonical_text.len(), 36);
1654 let canonical_text_is_valid =
1655 canonical_text.bytes().enumerate().all(|(index, byte)| {
1656 if matches!(index, 8 | 13 | 18 | 23) {
1657 byte == b'-'
1658 } else {
1659 byte.is_ascii_digit() || matches!(byte, b'a'..=b'f')
1660 }
1661 });
1662 prop_assert!(canonical_text_is_valid);
1663
1664 macro_rules! assert_id {
1665 ($type:ty) => {{
1666 let typed = <$type>::from_unix_milliseconds_and_random(
1667 unix_milliseconds,
1668 random,
1669 )
1670 .expect("generated timestamp is in range");
1671 prop_assert_eq!(typed.into_bytes(), bytes);
1672 prop_assert_eq!(<$type>::from_bytes(bytes), Ok(typed));
1673 prop_assert_eq!(typed.to_string(), canonical_text.as_str());
1674 }};
1675 }
1676
1677 assert_id!(RequestId);
1678 assert_id!(AgentSessionId);
1679 assert_id!(IncidentId);
1680 assert_id!(DatabaseId);
1681 assert_id!(CapabilityId);
1682 assert_id!(ProvenanceId);
1683 }
1684
1685 #[test]
1686 fn uuid_v7_network_order_tracks_timestamp_for_equal_randomness(
1687 left in 0u64..=UUID_V7_MAX_UNIX_MILLISECONDS,
1688 right in 0u64..=UUID_V7_MAX_UNIX_MILLISECONDS,
1689 random in any::<[u8; 10]>(),
1690 ) {
1691 let left_id = RequestId::from_unix_milliseconds_and_random(left, random)
1692 .expect("generated timestamp is in range");
1693 let right_id = RequestId::from_unix_milliseconds_and_random(right, random)
1694 .expect("generated timestamp is in range");
1695 prop_assert_eq!(left.cmp(&right), left_id.cmp(&right_id));
1696
1697 macro_rules! assert_order {
1698 ($type:ty) => {{
1699 let left_id = <$type>::from_unix_milliseconds_and_random(left, random)
1700 .expect("generated timestamp is in range");
1701 let right_id = <$type>::from_unix_milliseconds_and_random(right, random)
1702 .expect("generated timestamp is in range");
1703 prop_assert_eq!(left.cmp(&right), left_id.cmp(&right_id));
1704 }};
1705 }
1706
1707 assert_order!(AgentSessionId);
1708 assert_order!(IncidentId);
1709 assert_order!(DatabaseId);
1710 assert_order!(CapabilityId);
1711 assert_order!(ProvenanceId);
1712 }
1713 }
1714
1715 #[test]
1716 fn uuid_identifier_newtypes_are_distinct() {
1717 use std::any::TypeId;
1718
1719 let types = [
1720 TypeId::of::<RequestId>(),
1721 TypeId::of::<AgentSessionId>(),
1722 TypeId::of::<IncidentId>(),
1723 TypeId::of::<DatabaseId>(),
1724 TypeId::of::<CapabilityId>(),
1725 TypeId::of::<ProvenanceId>(),
1726 ];
1727 for (index, current) in types.iter().enumerate() {
1728 assert!(!types[..index].contains(current));
1729 }
1730 }
1731
1732 #[test]
1733 fn bounded_names_preserve_exact_text_and_validate_environment_alphabet() {
1734 let lineage = ContractLineage::new("Inventory.v2").expect("valid lineage");
1735 let environment = Environment::new("Prod_US-2.1").expect("valid environment");
1736 let tenant = TenantId::new("tenant-\u{00e9}").expect("valid tenant");
1737
1738 assert_eq!(lineage.as_str(), "Inventory.v2");
1739 assert_eq!(environment.as_str(), "Prod_US-2.1");
1740 assert_eq!(tenant.as_str(), "tenant-\u{00e9}");
1741 assert_eq!(
1742 Environment::new("prod/us"),
1743 Err(TextIdError::InvalidCharacter { index: 4 })
1744 );
1745 assert_eq!(
1746 Environment::new("prod \u{00e9}"),
1747 Err(TextIdError::InvalidCharacter { index: 4 })
1748 );
1749 assert_eq!(Environment::new(""), Err(TextIdError::Empty));
1750 }
1751
1752 #[test]
1753 fn tenant_debug_output_is_redacted() {
1754 let tenant = TenantId::new("customer-secret").expect("valid tenant");
1755 let scope = TenantScope::Tenant(tenant.clone());
1756
1757 let output = format!("{tenant:?} {scope:?}");
1758 assert!(!output.contains("customer-secret"));
1759 assert_eq!(format!("{:?}", TenantScope::Global), "TenantScope::Global");
1760 }
1761
1762 #[test]
1763 fn tenant_scope_identity_encoding_is_stable() {
1764 assert_eq!(TenantScope::Global.to_canonical_bytes(), [0]);
1765
1766 let scope = TenantScope::Tenant(TenantId::new("tenant-é").expect("valid tenant"));
1767 assert_eq!(
1768 scope.to_canonical_bytes(),
1769 [&[1, 0, 0, 0, 9][..], "tenant-é".as_bytes(),].concat()
1770 );
1771 }
1772
1773 #[test]
1774 fn sensitive_identifier_debug_output_is_redacted() {
1775 let actor = ActorId::new("principal-secret").expect("valid actor ID");
1776 let idempotency = IdempotencyKey::new("caller-secret").expect("valid key");
1777 let entity_key = EntityKey::from_bytes(
1778 [
1779 ENTITY_KEY_V1_PREFIX.as_slice(),
1780 &EntityTypeId::new(7).expect("nonzero").to_be_bytes(),
1781 b"business-key",
1782 ]
1783 .concat(),
1784 )
1785 .expect("valid key");
1786 let partition_key = PartitionKey::from_bytes(
1787 [
1788 PARTITION_KEY_V1_PREFIX.as_slice(),
1789 &AggregateTypeId::new(8).expect("nonzero").to_be_bytes(),
1790 b"business-partition",
1791 ]
1792 .concat(),
1793 )
1794 .expect("valid partition envelope");
1795 let index_key = IndexEntryKey::from_bytes(
1796 [
1797 INDEX_ENTRY_KEY_V1_PREFIX.as_slice(),
1798 &IndexId::new(9).expect("nonzero").to_be_bytes(),
1799 &[0, 0, 0, 6],
1800 ENTITY_KEY_V1_PREFIX.as_slice(),
1801 &EntityTypeId::new(7).expect("nonzero").to_be_bytes(),
1802 ]
1803 .concat(),
1804 )
1805 .expect("structurally bounded index envelope");
1806 let digest_key = DigestKey::from_bytes([0x5a; 32]);
1807
1808 let output = format!(
1809 "{actor:?} {idempotency:?} {entity_key:?} {partition_key:?} {index_key:?} {digest_key:?}"
1810 );
1811 assert!(!output.contains("principal-secret"));
1812 assert!(!output.contains("caller-secret"));
1813 assert!(!output.contains("business-key"));
1814 assert!(!output.contains("business-partition"));
1815 assert!(!output.contains("5a"));
1816 }
1817
1818 #[test]
1819 fn bounded_identifiers_reject_empty_and_oversized_values() {
1820 assert_eq!(ActorId::new(""), Err(TextIdError::Empty));
1821 assert!(matches!(
1822 IdempotencyKey::new("x".repeat(MAX_IDEMPOTENCY_KEY_BYTES + 1)),
1823 Err(TextIdError::TooLong { .. })
1824 ));
1825 assert!(matches!(
1826 ContractLineage::new("x".repeat(MAX_CONTRACT_LINEAGE_BYTES + 1)),
1827 Err(TextIdError::TooLong { .. })
1828 ));
1829 assert!(matches!(
1830 Environment::new("x".repeat(MAX_ENVIRONMENT_BYTES + 1)),
1831 Err(TextIdError::TooLong { .. })
1832 ));
1833 assert!(matches!(
1834 TenantId::new("x".repeat(MAX_TENANT_ID_BYTES + 1)),
1835 Err(TextIdError::TooLong { .. })
1836 ));
1837 assert_eq!(
1838 ConflictKey::from_bytes(vec![0; MAX_KEY_BYTES + 1]),
1839 Err(KeyBytesError::TooLong {
1840 actual: MAX_KEY_BYTES + 1,
1841 maximum: MAX_KEY_BYTES,
1842 })
1843 );
1844 }
1845
1846 #[test]
1847 fn typed_key_envelopes_reject_malformed_or_cross_purpose_bytes() {
1848 assert_eq!(
1849 EntityKey::from_bytes(Vec::new()),
1850 Err(KeyBytesError::TooShort {
1851 actual: 0,
1852 minimum: TYPED_KEY_ENVELOPE_BYTES,
1853 })
1854 );
1855 assert_eq!(
1856 EntityKey::from_bytes(vec![0x45, 0x02, 0, 0, 0, 1]),
1857 Err(KeyBytesError::UnsupportedVersion { version: 2 })
1858 );
1859 assert_eq!(
1860 EntityKey::from_bytes(vec![0x45, 0x01, 0, 0, 0, 0]),
1861 Err(KeyBytesError::ZeroTypeIdentity)
1862 );
1863 assert_eq!(
1864 EntityKey::from_bytes(vec![0x43, 0x01, 0, 0, 0, 1]),
1865 Err(KeyBytesError::WrongPurpose {
1866 expected: 0x45,
1867 actual: 0x43,
1868 })
1869 );
1870 assert_eq!(
1871 IndexEntryKey::from_bytes(vec![0x49, 0x01, 0, 0, 0, 1]),
1872 Err(KeyBytesError::TooShort {
1873 actual: TYPED_KEY_ENVELOPE_BYTES,
1874 minimum: INDEX_ENTRY_KEY_MIN_BYTES,
1875 })
1876 );
1877
1878 let entity =
1879 EntityKey::from_bytes(vec![0x45, 0x01, 1, 2, 3, 4]).expect("valid entity envelope");
1880 let conflict =
1881 ConflictKey::from_bytes(vec![0x43, 0x01, 5, 6, 7, 8]).expect("valid conflict envelope");
1882 let partition = PartitionKey::from_bytes(vec![0x50, 0x01, 9, 10, 11, 12])
1883 .expect("valid partition envelope");
1884 let index = IndexEntryKey::from_bytes(vec![
1885 0x49, 0x01, 13, 14, 15, 16, 0, 0, 0, 6, 0x45, 0x01, 0, 0, 0, 1,
1886 ])
1887 .expect("structurally bounded index envelope");
1888 assert_eq!(
1889 entity.entity_type_id(),
1890 EntityTypeId::new(0x0102_0304).expect("nonzero")
1891 );
1892 assert_eq!(
1893 conflict.aggregate_type_id(),
1894 AggregateTypeId::new(0x0506_0708).expect("nonzero")
1895 );
1896 assert_eq!(
1897 partition.aggregate_type_id(),
1898 AggregateTypeId::new(0x090a_0b0c).expect("nonzero")
1899 );
1900 assert_eq!(
1901 index.index_id(),
1902 IndexId::new(0x0d0e_0f10).expect("nonzero")
1903 );
1904 }
1905}