Skip to main content

riffdb_types/
ids.rs

1//! Stable identifiers and opaque key material.
2
3use 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/// A numeric identifier was reconstructed from its reserved zero value.
13#[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            /// Creates an identifier from its numeric representation.
33            #[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            /// Returns the numeric representation.
42            #[must_use]
43            pub const fn get(self) -> $inner {
44                self.0.get()
45            }
46
47            /// Returns the canonical big-endian representation.
48            #[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            /// Returns the first value in this one-based allocation space.
82            #[must_use]
83            pub const fn first() -> Self {
84                Self(<$nonzero>::MIN)
85            }
86
87            /// Returns the next value, or `None` when the allocation space is exhausted.
88            #[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    /// An application contract version.
101    ContractVersion,
102    u64,
103    NonZeroU64
104);
105nonzero_id!(
106    /// An application-owned immutable query-module version.
107    QueryModuleVersion,
108    u64,
109    NonZeroU64
110);
111allocatable_nonzero_id!(
112    /// A single-node application commit sequence.
113    CommitSequence,
114    u64,
115    NonZeroU64
116);
117allocatable_nonzero_id!(
118    /// The monotonically increasing version of an entity record.
119    EntityVersion,
120    u64,
121    NonZeroU64
122);
123
124allocatable_nonzero_id!(
125    /// A compiler-assigned stable entity type identifier.
126    EntityTypeId,
127    u32,
128    NonZeroU32
129);
130allocatable_nonzero_id!(
131    /// A compiler-assigned stable field identifier.
132    FieldId,
133    u32,
134    NonZeroU32
135);
136allocatable_nonzero_id!(
137    /// A compiler-assigned stable command identifier.
138    CommandId,
139    u32,
140    NonZeroU32
141);
142allocatable_nonzero_id!(
143    /// A compiler-assigned stable outcome identifier.
144    OutcomeId,
145    u32,
146    NonZeroU32
147);
148allocatable_nonzero_id!(
149    /// A compiler-assigned stable projection identifier.
150    ProjectionId,
151    u32,
152    NonZeroU32
153);
154allocatable_nonzero_id!(
155    /// A compiler-assigned stable event type identifier.
156    EventTypeId,
157    u32,
158    NonZeroU32
159);
160allocatable_nonzero_id!(
161    /// A compiler-assigned stable index identifier.
162    IndexId,
163    u32,
164    NonZeroU32
165);
166allocatable_nonzero_id!(
167    /// A compiler-assigned stable enum type identifier.
168    EnumTypeId,
169    u32,
170    NonZeroU32
171);
172allocatable_nonzero_id!(
173    /// A compiler-assigned stable vector field identifier.
174    VectorFieldId,
175    u32,
176    NonZeroU32
177);
178allocatable_nonzero_id!(
179    /// A compiler-assigned stable enum variant identifier.
180    EnumVariantId,
181    u32,
182    NonZeroU32
183);
184nonzero_id!(
185    /// The identifier of a keyed-digest configuration.
186    DigestKeyId,
187    u32,
188    NonZeroU32
189);
190allocatable_nonzero_id!(
191    /// A compiler-assigned stable aggregate type identifier.
192    AggregateTypeId,
193    u32,
194    NonZeroU32
195);
196allocatable_nonzero_id!(
197    /// A compiler-assigned stable invariant identifier.
198    InvariantId,
199    u32,
200    NonZeroU32
201);
202allocatable_nonzero_id!(
203    /// The epoch of a derived index representation.
204    IndexEpoch,
205    u64,
206    NonZeroU64
207);
208
209/// Empty or assigned state of one index-range epoch bucket.
210#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
211pub enum IndexEpochPosition {
212    /// No mutation has affected this exact prefix bucket.
213    BeforeFirst,
214    /// The bucket has this nonzero assigned epoch.
215    Value(IndexEpoch),
216}
217
218allocatable_nonzero_id!(
219    /// A sequence assigned to an administrative change.
220    AdministrationSequence,
221    u64,
222    NonZeroU64
223);
224
225/// The contiguous authoritative application-commit frontier.
226#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
227pub enum FrontierPosition {
228    /// No application commit has been applied.
229    BeforeFirst,
230    /// Every application commit through this sequence has been applied.
231    AppliedThrough(CommitSequence),
232}
233
234/// The durable identity of one event within a committed command.
235#[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    /// Creates an event identifier from its commit sequence and zero-based ordinal.
243    #[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    /// Decodes the canonical big-endian representation.
252    #[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    /// Returns the commit containing the event.
265    #[must_use]
266    pub const fn commit_sequence(self) -> CommitSequence {
267        self.commit_sequence
268    }
269
270    /// Returns the event's zero-based ordinal within its commit.
271    #[must_use]
272    pub const fn event_ordinal(self) -> u32 {
273        self.event_ordinal
274    }
275
276    /// Returns the canonical big-endian representation.
277    #[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            /// Creates a hash identifier from its 32 digest bytes.
307            #[must_use]
308            pub const fn from_bytes(bytes: [u8; 32]) -> Self {
309                Self(bytes)
310            }
311
312            /// Borrows the digest bytes.
313            #[must_use]
314            pub const fn as_bytes(&self) -> &[u8; 32] {
315                &self.0
316            }
317
318            /// Returns the digest bytes.
319            #[must_use]
320            pub const fn into_bytes(self) -> [u8; 32] {
321                self.0
322            }
323        }
324    };
325}
326
327hash_id!(
328    /// The hash of one canonical value document.
329    CanonicalValueHash
330);
331hash_id!(
332    /// The hash of an executable command plan.
333    PlanHash
334);
335hash_id!(
336    /// The hash of one validated projection plan.
337    ProjectionPlanHash
338);
339hash_id!(
340    /// The hash of one canonical sealed projection-provider descriptor.
341    ProjectionProviderDescriptorHash
342);
343hash_id!(
344    /// The hash of one complete canonical columnar definition-semantics document.
345    ColumnarDefinitionSemanticsHashV1
346);
347hash_id!(
348    /// The hash of one complete schema-bound columnar projection specification.
349    ColumnarProjectionSpecHashV1
350);
351hash_id!(
352    /// The hash of one closed RiffQL query access program.
353    QueryPlanHash
354);
355hash_id!(
356    /// The digest of one ordered bounded partition-set epoch observation.
357    PartitionSetObservationHash
358);
359hash_id!(
360    /// The identity of one canonical immutable query module.
361    QueryModuleHash
362);
363hash_id!(
364    /// The hash of one exact reactive source document.
365    ReactiveSourceHash
366);
367hash_id!(
368    /// The identity of one canonical reactive operation plan.
369    ReactiveOperationHash
370);
371hash_id!(
372    /// The identity of one canonical immutable reactive module.
373    ReactiveModuleHash
374);
375hash_id!(
376    /// The identity of one exact durable event consumer.
377    EventConsumerIdentityHash
378);
379hash_id!(
380    /// Stable identity of one bounded scheduled workflow attempt.
381    ScheduledAttemptHash
382);
383hash_id!(
384    /// The identity of one exact application installation plan.
385    ApplicationInstallationPlanHash
386);
387hash_id!(
388    /// The identity of one terminal application installation receipt.
389    ApplicationInstallationReceiptHash
390);
391hash_id!(
392    /// The identity of one exact adapter conformance manifest.
393    AdapterConformanceManifestHash
394);
395hash_id!(
396    /// The identity of one canonical application manifest.
397    ApplicationManifestHash
398);
399hash_id!(
400    /// The identity of one author-owned symbolic application source manifest.
401    ApplicationSourceHash
402);
403hash_id!(
404    /// The identity of one compiler-owned exact application lock.
405    ApplicationLockHash
406);
407hash_id!(
408    /// The content hash of one compiler-generated application artifact.
409    GeneratedArtifactHash
410);
411hash_id!(
412    /// The identity of one canonical compiled application role.
413    ApplicationRoleHash
414);
415hash_id!(
416    /// The identity of one exact, tenant-unbound application role definition.
417    ApplicationRoleDefinitionHash
418);
419hash_id!(
420    /// The hash of one exact RiffQL source document.
421    QuerySourceHash
422);
423hash_id!(
424    /// The hash of one canonical name-addressed RiffQL parameter set.
425    QueryParameterHash
426);
427hash_id!(
428    /// The hash of a contract bundle's ordered semantic plan set.
429    ContractPlanRootHash
430);
431hash_id!(
432    /// The hash of canonical contract source.
433    SourceHash
434);
435hash_id!(
436    /// The hash of one exact migration source document.
437    MigrationSourceHash
438);
439hash_id!(
440    /// The hash of an immutable contract bundle.
441    ContractBundleHash
442);
443hash_id!(
444    /// The hash of one canonical compiled migration bundle.
445    MigrationBundleHash
446);
447hash_id!(
448    /// The hash of canonical command input.
449    CanonicalInputHash
450);
451hash_id!(
452    /// The hash of a canonical durable event.
453    EventHash
454);
455hash_id!(
456    /// The hash of a complete canonical entity-record preimage.
457    EntityRecordHash
458);
459hash_id!(
460    /// The hash of one canonical committed entity transition.
461    EntityTransitionHash
462);
463hash_id!(
464    /// The hash of a canonical entity key.
465    EntityKeyHash
466);
467hash_id!(
468    /// The hash of a canonical conflict key.
469    ConflictKeyHash
470);
471hash_id!(
472    /// The hash of a canonical logical partition key.
473    PartitionKeyHash
474);
475hash_id!(
476    /// The hash of a public or durable schema.
477    SchemaHash
478);
479hash_id!(
480    /// The stable semantic-input hash of one offline maintenance operation.
481    OfflineMaintenanceInputHash
482);
483hash_id!(
484    /// The stable semantic-input hash of one contract migration operation.
485    ContractMigrationInputHash
486);
487hash_id!(
488    /// The chained hash of one durable contract migration journal state.
489    ContractMigrationJournalHash
490);
491hash_id!(
492    /// The digest of one complete staged contract migration validation.
493    ContractMigrationValidationDigest
494);
495hash_id!(
496    /// The hash of one canonical symbolic application-export page.
497    ApplicationExportPageHash
498);
499hash_id!(
500    /// The domain-separated genesis or ordered prefix of an export page ledger.
501    ApplicationExportLedgerHash
502);
503hash_id!(
504    /// The identity of one canonical symbolic application-export manifest.
505    ApplicationExportManifestHash
506);
507hash_id!(
508    /// The identity of one terminal symbolic application-export receipt.
509    ApplicationExportReceiptHash
510);
511hash_id!(
512    /// The identity of one adapter-owned compiled reimport mapping manifest.
513    ApplicationPortabilityManifestHash
514);
515hash_id!(
516    /// The identity of one terminal application reimport reconciliation receipt.
517    ApplicationReimportReceiptHash
518);
519
520/// A validation failure for a UUIDv7 identifier.
521#[derive(Clone, Copy, Debug, Eq, PartialEq)]
522pub enum UuidV7Error {
523    /// The UUID does not use the RFC 4122/RFC 9562 variant.
524    InvalidVariant,
525    /// The UUID does not use version 7.
526    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/// A safe failure while assembling a UUIDv7 value from explicit inputs.
541#[derive(Clone, Copy, Debug, Eq, PartialEq)]
542pub enum UuidV7ConstructionError {
543    /// The Unix-millisecond timestamp cannot fit UUIDv7's 48-bit timestamp field.
544    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            /// Assembles a UUIDv7 from an explicit 48-bit Unix-millisecond value and random bytes.
628            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            /// Validates and creates an identifier from network-order UUID bytes.
639            pub fn from_bytes(bytes: [u8; 16]) -> Result<Self, UuidV7Error> {
640                validate_uuid_v7(&bytes)?;
641                Ok(Self(bytes))
642            }
643
644            /// Borrows the network-order UUID bytes.
645            #[must_use]
646            pub const fn as_bytes(&self) -> &[u8; 16] {
647                &self.0
648            }
649
650            /// Returns the network-order UUID bytes.
651            #[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    /// The identifier of one transport submission.
675    RequestId
676);
677uuid_v7_id!(
678    /// The identifier of an agent session.
679    AgentSessionId
680);
681uuid_v7_id!(
682    /// A public correlation identifier for an internal failure.
683    IncidentId
684);
685uuid_v7_id!(
686    /// The durable identifier of a database instance.
687    DatabaseId
688);
689uuid_v7_id!(
690    /// The identifier of an authorization capability.
691    CapabilityId
692);
693uuid_v7_id!(
694    /// The identifier of a durable provenance record.
695    ProvenanceId
696);
697uuid_v7_id!(
698    /// The caller-stable identifier of one offline maintenance operation.
699    OfflineMaintenanceOperationId
700);
701uuid_v7_id!(
702    /// The caller-stable identifier of one irreversible primary fence.
703    ReplicationFenceOperationId
704);
705uuid_v7_id!(
706    /// The caller-stable identifier of one explicit follower promotion.
707    ReplicationPromotionOperationId
708);
709uuid_v7_id!(
710    /// The caller-stable identifier of one contract migration operation.
711    ContractMigrationOperationId
712);
713uuid_v7_id!(
714    /// The caller-stable identifier of one application installation campaign.
715    ApplicationInstallationCampaignId
716);
717uuid_v7_id!(
718    /// The caller-stable identifier of one symbolic application export.
719    ApplicationExportOperationId
720);
721
722/// A safe validation failure for a bounded textual identifier.
723#[derive(Clone, Copy, Debug, Eq, PartialEq)]
724pub enum TextIdError {
725    /// The identifier is empty.
726    Empty,
727    /// The UTF-8 representation exceeds its byte limit.
728    TooLong {
729        /// The applicable maximum byte length.
730        maximum: usize,
731        /// The supplied byte length.
732        actual: usize,
733    },
734    /// The identifier contains a byte outside its permitted alphabet.
735    InvalidCharacter {
736        /// The zero-based byte index of the invalid character.
737        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/// A bounded exact contract lineage name.
760///
761/// The text is held behind an `Arc` because this identifier is copied along
762/// every command record, index entry and audit target on the single writer
763/// thread; a `String` field made each of those copies a heap allocation.
764/// Validation, ordering, hashing and display use the exact same `str`. The
765/// closed owner retires its shared backing before releasing its charge.
766#[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    /// Validates and creates a contract lineage without normalizing its text.
789    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    /// Borrows the exact contract lineage name.
796    #[must_use]
797    pub fn as_str(&self) -> &str {
798        &self.0
799    }
800
801    /// Borrows the exact UTF-8 identity bytes.
802    #[must_use]
803    pub fn as_bytes(&self) -> &[u8] {
804        self.0.as_bytes()
805    }
806}
807
808/// Checked ASCII query-module name.
809#[derive(Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
810pub struct QueryModuleName(String);
811crate::inspect_owned_capacity_tuple_v1!(QueryModuleName, String);
812
813impl QueryModuleName {
814    /// Maximum encoded bytes in one query-module name.
815    pub const MAX_BYTES: usize = 256;
816
817    /// Checks one nonempty source-like module name.
818    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    /// Borrows the exact module name.
839    #[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/// Checked exact name of one deployed application query.
861#[derive(Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
862pub struct QueryOperationName(String);
863crate::inspect_owned_capacity_tuple_v1!(QueryOperationName, String);
864
865impl QueryOperationName {
866    /// Maximum encoded bytes in one query operation name.
867    pub const MAX_BYTES: usize = 256;
868
869    /// Checks one nonempty source identifier without normalization.
870    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    /// Borrows the exact query name.
891    #[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/// Checked exact name of one compiler-owned row policy.
913#[derive(Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
914pub struct RowPolicyName(String);
915crate::inspect_owned_capacity_tuple_v1!(RowPolicyName, String);
916
917impl RowPolicyName {
918    /// Maximum encoded bytes in one row-policy name.
919    pub const MAX_BYTES: usize = 256;
920
921    /// Checks one nonempty source identifier without normalization.
922    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    /// Borrows the exact policy name.
943    #[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/// Checked exact name of one reactive application operation.
965#[derive(Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
966pub struct ReactiveOperationName(String);
967crate::inspect_owned_capacity_tuple_v1!(ReactiveOperationName, String);
968
969impl ReactiveOperationName {
970    /// Maximum encoded bytes in one reactive operation name.
971    pub const MAX_BYTES: usize = 256;
972
973    /// Checks one nonempty source identifier without normalization.
974    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    /// Borrows the exact operation name.
995    #[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/// A bounded ASCII environment slug.
1023#[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    /// Validates and creates an environment slug without changing its spelling.
1043    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    /// Borrows the exact environment slug.
1058    #[must_use]
1059    pub fn as_str(&self) -> &str {
1060        &self.0
1061    }
1062
1063    /// Borrows the exact ASCII identity bytes.
1064    #[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/// A bounded exact tenant identifier.
1077#[derive(Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
1078pub struct TenantId(String);
1079crate::inspect_owned_capacity_tuple_v1!(TenantId, String);
1080
1081impl TenantId {
1082    /// Validates and creates a tenant identifier without normalizing its text.
1083    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    /// Borrows the exact tenant identifier.
1090    #[must_use]
1091    pub fn as_str(&self) -> &str {
1092        &self.0
1093    }
1094
1095    /// Borrows the exact UTF-8 identity bytes.
1096    #[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/// The tenant partition against which an operation is evaluated.
1109#[derive(Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
1110pub enum TenantScope {
1111    /// The operation is not scoped to one tenant.
1112    Global,
1113    /// The operation is scoped to the specified tenant.
1114    Tenant(TenantId),
1115}
1116
1117impl TenantScope {
1118    /// Returns the immutable idempotency-identity component encoding.
1119    #[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/// A bounded stable principal identifier.
1160#[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    /// Validates and creates an actor identifier.
1174    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    /// Borrows the identifier text.
1181    #[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/// A bounded caller-provided key for uncertainty recovery.
1194#[derive(Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
1195pub struct IdempotencyKey(String);
1196crate::inspect_owned_capacity_tuple_v1!(IdempotencyKey, String);
1197
1198impl IdempotencyKey {
1199    /// Validates and creates an idempotency key.
1200    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    /// Explicitly exposes the caller key for keyed digest calculation.
1207    #[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
1219/// Immutable entity-key namespace and v1 encoding prefix.
1220pub const ENTITY_KEY_V1_PREFIX: [u8; 2] = [0x45, 0x01];
1221
1222/// Immutable conflict-key namespace and v1 encoding prefix.
1223pub const CONFLICT_KEY_V1_PREFIX: [u8; 2] = [0x43, 0x01];
1224
1225/// Immutable partition-key namespace and v1 encoding prefix.
1226pub const PARTITION_KEY_V1_PREFIX: [u8; 2] = [0x50, 0x01];
1227
1228/// Immutable index-entry-key namespace and v1 encoding prefix.
1229pub 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/// A safe validation failure for bounded canonical key bytes.
1235#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1236pub enum KeyBytesError {
1237    /// The encoded key exceeds the v1 hard limit.
1238    TooLong {
1239        /// Supplied encoded length.
1240        actual: usize,
1241        /// Maximum encoded length.
1242        maximum: usize,
1243    },
1244    /// The encoded key cannot contain the required prefix and type identity.
1245    TooShort {
1246        /// Supplied encoded length.
1247        actual: usize,
1248        /// Minimum encoded envelope length.
1249        minimum: usize,
1250    },
1251    /// The key uses another purpose namespace.
1252    WrongPurpose {
1253        /// Required purpose byte.
1254        expected: u8,
1255        /// Supplied purpose byte.
1256        actual: u8,
1257    },
1258    /// The key uses an unsupported format version.
1259    UnsupportedVersion {
1260        /// Supplied version byte.
1261        version: u8,
1262    },
1263    /// The key envelope contains the reserved zero owner identity.
1264    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            /// Validates a key's purpose/version envelope and hard size bounds.
1305            ///
1306            /// Component-level validation requires the exact compiler-produced schema.
1307            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            /// Validates a key's purpose/version envelope and hard size bounds.
1336            pub fn from_bytes(bytes: Vec<u8>) -> Result<Self, KeyBytesError> {
1337                Self::new(bytes)
1338            }
1339
1340            /// Returns the stable type identity encoded in the key envelope.
1341            #[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            /// Borrows the canonical key bytes.
1357            #[must_use]
1358            pub fn as_bytes(&self) -> &[u8] {
1359                &self.0
1360            }
1361
1362            /// Returns the canonical key bytes.
1363            #[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    /// An opaque canonical entity key.
1383    EntityKey,
1384    EntityTypeId,
1385    entity_type_id,
1386    ENTITY_KEY_V1_PREFIX,
1387    TYPED_KEY_ENVELOPE_BYTES
1388);
1389bounded_key!(
1390    /// An opaque canonical logical conflict key.
1391    ConflictKey,
1392    AggregateTypeId,
1393    aggregate_type_id,
1394    CONFLICT_KEY_V1_PREFIX,
1395    TYPED_KEY_ENVELOPE_BYTES
1396);
1397bounded_key!(
1398    /// An opaque canonical logical partition key.
1399    PartitionKey,
1400    AggregateTypeId,
1401    aggregate_type_id,
1402    PARTITION_KEY_V1_PREFIX,
1403    TYPED_KEY_ENVELOPE_BYTES
1404);
1405bounded_key!(
1406    /// A complete canonical local-index entry key.
1407    IndexEntryKey,
1408    IndexId,
1409    index_id,
1410    INDEX_ENTRY_KEY_V1_PREFIX,
1411    INDEX_ENTRY_KEY_MIN_BYTES
1412);
1413
1414/// A 32-byte key used by a reviewed keyed-digest construction.
1415pub struct DigestKey([u8; 32]);
1416
1417impl DigestKey {
1418    /// Creates a digest key from secret key bytes.
1419    #[must_use]
1420    pub const fn from_bytes(bytes: [u8; 32]) -> Self {
1421        Self(bytes)
1422    }
1423
1424    /// Explicitly exposes the secret bytes to a keyed digest implementation.
1425    #[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
1437// Exhaustive non-durable ownership inventories for exact-provider admission.
1438impl 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], &timestamp[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}