epiphany/crates/epiphany-determinism/src/domain.rs

334 lines
14 KiB
Rust

//! Domain-separation tags.
//!
//! Epiphany hashes domain-separated preimages so that two semantically
//! different chunks with identical raw bytes never share a content address
//! (Chapter 8 §"Domain-Separated Preimages"). Every tag is a fixed 8-byte
//! ASCII string beginning with `MUSC`. Centralizing them here keeps the set
//! drift-free: there is exactly one definition of each tag in the workspace.
/// A fixed 8-byte domain-separation tag. Always the first bytes of a hash
/// preimage (see [`crate::Preimage`]).
///
/// The spec's domain-tag vocabulary is closed: the reserved built-ins
/// ([`DomainTag::BUILTINS`]) plus extension-introduced *system-derived* tags,
/// which "MUST begin with `MUSCS` and have length exactly 8 bytes" (Chapter 5).
/// Every tag is an 8-byte ASCII string. The field is private and the
/// constructors enforce that vocabulary, so a nonconforming tag — wrong prefix
/// (`b"BAD_TAG!"`), non-ASCII bytes, or an unregistered `MUSC....` domain —
/// cannot be minted and therefore cannot reach [`crate::derive_system_counter`]
/// or a hash preimage. To mint an extension's own tag use [`SystemDomainTag`].
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct DomainTag([u8; 8]);
impl DomainTag {
/// Length of every domain tag, in bytes.
pub const LEN: usize = 8;
/// The prefix marking a *system-derived* tag (Chapter 5:
/// "Additional domain tags introduced by registered extensions MUST begin
/// with `MUSCS` and have length exactly 8 bytes"). The three built-in
/// system tags ([`Self::SYSTEM_VOICE`], [`Self::SYSTEM_PITCH`],
/// [`Self::SYSTEM_ANOMALY`]) also carry it.
const SYSTEM_PREFIX: &'static [u8] = b"MUSCS";
/// The raw 8 ASCII bytes.
#[inline]
pub const fn as_bytes(&self) -> &[u8; 8] {
&self.0
}
// --- Built-in tags (Chapter 8 §"Domain-Separated Preimages", Ch. 5/6). ---
/// `.musc` chunk payloads.
pub const CHUNK: DomainTag = DomainTag(*b"MUSCCHNK");
/// Manifest chunk payloads.
pub const MANIFEST: DomainTag = DomainTag(*b"MUSCMANI");
/// Blob payloads; a `BlobId` is the [`crate::ContentHash`] under this tag.
pub const BLOB: DomainTag = DomainTag(*b"MUSCBLOB");
/// `ConflictId` derivation (Chapter 6 §"Conflict Identity").
pub const CONFLICT: DomainTag = DomainTag(*b"MUSCCONF");
/// Canonical operation-envelope hash, `EnvelopeHash` (Chapter 6 §6.5).
pub const ENVELOPE: DomainTag = DomainTag(*b"MUSCENVH");
/// Glyph-catalog metrics hash (Chapter 7 §"Glyph Catalog Identity").
pub const FONT_METRICS: DomainTag = DomainTag(*b"MUSCFNTM");
/// `ManifestId` derivation (Chapter 8 / deferred-types table).
pub const MANIFEST_ID: DomainTag = DomainTag(*b"MUSCMNIF");
/// System-promoted voice counter derivation (Chapter 5 §"System-Derived").
pub const SYSTEM_VOICE: DomainTag = DomainTag(*b"MUSCSVCE");
/// System-derived pitch counter derivation (Chapter 5 §"System-Derived").
pub const SYSTEM_PITCH: DomainTag = DomainTag(*b"MUSCSPCH");
/// `IntegrityAnomalyId` derivation (Chapter 5 §"System-Derived Counter
/// Collisions"). Reserved built-in: anomalies are core, not an extension
/// concern (Pass 11, item 1.4).
pub const SYSTEM_ANOMALY: DomainTag = DomainTag(*b"MUSCSANM");
/// `LayoutObjectId` derivation (Chapter 7 §"Provenance",
/// Requirement `req:layoutir:object-id-derivation`). Like
/// [`Self::FONT_METRICS`], this is a reserved built-in but **non-canonical**:
/// layout-object ids are not document state and enter no content hash, so the
/// tag lives in the layout namespace, not among the canonical system tags
/// (Chapter 8 §"Domain-tag registry").
pub const LAYOUT_OBJECT_ID: DomainTag = DomainTag(*b"MUSCLOID");
/// Every built-in tag, in declaration order. The closed core vocabulary
/// (the nine canonical tags plus the non-canonical layout tags
/// [`Self::FONT_METRICS`] and [`Self::LAYOUT_OBJECT_ID`]).
pub const BUILTINS: [DomainTag; 11] = [
Self::CHUNK,
Self::MANIFEST,
Self::BLOB,
Self::CONFLICT,
Self::ENVELOPE,
Self::FONT_METRICS,
Self::MANIFEST_ID,
Self::SYSTEM_VOICE,
Self::SYSTEM_PITCH,
Self::SYSTEM_ANOMALY,
Self::LAYOUT_OBJECT_ID,
];
/// Constructs a domain tag from raw bytes, accepting only the spec's closed
/// vocabulary: a reserved built-in, or a well-formed extension system tag
/// (begins `MUSCS`). Every byte must be printable ASCII. Returns `None`
/// otherwise — wrong prefix, non-ASCII bytes, or an unregistered
/// `MUSC....` domain that is neither built-in nor a `MUSCS` system tag.
/// This is the checked entry point for decoding a tag from storage/interop.
#[inline]
pub fn from_bytes(raw: [u8; 8]) -> Option<Self> {
if Self::is_valid_bytes(&raw) {
Some(DomainTag(raw))
} else {
None
}
}
/// Validity predicate for the closed vocabulary: printable-ASCII, not a
/// file-format magic byte string, and either a registered built-in or a
/// `MUSCS`-prefixed system tag.
#[inline]
fn is_valid_bytes(raw: &[u8; 8]) -> bool {
if !raw.iter().all(u8::is_ascii_graphic) {
return false;
}
if Self::is_file_magic(raw) {
return false;
}
DomainTag::BUILTINS.iter().any(|b| b.as_bytes() == raw)
|| raw.starts_with(Self::SYSTEM_PREFIX)
}
/// File-format magic byte strings are in the same 8-byte `MUSC*`
/// namespace, but they are not hash-domain tags and must not be reused by
/// extension system identifiers.
#[inline]
fn is_file_magic(raw: &[u8; 8]) -> bool {
raw == &BUNDLE_MAGIC || raw == &SUPERBLOCK_MAGIC
}
/// Whether this is one of the reserved built-in tags ([`Self::BUILTINS`]).
#[inline]
pub fn is_builtin(&self) -> bool {
Self::BUILTINS.contains(self)
}
/// Whether this is a *system-derived* tag (begins `MUSCS`): a built-in
/// [`Self::SYSTEM_VOICE`] / [`Self::SYSTEM_PITCH`] / [`Self::SYSTEM_ANOMALY`]
/// or an extension tag minted via [`SystemDomainTag::new_extension`].
#[inline]
pub fn is_system_derived(&self) -> bool {
self.0.starts_with(Self::SYSTEM_PREFIX)
}
/// Whether this is an *extension-introduced* system tag: system-derived and
/// not a reserved built-in. The three built-in system tags return `false`
/// here — they are reserved, not extension-introduced.
#[inline]
pub fn is_extension_system_tag(&self) -> bool {
self.is_system_derived() && !self.is_builtin()
}
}
/// A [`DomainTag`] proven to be *system-derived* (begins `MUSCS`): a built-in
/// [`DomainTag::SYSTEM_VOICE`] / [`DomainTag::SYSTEM_PITCH`] /
/// [`DomainTag::SYSTEM_ANOMALY`], or an
/// extension-introduced tag. Only these are admissible seeds for
/// [`crate::derive_system_counter`] (Chapter 5 §"System-Derived Identifiers").
///
/// Carrying the precondition in the type — rather than checking it at the call
/// site — makes `derive_system_counter` total: it is impossible to seed a
/// system identifier from, say, [`DomainTag::CHUNK`], because that value cannot
/// be turned into a `SystemDomainTag`.
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct SystemDomainTag(DomainTag);
impl SystemDomainTag {
/// Built-in: system-promoted voice counters (`MUSCSVCE`).
pub const VOICE: SystemDomainTag = SystemDomainTag(DomainTag::SYSTEM_VOICE);
/// Built-in: system-derived pitch counters (`MUSCSPCH`).
pub const PITCH: SystemDomainTag = SystemDomainTag(DomainTag::SYSTEM_PITCH);
/// Built-in: integrity-anomaly identifiers (`MUSCSANM`).
pub const ANOMALY: SystemDomainTag = SystemDomainTag(DomainTag::SYSTEM_ANOMALY);
/// Wraps a domain tag if it is system-derived; returns `None` otherwise.
#[inline]
pub fn new(tag: DomainTag) -> Option<Self> {
if tag.is_system_derived() {
Some(SystemDomainTag(tag))
} else {
None
}
}
/// Mints an *extension-introduced* system-derived tag from raw bytes,
/// enforcing the Chapter 5 rule: printable ASCII, begins `MUSCS`, and does
/// not collide with a reserved built-in. The only sanctioned way for an
/// extension to introduce its own system-derived domain tag.
#[inline]
pub fn new_extension(raw: [u8; 8]) -> Option<Self> {
let tag = DomainTag::from_bytes(raw)?;
if tag.is_extension_system_tag() {
Some(SystemDomainTag(tag))
} else {
None
}
}
/// The underlying domain tag.
#[inline]
pub const fn tag(self) -> DomainTag {
self.0
}
/// The raw 8 ASCII bytes.
#[inline]
pub const fn as_bytes(&self) -> &[u8; 8] {
self.0.as_bytes()
}
}
// --- File-format magic byte strings (Chapter 8 §"The Bundle Layout"). ---
//
// These are not hashing domain tags; they are the literal magic bytes that
// open the fixed header and the superblock slots. They are centralized here
// alongside the domain tags so the full set of 8-byte `MUSC*` constants has a
// single home. `epiphany-bundle` (Agent D) consumes them.
/// Bundle fixed-header magic: ASCII `"MUSCBND\0"` (8 bytes, trailing NUL).
pub const BUNDLE_MAGIC: [u8; 8] = *b"MUSCBND\0";
/// Superblock-slot magic: ASCII `"MUSCSUPR"` (8 bytes).
pub const SUPERBLOCK_MAGIC: [u8; 8] = *b"MUSCSUPR";
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn every_tag_is_eight_ascii_bytes_starting_with_musc() {
for t in DomainTag::BUILTINS {
assert_eq!(t.as_bytes().len(), DomainTag::LEN);
assert!(t.as_bytes().starts_with(b"MUSC"), "{t:?}");
assert!(t.as_bytes().iter().all(|b| b.is_ascii()), "{t:?}");
}
}
#[test]
fn tags_are_pairwise_distinct() {
let tags = DomainTag::BUILTINS;
for (i, a) in tags.iter().enumerate() {
for b in &tags[i + 1..] {
assert_ne!(a, b, "duplicate domain tag {a:?}");
}
}
}
#[test]
fn exact_tag_spellings_match_spec() {
// Locked literally against Chapter 8 / Chapter 5 / Chapter 6.
assert_eq!(DomainTag::CHUNK.as_bytes(), b"MUSCCHNK");
assert_eq!(DomainTag::MANIFEST.as_bytes(), b"MUSCMANI");
assert_eq!(DomainTag::BLOB.as_bytes(), b"MUSCBLOB");
assert_eq!(DomainTag::CONFLICT.as_bytes(), b"MUSCCONF");
assert_eq!(DomainTag::ENVELOPE.as_bytes(), b"MUSCENVH");
assert_eq!(DomainTag::FONT_METRICS.as_bytes(), b"MUSCFNTM");
assert_eq!(DomainTag::MANIFEST_ID.as_bytes(), b"MUSCMNIF");
assert_eq!(DomainTag::SYSTEM_VOICE.as_bytes(), b"MUSCSVCE");
assert_eq!(DomainTag::SYSTEM_PITCH.as_bytes(), b"MUSCSPCH");
assert_eq!(DomainTag::SYSTEM_ANOMALY.as_bytes(), b"MUSCSANM");
assert_eq!(DomainTag::LAYOUT_OBJECT_ID.as_bytes(), b"MUSCLOID");
assert_eq!(&BUNDLE_MAGIC, b"MUSCBND\0");
assert_eq!(&SUPERBLOCK_MAGIC, b"MUSCSUPR");
}
#[test]
fn builtin_system_tags_are_not_extension_tags() {
// They are system-derived (begin MUSCS)...
assert!(DomainTag::SYSTEM_VOICE.is_system_derived());
assert!(DomainTag::SYSTEM_PITCH.is_system_derived());
assert!(DomainTag::SYSTEM_ANOMALY.is_system_derived());
// ...but reserved built-ins, NOT extension-introduced.
assert!(DomainTag::SYSTEM_VOICE.is_builtin());
assert!(DomainTag::SYSTEM_ANOMALY.is_builtin());
assert!(!DomainTag::SYSTEM_VOICE.is_extension_system_tag());
assert!(!DomainTag::SYSTEM_PITCH.is_extension_system_tag());
assert!(!DomainTag::SYSTEM_ANOMALY.is_extension_system_tag());
// A non-system tag is neither.
assert!(!DomainTag::CHUNK.is_system_derived());
assert!(!DomainTag::CHUNK.is_extension_system_tag());
}
#[test]
fn from_bytes_accepts_only_the_closed_vocabulary() {
// Wrong prefix.
assert!(DomainTag::from_bytes(*b"BAD_TAG!").is_none());
assert!(DomainTag::from_bytes(*b"SHA2CHNK").is_none());
// Right format prefix but unregistered, non-system domain.
assert!(DomainTag::from_bytes(*b"MUSCWXYZ").is_none());
// Non-ASCII payload byte (0xFF) is rejected even with a MUSC prefix.
assert!(DomainTag::from_bytes([b'M', b'U', b'S', b'C', 0xFF, b'A', b'B', b'C']).is_none());
// Control byte (NUL) is not printable ASCII.
assert!(DomainTag::from_bytes(*b"MUSCS\0\0\0").is_none());
// Built-in: accepted.
assert_eq!(
DomainTag::from_bytes(*b"MUSCCHNK").unwrap(),
DomainTag::CHUNK
);
// The layout-object-id tag is a registered (non-system) built-in.
assert_eq!(
DomainTag::from_bytes(*b"MUSCLOID").unwrap(),
DomainTag::LAYOUT_OBJECT_ID
);
assert!(DomainTag::LAYOUT_OBJECT_ID.is_builtin());
assert!(!DomainTag::LAYOUT_OBJECT_ID.is_system_derived());
// Extension system tag: accepted.
assert!(DomainTag::from_bytes(*b"MUSCSEXT")
.unwrap()
.is_extension_system_tag());
}
#[test]
fn system_domain_tag_enforces_the_chapter5_rule() {
// Must begin MUSCS.
assert!(SystemDomainTag::new_extension(*b"MUSCXXXX").is_none());
// Must not collide with a reserved built-in.
assert!(SystemDomainTag::new_extension(*b"MUSCSVCE").is_none());
// MUSCSANM is now a reserved built-in too (Pass 11): not extension-mintable.
assert!(SystemDomainTag::new_extension(*b"MUSCSANM").is_none());
// Non-ASCII rejected.
assert!(
SystemDomainTag::new_extension([b'M', b'U', b'S', b'C', b'S', 0xFF, b'A', b'B'])
.is_none()
);
// File-format magic strings are reserved in the shared MUSC* namespace.
assert!(SystemDomainTag::new_extension(SUPERBLOCK_MAGIC).is_none());
// A genuine extension tag is accepted and classified correctly.
let ext = SystemDomainTag::new_extension(*b"MUSCSEXT").unwrap();
assert!(ext.tag().is_extension_system_tag());
// Built-in system tags wrap; non-system tags do not.
assert_eq!(SystemDomainTag::VOICE.tag(), DomainTag::SYSTEM_VOICE);
assert_eq!(SystemDomainTag::ANOMALY.tag(), DomainTag::SYSTEM_ANOMALY);
assert!(SystemDomainTag::new(DomainTag::SYSTEM_PITCH).is_some());
assert!(SystemDomainTag::new(DomainTag::SYSTEM_ANOMALY).is_some());
assert!(SystemDomainTag::new(DomainTag::CHUNK).is_none());
}
}