236 lines
8.1 KiB
Rust
236 lines
8.1 KiB
Rust
//! 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<u8>) {
|
|
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<u8>) {
|
|
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<u8>) {
|
|
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<u8>);
|
|
|
|
impl CanonicalEncode for SerializedCanonicalInputs {
|
|
#[inline]
|
|
fn encode_canonical(&self, out: &mut Vec<u8>) {
|
|
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);
|
|
}
|
|
}
|