//! Support types for the Chapter 6 surface: the extension-facing registry //! identifiers, the [`AuthorId`], the [`ObjectKind`] discriminator, and the //! [`SerializedCanonicalInputs`] blob. //! //! The registry identifiers are the spec's escape hatches: every Chapter 6 //! enum that the core vocabulary does not close ends in a `Registered(...Id)` //! variant so a versioned extension registry can introduce its own values //! without a spec revision (Chapter 11 §"Extension Registry Contract"). The //! core does not interpret them; it only stores, orders, and hashes them, so a //! plain 128-bit opaque newtype is the whole contract. v0 loads no external //! registries (QUICKSTART "Don't … implement extension registries"); these //! types exist so the canonical forms that *mention* extensions stay total. use epiphany_determinism::{CanonicalByteOrder, CanonicalEncode}; /// Defines an opaque 128-bit registry/extension identifier newtype with the /// canonical 16-byte big-endian form and byte-lexicographic order shared by /// every identifier in the format (Appendix D §"Ordered Iteration"). macro_rules! registry_id { ($(#[$meta:meta])* $name:ident) => { $(#[$meta])* #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)] pub struct $name(pub u128); impl $name { /// Wraps a raw `u128`. #[inline] pub const fn from_raw(raw: u128) -> Self { $name(raw) } /// The raw `u128`. #[inline] pub const fn as_u128(self) -> u128 { self.0 } /// The canonical 16-byte big-endian form. #[inline] pub const fn canonical_bytes(self) -> [u8; 16] { self.0.to_be_bytes() } } impl CanonicalEncode for $name { #[inline] fn encode_canonical(&self, out: &mut Vec) { out.extend_from_slice(&self.canonical_bytes()); } } impl CanonicalByteOrder for $name {} }; } registry_id!( /// Identifies an extension-defined primitive operation kind /// ([`crate::OperationKind::Registered`]). OperationKindRegistryId ); registry_id!( /// Identifies an extension-defined conflict kind /// ([`crate::ConflictKind::ExtensionConflict`]). ConflictKindRegistryId ); registry_id!( /// Identifies an extension-defined conflict-resolution action /// ([`crate::ResolutionAction::Registered`]). ResolutionRegistryId ); registry_id!( /// Identifies an extension-defined repair kind /// ([`crate::RepairKind::Registered`]). RepairKindRegistryId ); registry_id!( /// Identifies an extension-defined re-anchor reason /// ([`crate::ReanchorReason::DeclaredByExtension`]). ReanchorReasonRegistryId ); registry_id!( /// Identifies an extension- or transport-defined replica-anomaly reason /// ([`crate::ReplicaAnomalyReason::Registered`]). ReplicaAnomalyRegistryId ); registry_id!( /// Identifies an extension- or transport-defined integrity anomaly /// ([`crate::IntegrityAnomalyKind::Registered`]). IntegrityAnomalyRegistryId ); registry_id!( /// Identifies an extension-declared precondition family; the extension's /// own catalog enumerates the specific codes /// ([`crate::PreconditionFailureReason::ExtensionPrecondition`]). ExtensionPreconditionId ); registry_id!( /// Identifies a registered precondition-failure code from a versioned /// registry ([`crate::PreconditionFailureReason::Registered`]). PreconditionFailureRegistryId ); /// The author of an operation (Chapter 6 §"Operation Envelopes"): "may differ /// from replica in shared authoring sessions where multiple authors share a /// replica." Modeled as an opaque 128-bit identifier; the core does not derive /// behavior from it, so it is provenance metadata, not part of operation /// identity (which is the [`epiphany_core::OperationId`]). #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)] pub struct AuthorId(pub u128); impl AuthorId { /// Wraps a raw `u128`. #[inline] pub const fn from_raw(raw: u128) -> Self { AuthorId(raw) } /// The canonical 16-byte big-endian form. #[inline] pub const fn canonical_bytes(self) -> [u8; 16] { self.0.to_be_bytes() } } impl CanonicalEncode for AuthorId { #[inline] fn encode_canonical(&self, out: &mut Vec) { out.extend_from_slice(&self.canonical_bytes()); } } impl CanonicalByteOrder for AuthorId {} /// The kind of object a *system-derived* identifier names, used by /// [`crate::IntegrityAnomalyKind::SystemIdentifierCollision`] to disambiguate /// a collided 64-bit counter within its kind (Chapter 5 §"System-Derived /// Counter Collisions": the collision is checked "within the same typed /// identifier kind"). /// /// Only the kinds that the spec actually derives into the /// [`epiphany_core::ReplicaId::SYSTEM_DERIVED`] namespace are enumerated — /// system-promoted [`Voice`](ObjectKind::Voice)s and content-derived synthetic /// [`Pitch`](ObjectKind::Pitch)es (Chapter 5) — plus a [`Registered`](ObjectKind::Registered) /// escape for extension-introduced system kinds. A non-system object kind /// cannot collide in this namespace, so it is intentionally not representable /// here. #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] pub enum ObjectKind { /// A system-promoted voice (`MUSCSVCE`). Voice, /// A content-derived synthetic pitch (`MUSCSPCH`). Pitch, /// An extension-introduced system-derived object kind. Registered(crate::support::OperationKindRegistryId), } impl ObjectKind { /// The discriminant byte for this kind. Part of the canonical bytes that /// feed an anomaly's identity, so it is fixed here. #[inline] pub fn discriminant(&self) -> u8 { match self { ObjectKind::Voice => 0, ObjectKind::Pitch => 1, ObjectKind::Registered(_) => 2, } } } impl CanonicalEncode for ObjectKind { fn encode_canonical(&self, out: &mut Vec) { out.push(self.discriminant()); if let ObjectKind::Registered(reg) = self { reg.encode_canonical(out); } } } /// The canonical input bytes that were hashed to derive a system counter /// (Chapter 5 §"System-Derived Counter Collisions": the two colliding input /// sets are retained so recovery can tell them apart). Opaque to the core. #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)] pub struct SerializedCanonicalInputs(pub Vec); impl CanonicalEncode for SerializedCanonicalInputs { #[inline] fn encode_canonical(&self, out: &mut Vec) { crate::encode::push_lp_bytes(out, &self.0); } } #[cfg(test)] mod tests { use super::*; use epiphany_determinism::sort_canonical; #[test] fn object_kind_discriminants_are_golden() { // RATIFIED by Pass 11 (item 2.4, req:graph:object-kind-vocab): the // discriminant byte feeds an IntegrityAnomaly's content-derived // identity, so the literal values are normative and must agree across // replicas. Lock them. assert_eq!(ObjectKind::Voice.discriminant(), 0); assert_eq!(ObjectKind::Pitch.discriminant(), 1); assert_eq!( ObjectKind::Registered(OperationKindRegistryId(0)).discriminant(), 2 ); } #[test] fn registry_ids_order_by_canonical_bytes() { let mut v = vec![ OperationKindRegistryId(3), OperationKindRegistryId(1), OperationKindRegistryId(2), ]; sort_canonical(&mut v); assert_eq!( v, vec![ OperationKindRegistryId(1), OperationKindRegistryId(2), OperationKindRegistryId(3), ] ); } #[test] fn object_kind_discriminant_separates_voice_and_pitch() { let mut a = Vec::new(); ObjectKind::Voice.encode_canonical(&mut a); let mut b = Vec::new(); ObjectKind::Pitch.encode_canonical(&mut b); assert_ne!(a, b); } }