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
225allocatable_nonzero_id!(
226    /// Monotonically increasing compare-and-swap revision of a durable saga instance.
227    SagaRevision,
228    u64,
229    NonZeroU64
230);
231
232allocatable_nonzero_id!(
233    /// Monotonically increasing worker lease fencing token of a durable saga instance.
234    SagaFencingToken,
235    u64,
236    NonZeroU64
237);
238
239/// The contiguous authoritative application-commit frontier.
240#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
241pub enum FrontierPosition {
242    /// No application commit has been applied.
243    BeforeFirst,
244    /// Every application commit through this sequence has been applied.
245    AppliedThrough(CommitSequence),
246}
247
248/// The durable identity of one event within a committed command.
249#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
250pub struct EventId {
251    commit_sequence: CommitSequence,
252    event_ordinal: u32,
253}
254
255impl EventId {
256    /// Creates an event identifier from its commit sequence and zero-based ordinal.
257    #[must_use]
258    pub const fn new(commit_sequence: CommitSequence, event_ordinal: u32) -> Self {
259        Self {
260            commit_sequence,
261            event_ordinal,
262        }
263    }
264
265    /// Decodes the canonical big-endian representation.
266    #[must_use]
267    pub const fn from_be_bytes(bytes: [u8; 12]) -> Option<Self> {
268        let commit_sequence = match CommitSequence::new(u64::from_be_bytes([
269            bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
270        ])) {
271            Some(value) => value,
272            None => return None,
273        };
274        let event_ordinal = u32::from_be_bytes([bytes[8], bytes[9], bytes[10], bytes[11]]);
275        Some(Self::new(commit_sequence, event_ordinal))
276    }
277
278    /// Returns the commit containing the event.
279    #[must_use]
280    pub const fn commit_sequence(self) -> CommitSequence {
281        self.commit_sequence
282    }
283
284    /// Returns the event's zero-based ordinal within its commit.
285    #[must_use]
286    pub const fn event_ordinal(self) -> u32 {
287        self.event_ordinal
288    }
289
290    /// Returns the canonical big-endian representation.
291    #[must_use]
292    pub const fn to_be_bytes(self) -> [u8; 12] {
293        let sequence = self.commit_sequence.to_be_bytes();
294        let ordinal = self.event_ordinal.to_be_bytes();
295        [
296            sequence[0],
297            sequence[1],
298            sequence[2],
299            sequence[3],
300            sequence[4],
301            sequence[5],
302            sequence[6],
303            sequence[7],
304            ordinal[0],
305            ordinal[1],
306            ordinal[2],
307            ordinal[3],
308        ]
309    }
310}
311
312macro_rules! hash_id {
313    ($(#[$meta:meta])* $name:ident) => {
314        $(#[$meta])*
315        #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
316        pub struct $name([u8; 32]);
317        crate::inspect_owned_capacity_tuple_v1!($name, [u8; 32]);
318
319        impl $name {
320            /// Creates a hash identifier from its 32 digest bytes.
321            #[must_use]
322            pub const fn from_bytes(bytes: [u8; 32]) -> Self {
323                Self(bytes)
324            }
325
326            /// Borrows the digest bytes.
327            #[must_use]
328            pub const fn as_bytes(&self) -> &[u8; 32] {
329                &self.0
330            }
331
332            /// Returns the digest bytes.
333            #[must_use]
334            pub const fn into_bytes(self) -> [u8; 32] {
335                self.0
336            }
337        }
338    };
339}
340
341hash_id!(
342    /// The hash of one canonical value document.
343    CanonicalValueHash
344);
345hash_id!(
346    /// The hash of an executable command plan.
347    PlanHash
348);
349hash_id!(
350    /// The hash of one validated projection plan.
351    ProjectionPlanHash
352);
353hash_id!(
354    /// The hash of one canonical sealed projection-provider descriptor.
355    ProjectionProviderDescriptorHash
356);
357hash_id!(
358    /// The hash of one complete canonical columnar definition-semantics document.
359    ColumnarDefinitionSemanticsHashV1
360);
361hash_id!(
362    /// The hash of one complete schema-bound columnar projection specification.
363    ColumnarProjectionSpecHashV1
364);
365hash_id!(
366    /// The hash of one closed RiffQL query access program.
367    QueryPlanHash
368);
369hash_id!(
370    /// The digest of one ordered bounded partition-set epoch observation.
371    PartitionSetObservationHash
372);
373hash_id!(
374    /// The identity of one canonical immutable query module.
375    QueryModuleHash
376);
377hash_id!(
378    /// The hash of one exact reactive source document.
379    ReactiveSourceHash
380);
381hash_id!(
382    /// The identity of one canonical reactive operation plan.
383    ReactiveOperationHash
384);
385hash_id!(
386    /// The identity of one canonical immutable reactive module.
387    ReactiveModuleHash
388);
389hash_id!(
390    /// The identity of one exact durable event consumer.
391    EventConsumerIdentityHash
392);
393hash_id!(
394    /// Stable identity of one bounded scheduled workflow attempt.
395    ScheduledAttemptHash
396);
397hash_id!(
398    /// The identity of one exact application installation plan.
399    ApplicationInstallationPlanHash
400);
401hash_id!(
402    /// The identity of one terminal application installation receipt.
403    ApplicationInstallationReceiptHash
404);
405hash_id!(
406    /// The identity of one exact adapter conformance manifest.
407    AdapterConformanceManifestHash
408);
409hash_id!(
410    /// The identity of one canonical application manifest.
411    ApplicationManifestHash
412);
413hash_id!(
414    /// The identity of one author-owned symbolic application source manifest.
415    ApplicationSourceHash
416);
417hash_id!(
418    /// The identity of one compiler-owned exact application lock.
419    ApplicationLockHash
420);
421hash_id!(
422    /// The content hash of one compiler-generated application artifact.
423    GeneratedArtifactHash
424);
425hash_id!(
426    /// The identity of one canonical compiled application role.
427    ApplicationRoleHash
428);
429hash_id!(
430    /// The identity of one exact, tenant-unbound application role definition.
431    ApplicationRoleDefinitionHash
432);
433hash_id!(
434    /// The hash of one exact RiffQL source document.
435    QuerySourceHash
436);
437hash_id!(
438    /// The hash of one canonical name-addressed RiffQL parameter set.
439    QueryParameterHash
440);
441hash_id!(
442    /// The hash of a contract bundle's ordered semantic plan set.
443    ContractPlanRootHash
444);
445hash_id!(
446    /// The hash of canonical contract source.
447    SourceHash
448);
449hash_id!(
450    /// The hash of one exact migration source document.
451    MigrationSourceHash
452);
453hash_id!(
454    /// The hash of an immutable contract bundle.
455    ContractBundleHash
456);
457hash_id!(
458    /// The hash of one canonical compiled migration bundle.
459    MigrationBundleHash
460);
461hash_id!(
462    /// The hash of canonical command input.
463    CanonicalInputHash
464);
465hash_id!(
466    /// The hash of a canonical durable event.
467    EventHash
468);
469hash_id!(
470    /// The hash of a complete canonical entity-record preimage.
471    EntityRecordHash
472);
473hash_id!(
474    /// The hash of one canonical committed entity transition.
475    EntityTransitionHash
476);
477hash_id!(
478    /// The hash of a canonical entity key.
479    EntityKeyHash
480);
481hash_id!(
482    /// The hash of a canonical conflict key.
483    ConflictKeyHash
484);
485hash_id!(
486    /// The hash of a canonical logical partition key.
487    PartitionKeyHash
488);
489hash_id!(
490    /// The hash of a public or durable schema.
491    SchemaHash
492);
493hash_id!(
494    /// The stable semantic-input hash of one offline maintenance operation.
495    OfflineMaintenanceInputHash
496);
497hash_id!(
498    /// The stable semantic-input hash of one contract migration operation.
499    ContractMigrationInputHash
500);
501hash_id!(
502    /// The chained hash of one durable contract migration journal state.
503    ContractMigrationJournalHash
504);
505hash_id!(
506    /// The digest of one complete staged contract migration validation.
507    ContractMigrationValidationDigest
508);
509hash_id!(
510    /// The hash of one canonical symbolic application-export page.
511    ApplicationExportPageHash
512);
513hash_id!(
514    /// The domain-separated genesis or ordered prefix of an export page ledger.
515    ApplicationExportLedgerHash
516);
517hash_id!(
518    /// The identity of one canonical symbolic application-export manifest.
519    ApplicationExportManifestHash
520);
521hash_id!(
522    /// The identity of one terminal symbolic application-export receipt.
523    ApplicationExportReceiptHash
524);
525hash_id!(
526    /// The identity of one adapter-owned compiled reimport mapping manifest.
527    ApplicationPortabilityManifestHash
528);
529hash_id!(
530    /// The identity of one terminal application reimport reconciliation receipt.
531    ApplicationReimportReceiptHash
532);
533
534/// A validation failure for a UUIDv7 identifier.
535#[derive(Clone, Copy, Debug, Eq, PartialEq)]
536pub enum UuidV7Error {
537    /// The UUID does not use the RFC 4122/RFC 9562 variant.
538    InvalidVariant,
539    /// The UUID does not use version 7.
540    InvalidVersion,
541}
542
543impl fmt::Display for UuidV7Error {
544    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
545        match self {
546            Self::InvalidVariant => formatter.write_str("identifier has an invalid UUID variant"),
547            Self::InvalidVersion => formatter.write_str("identifier is not UUID version 7"),
548        }
549    }
550}
551
552impl Error for UuidV7Error {}
553
554/// A safe failure while assembling a UUIDv7 value from explicit inputs.
555#[derive(Clone, Copy, Debug, Eq, PartialEq)]
556pub enum UuidV7ConstructionError {
557    /// The Unix-millisecond timestamp cannot fit UUIDv7's 48-bit timestamp field.
558    UnixMillisecondsOutOfRange,
559}
560
561impl fmt::Display for UuidV7ConstructionError {
562    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
563        formatter.write_str("UUIDv7 Unix-millisecond timestamp is out of range")
564    }
565}
566
567impl Error for UuidV7ConstructionError {}
568
569const UUID_V7_MAX_UNIX_MILLISECONDS: u64 = 0xffff_ffff_ffff;
570
571const fn assemble_uuid_v7(
572    unix_milliseconds: u64,
573    random: [u8; 10],
574) -> Result<[u8; 16], UuidV7ConstructionError> {
575    if unix_milliseconds > UUID_V7_MAX_UNIX_MILLISECONDS {
576        return Err(UuidV7ConstructionError::UnixMillisecondsOutOfRange);
577    }
578
579    let timestamp = unix_milliseconds.to_be_bytes();
580    Ok([
581        timestamp[2],
582        timestamp[3],
583        timestamp[4],
584        timestamp[5],
585        timestamp[6],
586        timestamp[7],
587        0x70 | (random[0] & 0x0f),
588        random[1],
589        0x80 | (random[2] & 0x3f),
590        random[3],
591        random[4],
592        random[5],
593        random[6],
594        random[7],
595        random[8],
596        random[9],
597    ])
598}
599
600fn validate_uuid_v7(bytes: &[u8; 16]) -> Result<(), UuidV7Error> {
601    if bytes[8] & 0b1100_0000 != 0b1000_0000 {
602        return Err(UuidV7Error::InvalidVariant);
603    }
604    if bytes[6] >> 4 != 7 {
605        return Err(UuidV7Error::InvalidVersion);
606    }
607    Ok(())
608}
609
610fn fmt_uuid(bytes: &[u8; 16], formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
611    write!(
612        formatter,
613        "{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
614        bytes[0],
615        bytes[1],
616        bytes[2],
617        bytes[3],
618        bytes[4],
619        bytes[5],
620        bytes[6],
621        bytes[7],
622        bytes[8],
623        bytes[9],
624        bytes[10],
625        bytes[11],
626        bytes[12],
627        bytes[13],
628        bytes[14],
629        bytes[15]
630    )
631}
632
633macro_rules! uuid_v7_id {
634    ($(#[$meta:meta])* $name:ident) => {
635        $(#[$meta])*
636        #[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
637        pub struct $name([u8; 16]);
638        crate::inspect_owned_capacity_tuple_v1!($name, [u8; 16]);
639
640        impl $name {
641            /// Assembles a UUIDv7 from an explicit 48-bit Unix-millisecond value and random bytes.
642            pub const fn from_unix_milliseconds_and_random(
643                unix_milliseconds: u64,
644                random: [u8; 10],
645            ) -> Result<Self, UuidV7ConstructionError> {
646                match assemble_uuid_v7(unix_milliseconds, random) {
647                    Ok(bytes) => Ok(Self(bytes)),
648                    Err(error) => Err(error),
649                }
650            }
651
652            /// Validates and creates an identifier from network-order UUID bytes.
653            pub fn from_bytes(bytes: [u8; 16]) -> Result<Self, UuidV7Error> {
654                validate_uuid_v7(&bytes)?;
655                Ok(Self(bytes))
656            }
657
658            /// Borrows the network-order UUID bytes.
659            #[must_use]
660            pub const fn as_bytes(&self) -> &[u8; 16] {
661                &self.0
662            }
663
664            /// Returns the network-order UUID bytes.
665            #[must_use]
666            pub const fn into_bytes(self) -> [u8; 16] {
667                self.0
668            }
669        }
670
671        impl fmt::Display for $name {
672            fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
673                fmt_uuid(&self.0, formatter)
674            }
675        }
676
677        impl fmt::Debug for $name {
678            fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
679                formatter.write_str(concat!(stringify!($name), "("))?;
680                fmt_uuid(&self.0, formatter)?;
681                formatter.write_str(")")
682            }
683        }
684    };
685}
686
687uuid_v7_id!(
688    /// The identifier of one transport submission.
689    RequestId
690);
691uuid_v7_id!(
692    /// The identifier of an agent session.
693    AgentSessionId
694);
695uuid_v7_id!(
696    /// A public correlation identifier for an internal failure.
697    IncidentId
698);
699uuid_v7_id!(
700    /// The durable identifier of a database instance.
701    DatabaseId
702);
703uuid_v7_id!(
704    /// The identifier of an authorization capability.
705    CapabilityId
706);
707uuid_v7_id!(
708    /// The identifier of a durable provenance record.
709    ProvenanceId
710);
711uuid_v7_id!(
712    /// The caller-stable identifier of one offline maintenance operation.
713    OfflineMaintenanceOperationId
714);
715uuid_v7_id!(
716    /// The caller-stable identifier of one irreversible primary fence.
717    ReplicationFenceOperationId
718);
719uuid_v7_id!(
720    /// The caller-stable identifier of one explicit follower promotion.
721    ReplicationPromotionOperationId
722);
723uuid_v7_id!(
724    /// The caller-stable identifier of one contract migration operation.
725    ContractMigrationOperationId
726);
727uuid_v7_id!(
728    /// The caller-stable identifier of one application installation campaign.
729    ApplicationInstallationCampaignId
730);
731uuid_v7_id!(
732    /// The caller-stable identifier of one symbolic application export.
733    ApplicationExportOperationId
734);
735uuid_v7_id!(
736    /// The durable UUIDv7 identifier of one multi-step saga instance.
737    SagaId
738);
739
740/// A safe validation failure for a bounded textual identifier.
741#[derive(Clone, Copy, Debug, Eq, PartialEq)]
742pub enum TextIdError {
743    /// The identifier is empty.
744    Empty,
745    /// The UTF-8 representation exceeds its byte limit.
746    TooLong {
747        /// The applicable maximum byte length.
748        maximum: usize,
749        /// The supplied byte length.
750        actual: usize,
751    },
752    /// The identifier contains a byte outside its permitted alphabet.
753    InvalidCharacter {
754        /// The zero-based byte index of the invalid character.
755        index: usize,
756    },
757}
758
759impl fmt::Display for TextIdError {
760    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
761        match self {
762            Self::Empty => formatter.write_str("identifier must not be empty"),
763            Self::TooLong { maximum, actual } => write!(
764                formatter,
765                "identifier is {actual} bytes but the maximum is {maximum} bytes"
766            ),
767            Self::InvalidCharacter { index } => {
768                write!(
769                    formatter,
770                    "identifier has an invalid character at byte {index}"
771                )
772            }
773        }
774    }
775}
776
777/// A bounded exact contract lineage name.
778///
779/// The text is held behind an `Arc` because this identifier is copied along
780/// every command record, index entry and audit target on the single writer
781/// thread; a `String` field made each of those copies a heap allocation.
782/// Validation, ordering, hashing and display use the exact same `str`. The
783/// closed owner retires its shared backing before releasing its charge.
784#[derive(Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
785pub struct ContractLineage(crate::OwnedCapacityTextV1);
786impl fmt::Debug for ContractLineage {
787    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
788        formatter
789            .debug_tuple("ContractLineage")
790            .field(&self.0)
791            .finish()
792    }
793}
794
795impl crate::InspectOwnedCapacityV1 for ContractLineage {
796    fn inspect_owned_capacity(
797        &self,
798        inspection: &mut crate::OwnedCapacityInspectionV1,
799    ) -> Result<(), crate::OwnedCapacityInspectionErrorV1> {
800        let Self(value) = self;
801        inspection.visit(value)
802    }
803}
804
805impl ContractLineage {
806    /// Validates and creates a contract lineage without normalizing its text.
807    pub fn new(value: impl Into<String>) -> Result<Self, TextIdError> {
808        let value = value.into();
809        validate_text_id(&value, MAX_CONTRACT_LINEAGE_BYTES)?;
810        Ok(Self(crate::OwnedCapacityTextV1::new(value)))
811    }
812
813    /// Borrows the exact contract lineage name.
814    #[must_use]
815    pub fn as_str(&self) -> &str {
816        &self.0
817    }
818
819    /// Borrows the exact UTF-8 identity bytes.
820    #[must_use]
821    pub fn as_bytes(&self) -> &[u8] {
822        self.0.as_bytes()
823    }
824}
825
826/// Checked ASCII query-module name.
827#[derive(Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
828pub struct QueryModuleName(String);
829crate::inspect_owned_capacity_tuple_v1!(QueryModuleName, String);
830
831impl QueryModuleName {
832    /// Maximum encoded bytes in one query-module name.
833    pub const MAX_BYTES: usize = 256;
834
835    /// Checks one nonempty source-like module name.
836    pub fn new(value: impl Into<String>) -> Result<Self, TextIdError> {
837        let value = value.into();
838        if value.is_empty() {
839            return Err(TextIdError::Empty);
840        }
841        if value.len() > Self::MAX_BYTES {
842            return Err(TextIdError::TooLong {
843                maximum: Self::MAX_BYTES,
844                actual: value.len(),
845            });
846        }
847        if let Some(index) = value.bytes().enumerate().find_map(|(index, byte)| {
848            (!(byte == b'_' || byte.is_ascii_alphabetic() || index > 0 && byte.is_ascii_digit()))
849                .then_some(index)
850        }) {
851            return Err(TextIdError::InvalidCharacter { index });
852        }
853        Ok(Self(value))
854    }
855
856    /// Borrows the exact module name.
857    #[must_use]
858    pub fn as_str(&self) -> &str {
859        &self.0
860    }
861}
862
863impl fmt::Debug for QueryModuleName {
864    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
865        formatter
866            .debug_tuple("QueryModuleName")
867            .field(&self.0)
868            .finish()
869    }
870}
871
872impl fmt::Display for QueryModuleName {
873    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
874        self.0.fmt(formatter)
875    }
876}
877
878/// Checked exact name of one deployed application query.
879#[derive(Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
880pub struct QueryOperationName(String);
881crate::inspect_owned_capacity_tuple_v1!(QueryOperationName, String);
882
883impl QueryOperationName {
884    /// Maximum encoded bytes in one query operation name.
885    pub const MAX_BYTES: usize = 256;
886
887    /// Checks one nonempty source identifier without normalization.
888    pub fn new(value: impl Into<String>) -> Result<Self, TextIdError> {
889        let value = value.into();
890        if value.is_empty() {
891            return Err(TextIdError::Empty);
892        }
893        if value.len() > Self::MAX_BYTES {
894            return Err(TextIdError::TooLong {
895                maximum: Self::MAX_BYTES,
896                actual: value.len(),
897            });
898        }
899        if let Some(index) = value.bytes().enumerate().find_map(|(index, byte)| {
900            (!(byte == b'_' || byte.is_ascii_alphabetic() || index > 0 && byte.is_ascii_digit()))
901                .then_some(index)
902        }) {
903            return Err(TextIdError::InvalidCharacter { index });
904        }
905        Ok(Self(value))
906    }
907
908    /// Borrows the exact query name.
909    #[must_use]
910    pub fn as_str(&self) -> &str {
911        &self.0
912    }
913}
914
915impl fmt::Debug for QueryOperationName {
916    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
917        formatter
918            .debug_tuple("QueryOperationName")
919            .field(&self.0)
920            .finish()
921    }
922}
923
924impl fmt::Display for QueryOperationName {
925    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
926        self.0.fmt(formatter)
927    }
928}
929
930/// Checked exact name of one compiler-owned row policy.
931#[derive(Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
932pub struct RowPolicyName(String);
933crate::inspect_owned_capacity_tuple_v1!(RowPolicyName, String);
934
935impl RowPolicyName {
936    /// Maximum encoded bytes in one row-policy name.
937    pub const MAX_BYTES: usize = 256;
938
939    /// Checks one nonempty source identifier without normalization.
940    pub fn new(value: impl Into<String>) -> Result<Self, TextIdError> {
941        let value = value.into();
942        if value.is_empty() {
943            return Err(TextIdError::Empty);
944        }
945        if value.len() > Self::MAX_BYTES {
946            return Err(TextIdError::TooLong {
947                maximum: Self::MAX_BYTES,
948                actual: value.len(),
949            });
950        }
951        if let Some(index) = value.bytes().enumerate().find_map(|(index, byte)| {
952            (!(byte == b'_' || byte.is_ascii_alphabetic() || index > 0 && byte.is_ascii_digit()))
953                .then_some(index)
954        }) {
955            return Err(TextIdError::InvalidCharacter { index });
956        }
957        Ok(Self(value))
958    }
959
960    /// Borrows the exact policy name.
961    #[must_use]
962    pub fn as_str(&self) -> &str {
963        &self.0
964    }
965}
966
967impl fmt::Debug for RowPolicyName {
968    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
969        formatter
970            .debug_tuple("RowPolicyName")
971            .field(&self.0)
972            .finish()
973    }
974}
975
976impl fmt::Display for RowPolicyName {
977    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
978        self.0.fmt(formatter)
979    }
980}
981
982/// Checked exact name of one reactive application operation.
983#[derive(Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
984pub struct ReactiveOperationName(String);
985crate::inspect_owned_capacity_tuple_v1!(ReactiveOperationName, String);
986
987impl ReactiveOperationName {
988    /// Maximum encoded bytes in one reactive operation name.
989    pub const MAX_BYTES: usize = 256;
990
991    /// Checks one nonempty source identifier without normalization.
992    pub fn new(value: impl Into<String>) -> Result<Self, TextIdError> {
993        let value = value.into();
994        if value.is_empty() {
995            return Err(TextIdError::Empty);
996        }
997        if value.len() > Self::MAX_BYTES {
998            return Err(TextIdError::TooLong {
999                maximum: Self::MAX_BYTES,
1000                actual: value.len(),
1001            });
1002        }
1003        if let Some(index) = value.bytes().enumerate().find_map(|(index, byte)| {
1004            (!(byte == b'_' || byte.is_ascii_alphabetic() || index > 0 && byte.is_ascii_digit()))
1005                .then_some(index)
1006        }) {
1007            return Err(TextIdError::InvalidCharacter { index });
1008        }
1009        Ok(Self(value))
1010    }
1011
1012    /// Borrows the exact operation name.
1013    #[must_use]
1014    pub fn as_str(&self) -> &str {
1015        &self.0
1016    }
1017}
1018
1019impl fmt::Debug for ReactiveOperationName {
1020    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1021        formatter
1022            .debug_tuple("ReactiveOperationName")
1023            .field(&self.0)
1024            .finish()
1025    }
1026}
1027
1028impl fmt::Display for ReactiveOperationName {
1029    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1030        self.0.fmt(formatter)
1031    }
1032}
1033
1034impl fmt::Display for ContractLineage {
1035    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1036        self.0.fmt(formatter)
1037    }
1038}
1039
1040/// A bounded ASCII environment slug.
1041#[derive(Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
1042pub struct Environment(crate::OwnedCapacityTextV1);
1043impl fmt::Debug for Environment {
1044    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1045        formatter.debug_tuple("Environment").field(&self.0).finish()
1046    }
1047}
1048
1049impl crate::InspectOwnedCapacityV1 for Environment {
1050    fn inspect_owned_capacity(
1051        &self,
1052        inspection: &mut crate::OwnedCapacityInspectionV1,
1053    ) -> Result<(), crate::OwnedCapacityInspectionErrorV1> {
1054        let Self(value) = self;
1055        inspection.visit(value)
1056    }
1057}
1058
1059impl Environment {
1060    /// Validates and creates an environment slug without changing its spelling.
1061    pub fn new(value: impl Into<String>) -> Result<Self, TextIdError> {
1062        let value = value.into();
1063        validate_text_id(&value, MAX_ENVIRONMENT_BYTES)?;
1064
1065        if let Some(index) = value
1066            .bytes()
1067            .position(|byte| !byte.is_ascii_alphanumeric() && !matches!(byte, b'.' | b'_' | b'-'))
1068        {
1069            return Err(TextIdError::InvalidCharacter { index });
1070        }
1071
1072        Ok(Self(crate::OwnedCapacityTextV1::new(value)))
1073    }
1074
1075    /// Borrows the exact environment slug.
1076    #[must_use]
1077    pub fn as_str(&self) -> &str {
1078        &self.0
1079    }
1080
1081    /// Borrows the exact ASCII identity bytes.
1082    #[must_use]
1083    pub fn as_bytes(&self) -> &[u8] {
1084        self.0.as_bytes()
1085    }
1086}
1087
1088impl fmt::Display for Environment {
1089    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1090        self.0.fmt(formatter)
1091    }
1092}
1093
1094/// A bounded exact tenant identifier.
1095#[derive(Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
1096pub struct TenantId(String);
1097crate::inspect_owned_capacity_tuple_v1!(TenantId, String);
1098
1099impl TenantId {
1100    /// Validates and creates a tenant identifier without normalizing its text.
1101    pub fn new(value: impl Into<String>) -> Result<Self, TextIdError> {
1102        let value = value.into();
1103        validate_text_id(&value, MAX_TENANT_ID_BYTES)?;
1104        Ok(Self(value))
1105    }
1106
1107    /// Borrows the exact tenant identifier.
1108    #[must_use]
1109    pub fn as_str(&self) -> &str {
1110        &self.0
1111    }
1112
1113    /// Borrows the exact UTF-8 identity bytes.
1114    #[must_use]
1115    pub fn as_bytes(&self) -> &[u8] {
1116        self.0.as_bytes()
1117    }
1118}
1119
1120impl fmt::Debug for TenantId {
1121    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1122        formatter.write_str("TenantId([REDACTED])")
1123    }
1124}
1125
1126/// The tenant partition against which an operation is evaluated.
1127#[derive(Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
1128pub enum TenantScope {
1129    /// The operation is not scoped to one tenant.
1130    Global,
1131    /// The operation is scoped to the specified tenant.
1132    Tenant(TenantId),
1133}
1134
1135impl TenantScope {
1136    /// Returns the immutable idempotency-identity component encoding.
1137    #[must_use]
1138    pub fn to_canonical_bytes(&self) -> Vec<u8> {
1139        match self {
1140            Self::Global => vec![0],
1141            Self::Tenant(tenant_id) => {
1142                let tenant_bytes = tenant_id.as_bytes();
1143                let mut bytes = Vec::with_capacity(1 + 4 + tenant_bytes.len());
1144                bytes.push(1);
1145                bytes.extend_from_slice(&(tenant_bytes.len() as u32).to_be_bytes());
1146                bytes.extend_from_slice(tenant_bytes);
1147                bytes
1148            }
1149        }
1150    }
1151}
1152
1153impl fmt::Debug for TenantScope {
1154    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1155        match self {
1156            Self::Global => formatter.write_str("TenantScope::Global"),
1157            Self::Tenant(_) => formatter.write_str("TenantScope::Tenant([REDACTED])"),
1158        }
1159    }
1160}
1161
1162impl Error for TextIdError {}
1163
1164fn validate_text_id(value: &str, maximum: usize) -> Result<(), TextIdError> {
1165    if value.is_empty() {
1166        return Err(TextIdError::Empty);
1167    }
1168    if value.len() > maximum {
1169        return Err(TextIdError::TooLong {
1170            maximum,
1171            actual: value.len(),
1172        });
1173    }
1174    Ok(())
1175}
1176
1177/// A bounded stable principal identifier.
1178#[derive(Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
1179pub struct ActorId(crate::OwnedCapacityTextV1);
1180impl crate::InspectOwnedCapacityV1 for ActorId {
1181    fn inspect_owned_capacity(
1182        &self,
1183        inspection: &mut crate::OwnedCapacityInspectionV1,
1184    ) -> Result<(), crate::OwnedCapacityInspectionErrorV1> {
1185        let Self(value) = self;
1186        inspection.visit(value)
1187    }
1188}
1189
1190impl ActorId {
1191    /// Validates and creates an actor identifier.
1192    pub fn new(value: impl Into<String>) -> Result<Self, TextIdError> {
1193        let value = value.into();
1194        validate_text_id(&value, MAX_ACTOR_ID_BYTES)?;
1195        Ok(Self(crate::OwnedCapacityTextV1::new(value)))
1196    }
1197
1198    /// Borrows the identifier text.
1199    #[must_use]
1200    pub fn as_str(&self) -> &str {
1201        &self.0
1202    }
1203}
1204
1205impl fmt::Debug for ActorId {
1206    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1207        formatter.write_str("ActorId([REDACTED])")
1208    }
1209}
1210
1211/// A bounded caller-provided key for uncertainty recovery.
1212#[derive(Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
1213pub struct IdempotencyKey(String);
1214crate::inspect_owned_capacity_tuple_v1!(IdempotencyKey, String);
1215
1216impl IdempotencyKey {
1217    /// Validates and creates an idempotency key.
1218    pub fn new(value: impl Into<String>) -> Result<Self, TextIdError> {
1219        let value = value.into();
1220        validate_text_id(&value, MAX_IDEMPOTENCY_KEY_BYTES)?;
1221        Ok(Self(value))
1222    }
1223
1224    /// Explicitly exposes the caller key for keyed digest calculation.
1225    #[must_use]
1226    pub fn expose_secret(&self) -> &str {
1227        &self.0
1228    }
1229}
1230
1231impl fmt::Debug for IdempotencyKey {
1232    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1233        formatter.write_str("IdempotencyKey([REDACTED])")
1234    }
1235}
1236
1237/// Immutable entity-key namespace and v1 encoding prefix.
1238pub const ENTITY_KEY_V1_PREFIX: [u8; 2] = [0x45, 0x01];
1239
1240/// Immutable conflict-key namespace and v1 encoding prefix.
1241pub const CONFLICT_KEY_V1_PREFIX: [u8; 2] = [0x43, 0x01];
1242
1243/// Immutable partition-key namespace and v1 encoding prefix.
1244pub const PARTITION_KEY_V1_PREFIX: [u8; 2] = [0x50, 0x01];
1245
1246/// Immutable index-entry-key namespace and v1 encoding prefix.
1247pub const INDEX_ENTRY_KEY_V1_PREFIX: [u8; 2] = [0x49, 0x01];
1248
1249const TYPED_KEY_ENVELOPE_BYTES: usize = 6;
1250const INDEX_ENTRY_KEY_MIN_BYTES: usize = TYPED_KEY_ENVELOPE_BYTES + 4 + TYPED_KEY_ENVELOPE_BYTES;
1251
1252/// A safe validation failure for bounded canonical key bytes.
1253#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1254pub enum KeyBytesError {
1255    /// The encoded key exceeds the v1 hard limit.
1256    TooLong {
1257        /// Supplied encoded length.
1258        actual: usize,
1259        /// Maximum encoded length.
1260        maximum: usize,
1261    },
1262    /// The encoded key cannot contain the required prefix and type identity.
1263    TooShort {
1264        /// Supplied encoded length.
1265        actual: usize,
1266        /// Minimum encoded envelope length.
1267        minimum: usize,
1268    },
1269    /// The key uses another purpose namespace.
1270    WrongPurpose {
1271        /// Required purpose byte.
1272        expected: u8,
1273        /// Supplied purpose byte.
1274        actual: u8,
1275    },
1276    /// The key uses an unsupported format version.
1277    UnsupportedVersion {
1278        /// Supplied version byte.
1279        version: u8,
1280    },
1281    /// The key envelope contains the reserved zero owner identity.
1282    ZeroTypeIdentity,
1283}
1284
1285impl fmt::Display for KeyBytesError {
1286    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1287        match self {
1288            Self::TooLong { actual, maximum } => {
1289                write!(
1290                    formatter,
1291                    "key is {actual} bytes but the maximum is {maximum} bytes"
1292                )
1293            }
1294            Self::TooShort { actual, minimum } => {
1295                write!(
1296                    formatter,
1297                    "key is {actual} bytes but the minimum envelope is {minimum} bytes"
1298                )
1299            }
1300            Self::WrongPurpose { expected, actual } => write!(
1301                formatter,
1302                "key purpose byte {actual} does not match expected purpose {expected}"
1303            ),
1304            Self::UnsupportedVersion { version } => {
1305                write!(formatter, "unsupported key encoding version {version}")
1306            }
1307            Self::ZeroTypeIdentity => formatter.write_str("key type identity must be nonzero"),
1308        }
1309    }
1310}
1311
1312impl Error for KeyBytesError {}
1313
1314macro_rules! bounded_key {
1315    ($(#[$meta:meta])* $name:ident, $type_id:ident, $type_id_method:ident, $prefix:ident, $minimum:expr) => {
1316        $(#[$meta])*
1317        #[derive(Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
1318        pub struct $name(Vec<u8>);
1319        crate::inspect_owned_capacity_tuple_v1!($name, Vec<u8>);
1320
1321        impl $name {
1322            /// Validates a key's purpose/version envelope and hard size bounds.
1323            ///
1324            /// Component-level validation requires the exact compiler-produced schema.
1325            pub fn new(bytes: Vec<u8>) -> Result<Self, KeyBytesError> {
1326                if bytes.len() > MAX_KEY_BYTES {
1327                    return Err(KeyBytesError::TooLong {
1328                        actual: bytes.len(),
1329                        maximum: MAX_KEY_BYTES,
1330                    });
1331                }
1332                if bytes.len() < $minimum {
1333                    return Err(KeyBytesError::TooShort {
1334                        actual: bytes.len(),
1335                        minimum: $minimum,
1336                    });
1337                }
1338                if bytes[0] != $prefix[0] {
1339                    return Err(KeyBytesError::WrongPurpose {
1340                        expected: $prefix[0],
1341                        actual: bytes[0],
1342                    });
1343                }
1344                if bytes[1] != $prefix[1] {
1345                    return Err(KeyBytesError::UnsupportedVersion { version: bytes[1] });
1346                }
1347                if bytes[2..TYPED_KEY_ENVELOPE_BYTES] == [0; 4] {
1348                    return Err(KeyBytesError::ZeroTypeIdentity);
1349                }
1350                Ok(Self(bytes))
1351            }
1352
1353            /// Validates a key's purpose/version envelope and hard size bounds.
1354            pub fn from_bytes(bytes: Vec<u8>) -> Result<Self, KeyBytesError> {
1355                Self::new(bytes)
1356            }
1357
1358            /// Returns the stable type identity encoded in the key envelope.
1359            #[must_use]
1360            pub fn $type_id_method(&self) -> $type_id {
1361                let value = u32::from_be_bytes([
1362                    self.0[2], self.0[3], self.0[4], self.0[5],
1363                ]);
1364                $type_id::new(value).expect("validated key contains a nonzero type identity")
1365            }
1366
1367            pub(crate) fn from_validated_bytes(bytes: Vec<u8>) -> Self {
1368                debug_assert!(bytes.len() >= $minimum);
1369                debug_assert!(bytes.len() <= MAX_KEY_BYTES);
1370                debug_assert_eq!(bytes[..2], $prefix);
1371                Self(bytes)
1372            }
1373
1374            /// Borrows the canonical key bytes.
1375            #[must_use]
1376            pub fn as_bytes(&self) -> &[u8] {
1377                &self.0
1378            }
1379
1380            /// Returns the canonical key bytes.
1381            #[must_use]
1382            pub fn into_bytes(self) -> Vec<u8> {
1383                self.0
1384            }
1385        }
1386
1387        impl fmt::Debug for $name {
1388            fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1389                formatter
1390                    .debug_struct(stringify!($name))
1391                    .field("bytes", &"[REDACTED]")
1392                    .field("length", &self.0.len())
1393                    .finish()
1394            }
1395        }
1396    };
1397}
1398
1399bounded_key!(
1400    /// An opaque canonical entity key.
1401    EntityKey,
1402    EntityTypeId,
1403    entity_type_id,
1404    ENTITY_KEY_V1_PREFIX,
1405    TYPED_KEY_ENVELOPE_BYTES
1406);
1407bounded_key!(
1408    /// An opaque canonical logical conflict key.
1409    ConflictKey,
1410    AggregateTypeId,
1411    aggregate_type_id,
1412    CONFLICT_KEY_V1_PREFIX,
1413    TYPED_KEY_ENVELOPE_BYTES
1414);
1415bounded_key!(
1416    /// An opaque canonical logical partition key.
1417    PartitionKey,
1418    AggregateTypeId,
1419    aggregate_type_id,
1420    PARTITION_KEY_V1_PREFIX,
1421    TYPED_KEY_ENVELOPE_BYTES
1422);
1423bounded_key!(
1424    /// A complete canonical local-index entry key.
1425    IndexEntryKey,
1426    IndexId,
1427    index_id,
1428    INDEX_ENTRY_KEY_V1_PREFIX,
1429    INDEX_ENTRY_KEY_MIN_BYTES
1430);
1431
1432/// A 32-byte key used by a reviewed keyed-digest construction.
1433pub struct DigestKey([u8; 32]);
1434
1435impl DigestKey {
1436    /// Creates a digest key from secret key bytes.
1437    #[must_use]
1438    pub const fn from_bytes(bytes: [u8; 32]) -> Self {
1439        Self(bytes)
1440    }
1441
1442    /// Explicitly exposes the secret bytes to a keyed digest implementation.
1443    #[must_use]
1444    pub const fn expose_secret(&self) -> &[u8; 32] {
1445        &self.0
1446    }
1447}
1448
1449impl fmt::Debug for DigestKey {
1450    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1451        formatter.write_str("DigestKey([REDACTED])")
1452    }
1453}
1454
1455// Exhaustive non-durable ownership inventories for exact-provider admission.
1456impl crate::InspectOwnedCapacityV1 for TenantScope {
1457    fn inspect_owned_capacity(
1458        &self,
1459        inspection: &mut crate::OwnedCapacityInspectionV1,
1460    ) -> Result<(), crate::OwnedCapacityInspectionErrorV1> {
1461        match self {
1462            Self::Global => {}
1463            Self::Tenant(field_0) => {
1464                inspection.visit(field_0)?;
1465            }
1466        }
1467        Ok(())
1468    }
1469}
1470
1471#[cfg(test)]
1472mod tests {
1473    use super::*;
1474    use proptest::prelude::*;
1475
1476    const UUID_V7: [u8; 16] = [
1477        0x01, 0x8f, 0x00, 0x00, 0x00, 0x00, 0x70, 0x01, 0x80, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
1478        0x03,
1479    ];
1480
1481    #[test]
1482    fn commit_sequence_next_is_checked() {
1483        let forty_one = CommitSequence::new(41).expect("nonzero sequence");
1484        assert_eq!(forty_one.checked_next(), CommitSequence::new(42));
1485        assert_eq!(
1486            CommitSequence::new(u64::MAX)
1487                .expect("nonzero sequence")
1488                .checked_next(),
1489            None
1490        );
1491    }
1492
1493    #[test]
1494    fn event_id_has_stable_big_endian_ordering() {
1495        let first = EventId::new(
1496            CommitSequence::new(0x0102_0304_0506_0708).expect("nonzero sequence"),
1497            0x090a_0b0c,
1498        );
1499        let next_event = EventId::new(first.commit_sequence(), 0x090a_0b0d);
1500        let next_commit = EventId::new(
1501            CommitSequence::new(0x0102_0304_0506_0709).expect("nonzero sequence"),
1502            0,
1503        );
1504
1505        assert_eq!(first.to_be_bytes(), [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]);
1506        assert_eq!(EventId::from_be_bytes(first.to_be_bytes()), Some(first));
1507        assert_eq!(EventId::from_be_bytes([0; 12]), None);
1508        assert!(first < next_event);
1509        assert!(next_event < next_commit);
1510        assert_eq!(first.event_ordinal(), 0x090a_0b0c);
1511    }
1512
1513    #[test]
1514    fn assigned_and_compiler_ids_are_one_based_and_exhaustion_is_checked() {
1515        macro_rules! assert_u64_id {
1516            ($type:ty) => {{
1517                assert_eq!(<$type>::new(0), None);
1518                assert_eq!(<$type>::first().get(), 1);
1519                assert_eq!(<$type>::first().checked_next().map(<$type>::get), Some(2));
1520                assert_eq!(
1521                    <$type>::new(u64::MAX).expect("nonzero").checked_next(),
1522                    None
1523                );
1524                assert_eq!(<$type>::try_from(0), Err(ZeroNumericIdError));
1525            }};
1526        }
1527
1528        macro_rules! assert_u32_id {
1529            ($type:ty) => {{
1530                assert_eq!(<$type>::new(0), None);
1531                assert_eq!(<$type>::first().get(), 1);
1532                assert_eq!(<$type>::first().checked_next().map(<$type>::get), Some(2));
1533                assert_eq!(
1534                    <$type>::new(u32::MAX).expect("nonzero").checked_next(),
1535                    None
1536                );
1537                assert_eq!(<$type>::try_from(0), Err(ZeroNumericIdError));
1538            }};
1539        }
1540
1541        assert_u64_id!(CommitSequence);
1542        assert_u64_id!(AdministrationSequence);
1543        assert_u64_id!(EntityVersion);
1544        assert_u64_id!(IndexEpoch);
1545        assert_u32_id!(EntityTypeId);
1546        assert_u32_id!(FieldId);
1547        assert_u32_id!(CommandId);
1548        assert_u32_id!(OutcomeId);
1549        assert_u32_id!(ProjectionId);
1550        assert_u32_id!(EventTypeId);
1551        assert_u32_id!(IndexId);
1552        assert_u32_id!(EnumTypeId);
1553        assert_u32_id!(EnumVariantId);
1554        assert_u32_id!(AggregateTypeId);
1555        assert_u32_id!(InvariantId);
1556
1557        assert_eq!(ContractVersion::new(0), None);
1558        assert_eq!(DigestKeyId::new(0), None);
1559        assert_eq!(ContractVersion::try_from(0), Err(ZeroNumericIdError));
1560        assert_eq!(DigestKeyId::try_from(0), Err(ZeroNumericIdError));
1561    }
1562
1563    #[test]
1564    fn uuid_v7_identifiers_validate_variant_and_version() {
1565        let mut invalid_version = UUID_V7;
1566        invalid_version[6] = 0x40;
1567        let mut invalid_variant = UUID_V7;
1568        invalid_variant[8] = 0xc0;
1569
1570        macro_rules! assert_decoder {
1571            ($type:ty) => {{
1572                let value = <$type>::from_bytes(UUID_V7).expect("valid UUIDv7");
1573                assert_eq!(value.as_bytes(), &UUID_V7);
1574                assert_eq!(
1575                    <$type>::from_bytes(invalid_version),
1576                    Err(UuidV7Error::InvalidVersion)
1577                );
1578                assert_eq!(
1579                    <$type>::from_bytes(invalid_variant),
1580                    Err(UuidV7Error::InvalidVariant)
1581                );
1582            }};
1583        }
1584
1585        assert_decoder!(RequestId);
1586        assert_decoder!(AgentSessionId);
1587        assert_decoder!(IncidentId);
1588        assert_decoder!(DatabaseId);
1589        assert_decoder!(CapabilityId);
1590        assert_decoder!(ProvenanceId);
1591
1592        let request = RequestId::from_bytes(UUID_V7).expect("valid UUIDv7");
1593        assert_eq!(request.to_string(), "018f0000-0000-7001-8002-000000000003");
1594    }
1595
1596    #[test]
1597    fn uuid_v7_pure_construction_matches_accepted_goldens_for_every_id_type() {
1598        const RANDOM: [u8; 10] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
1599        const PRIMARY: [u8; 16] = [
1600            0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0x70, 0x01, 0x82, 0x03, 0x04, 0x05, 0x06, 0x07,
1601            0x08, 0x09,
1602        ];
1603        const ZERO: [u8; 16] = [0, 0, 0, 0, 0, 0, 0x70, 0, 0x80, 0, 0, 0, 0, 0, 0, 0];
1604        const MAXIMUM: [u8; 16] = [
1605            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xff, 0xbf, 0xff, 0xff, 0xff, 0xff, 0xff,
1606            0xff, 0xff,
1607        ];
1608
1609        macro_rules! assert_constructor {
1610            ($type:ty) => {{
1611                let primary = <$type>::from_unix_milliseconds_and_random(0x0123_4567_89ab, RANDOM)
1612                    .expect("48-bit timestamp");
1613                assert_eq!(primary.into_bytes(), PRIMARY);
1614                assert_eq!(primary.to_string(), "01234567-89ab-7001-8203-040506070809");
1615                assert_eq!(
1616                    <$type>::from_unix_milliseconds_and_random(0, [0; 10])
1617                        .expect("lower bound")
1618                        .into_bytes(),
1619                    ZERO
1620                );
1621                assert_eq!(
1622                    <$type>::from_unix_milliseconds_and_random(
1623                        UUID_V7_MAX_UNIX_MILLISECONDS,
1624                        [0xff; 10],
1625                    )
1626                    .expect("upper bound")
1627                    .into_bytes(),
1628                    MAXIMUM
1629                );
1630                assert_eq!(
1631                    <$type>::from_unix_milliseconds_and_random(0x1_0000_0000_0000, [0xa5; 10]),
1632                    Err(UuidV7ConstructionError::UnixMillisecondsOutOfRange)
1633                );
1634            }};
1635        }
1636
1637        assert_constructor!(RequestId);
1638        assert_constructor!(AgentSessionId);
1639        assert_constructor!(IncidentId);
1640        assert_constructor!(DatabaseId);
1641        assert_constructor!(CapabilityId);
1642        assert_constructor!(ProvenanceId);
1643
1644        let error = UuidV7ConstructionError::UnixMillisecondsOutOfRange;
1645        assert_eq!(
1646            error.to_string(),
1647            "UUIDv7 Unix-millisecond timestamp is out of range"
1648        );
1649        assert!(!format!("{error:?}").contains("a5"));
1650    }
1651
1652    proptest! {
1653        #[test]
1654        fn uuid_v7_construction_preserves_fields_and_round_trips(
1655            unix_milliseconds in 0u64..=UUID_V7_MAX_UNIX_MILLISECONDS,
1656            random in any::<[u8; 10]>(),
1657        ) {
1658            let id = RequestId::from_unix_milliseconds_and_random(unix_milliseconds, random)
1659                .expect("generated timestamp is in range");
1660            let bytes = id.into_bytes();
1661            let timestamp = unix_milliseconds.to_be_bytes();
1662            let canonical_text = id.to_string();
1663
1664            prop_assert_eq!(&bytes[..6], &timestamp[2..]);
1665            prop_assert_eq!(bytes[6] >> 4, 7);
1666            prop_assert_eq!(bytes[6] & 0x0f, random[0] & 0x0f);
1667            prop_assert_eq!(bytes[7], random[1]);
1668            prop_assert_eq!(bytes[8] >> 6, 0b10);
1669            prop_assert_eq!(bytes[8] & 0x3f, random[2] & 0x3f);
1670            prop_assert_eq!(&bytes[9..], &random[3..]);
1671            prop_assert_eq!(canonical_text.len(), 36);
1672            let canonical_text_is_valid =
1673                canonical_text.bytes().enumerate().all(|(index, byte)| {
1674                if matches!(index, 8 | 13 | 18 | 23) {
1675                    byte == b'-'
1676                } else {
1677                    byte.is_ascii_digit() || matches!(byte, b'a'..=b'f')
1678                }
1679            });
1680            prop_assert!(canonical_text_is_valid);
1681
1682            macro_rules! assert_id {
1683                ($type:ty) => {{
1684                    let typed = <$type>::from_unix_milliseconds_and_random(
1685                        unix_milliseconds,
1686                        random,
1687                    )
1688                    .expect("generated timestamp is in range");
1689                    prop_assert_eq!(typed.into_bytes(), bytes);
1690                    prop_assert_eq!(<$type>::from_bytes(bytes), Ok(typed));
1691                    prop_assert_eq!(typed.to_string(), canonical_text.as_str());
1692                }};
1693            }
1694
1695            assert_id!(RequestId);
1696            assert_id!(AgentSessionId);
1697            assert_id!(IncidentId);
1698            assert_id!(DatabaseId);
1699            assert_id!(CapabilityId);
1700            assert_id!(ProvenanceId);
1701        }
1702
1703        #[test]
1704        fn uuid_v7_network_order_tracks_timestamp_for_equal_randomness(
1705            left in 0u64..=UUID_V7_MAX_UNIX_MILLISECONDS,
1706            right in 0u64..=UUID_V7_MAX_UNIX_MILLISECONDS,
1707            random in any::<[u8; 10]>(),
1708        ) {
1709            let left_id = RequestId::from_unix_milliseconds_and_random(left, random)
1710                .expect("generated timestamp is in range");
1711            let right_id = RequestId::from_unix_milliseconds_and_random(right, random)
1712                .expect("generated timestamp is in range");
1713            prop_assert_eq!(left.cmp(&right), left_id.cmp(&right_id));
1714
1715            macro_rules! assert_order {
1716                ($type:ty) => {{
1717                    let left_id = <$type>::from_unix_milliseconds_and_random(left, random)
1718                        .expect("generated timestamp is in range");
1719                    let right_id = <$type>::from_unix_milliseconds_and_random(right, random)
1720                        .expect("generated timestamp is in range");
1721                    prop_assert_eq!(left.cmp(&right), left_id.cmp(&right_id));
1722                }};
1723            }
1724
1725            assert_order!(AgentSessionId);
1726            assert_order!(IncidentId);
1727            assert_order!(DatabaseId);
1728            assert_order!(CapabilityId);
1729            assert_order!(ProvenanceId);
1730        }
1731    }
1732
1733    #[test]
1734    fn uuid_identifier_newtypes_are_distinct() {
1735        use std::any::TypeId;
1736
1737        let types = [
1738            TypeId::of::<RequestId>(),
1739            TypeId::of::<AgentSessionId>(),
1740            TypeId::of::<IncidentId>(),
1741            TypeId::of::<DatabaseId>(),
1742            TypeId::of::<CapabilityId>(),
1743            TypeId::of::<ProvenanceId>(),
1744        ];
1745        for (index, current) in types.iter().enumerate() {
1746            assert!(!types[..index].contains(current));
1747        }
1748    }
1749
1750    #[test]
1751    fn bounded_names_preserve_exact_text_and_validate_environment_alphabet() {
1752        let lineage = ContractLineage::new("Inventory.v2").expect("valid lineage");
1753        let environment = Environment::new("Prod_US-2.1").expect("valid environment");
1754        let tenant = TenantId::new("tenant-\u{00e9}").expect("valid tenant");
1755
1756        assert_eq!(lineage.as_str(), "Inventory.v2");
1757        assert_eq!(environment.as_str(), "Prod_US-2.1");
1758        assert_eq!(tenant.as_str(), "tenant-\u{00e9}");
1759        assert_eq!(
1760            Environment::new("prod/us"),
1761            Err(TextIdError::InvalidCharacter { index: 4 })
1762        );
1763        assert_eq!(
1764            Environment::new("prod \u{00e9}"),
1765            Err(TextIdError::InvalidCharacter { index: 4 })
1766        );
1767        assert_eq!(Environment::new(""), Err(TextIdError::Empty));
1768    }
1769
1770    #[test]
1771    fn tenant_debug_output_is_redacted() {
1772        let tenant = TenantId::new("customer-secret").expect("valid tenant");
1773        let scope = TenantScope::Tenant(tenant.clone());
1774
1775        let output = format!("{tenant:?} {scope:?}");
1776        assert!(!output.contains("customer-secret"));
1777        assert_eq!(format!("{:?}", TenantScope::Global), "TenantScope::Global");
1778    }
1779
1780    #[test]
1781    fn tenant_scope_identity_encoding_is_stable() {
1782        assert_eq!(TenantScope::Global.to_canonical_bytes(), [0]);
1783
1784        let scope = TenantScope::Tenant(TenantId::new("tenant-é").expect("valid tenant"));
1785        assert_eq!(
1786            scope.to_canonical_bytes(),
1787            [&[1, 0, 0, 0, 9][..], "tenant-é".as_bytes(),].concat()
1788        );
1789    }
1790
1791    #[test]
1792    fn sensitive_identifier_debug_output_is_redacted() {
1793        let actor = ActorId::new("principal-secret").expect("valid actor ID");
1794        let idempotency = IdempotencyKey::new("caller-secret").expect("valid key");
1795        let entity_key = EntityKey::from_bytes(
1796            [
1797                ENTITY_KEY_V1_PREFIX.as_slice(),
1798                &EntityTypeId::new(7).expect("nonzero").to_be_bytes(),
1799                b"business-key",
1800            ]
1801            .concat(),
1802        )
1803        .expect("valid key");
1804        let partition_key = PartitionKey::from_bytes(
1805            [
1806                PARTITION_KEY_V1_PREFIX.as_slice(),
1807                &AggregateTypeId::new(8).expect("nonzero").to_be_bytes(),
1808                b"business-partition",
1809            ]
1810            .concat(),
1811        )
1812        .expect("valid partition envelope");
1813        let index_key = IndexEntryKey::from_bytes(
1814            [
1815                INDEX_ENTRY_KEY_V1_PREFIX.as_slice(),
1816                &IndexId::new(9).expect("nonzero").to_be_bytes(),
1817                &[0, 0, 0, 6],
1818                ENTITY_KEY_V1_PREFIX.as_slice(),
1819                &EntityTypeId::new(7).expect("nonzero").to_be_bytes(),
1820            ]
1821            .concat(),
1822        )
1823        .expect("structurally bounded index envelope");
1824        let digest_key = DigestKey::from_bytes([0x5a; 32]);
1825
1826        let output = format!(
1827            "{actor:?} {idempotency:?} {entity_key:?} {partition_key:?} {index_key:?} {digest_key:?}"
1828        );
1829        assert!(!output.contains("principal-secret"));
1830        assert!(!output.contains("caller-secret"));
1831        assert!(!output.contains("business-key"));
1832        assert!(!output.contains("business-partition"));
1833        assert!(!output.contains("5a"));
1834    }
1835
1836    #[test]
1837    fn bounded_identifiers_reject_empty_and_oversized_values() {
1838        assert_eq!(ActorId::new(""), Err(TextIdError::Empty));
1839        assert!(matches!(
1840            IdempotencyKey::new("x".repeat(MAX_IDEMPOTENCY_KEY_BYTES + 1)),
1841            Err(TextIdError::TooLong { .. })
1842        ));
1843        assert!(matches!(
1844            ContractLineage::new("x".repeat(MAX_CONTRACT_LINEAGE_BYTES + 1)),
1845            Err(TextIdError::TooLong { .. })
1846        ));
1847        assert!(matches!(
1848            Environment::new("x".repeat(MAX_ENVIRONMENT_BYTES + 1)),
1849            Err(TextIdError::TooLong { .. })
1850        ));
1851        assert!(matches!(
1852            TenantId::new("x".repeat(MAX_TENANT_ID_BYTES + 1)),
1853            Err(TextIdError::TooLong { .. })
1854        ));
1855        assert_eq!(
1856            ConflictKey::from_bytes(vec![0; MAX_KEY_BYTES + 1]),
1857            Err(KeyBytesError::TooLong {
1858                actual: MAX_KEY_BYTES + 1,
1859                maximum: MAX_KEY_BYTES,
1860            })
1861        );
1862    }
1863
1864    #[test]
1865    fn typed_key_envelopes_reject_malformed_or_cross_purpose_bytes() {
1866        assert_eq!(
1867            EntityKey::from_bytes(Vec::new()),
1868            Err(KeyBytesError::TooShort {
1869                actual: 0,
1870                minimum: TYPED_KEY_ENVELOPE_BYTES,
1871            })
1872        );
1873        assert_eq!(
1874            EntityKey::from_bytes(vec![0x45, 0x02, 0, 0, 0, 1]),
1875            Err(KeyBytesError::UnsupportedVersion { version: 2 })
1876        );
1877        assert_eq!(
1878            EntityKey::from_bytes(vec![0x45, 0x01, 0, 0, 0, 0]),
1879            Err(KeyBytesError::ZeroTypeIdentity)
1880        );
1881        assert_eq!(
1882            EntityKey::from_bytes(vec![0x43, 0x01, 0, 0, 0, 1]),
1883            Err(KeyBytesError::WrongPurpose {
1884                expected: 0x45,
1885                actual: 0x43,
1886            })
1887        );
1888        assert_eq!(
1889            IndexEntryKey::from_bytes(vec![0x49, 0x01, 0, 0, 0, 1]),
1890            Err(KeyBytesError::TooShort {
1891                actual: TYPED_KEY_ENVELOPE_BYTES,
1892                minimum: INDEX_ENTRY_KEY_MIN_BYTES,
1893            })
1894        );
1895
1896        let entity =
1897            EntityKey::from_bytes(vec![0x45, 0x01, 1, 2, 3, 4]).expect("valid entity envelope");
1898        let conflict =
1899            ConflictKey::from_bytes(vec![0x43, 0x01, 5, 6, 7, 8]).expect("valid conflict envelope");
1900        let partition = PartitionKey::from_bytes(vec![0x50, 0x01, 9, 10, 11, 12])
1901            .expect("valid partition envelope");
1902        let index = IndexEntryKey::from_bytes(vec![
1903            0x49, 0x01, 13, 14, 15, 16, 0, 0, 0, 6, 0x45, 0x01, 0, 0, 0, 1,
1904        ])
1905        .expect("structurally bounded index envelope");
1906        assert_eq!(
1907            entity.entity_type_id(),
1908            EntityTypeId::new(0x0102_0304).expect("nonzero")
1909        );
1910        assert_eq!(
1911            conflict.aggregate_type_id(),
1912            AggregateTypeId::new(0x0506_0708).expect("nonzero")
1913        );
1914        assert_eq!(
1915            partition.aggregate_type_id(),
1916            AggregateTypeId::new(0x090a_0b0c).expect("nonzero")
1917        );
1918        assert_eq!(
1919            index.index_id(),
1920            IndexId::new(0x0d0e_0f10).expect("nonzero")
1921        );
1922    }
1923}