//! Joule Code schema: content addressing, canonical serialization, shared types. //! //! Implements SPEC §3 (identity / content addressing) and §4 (canonical //! serialization), plus the shared [`Tier`] type used across the cargo, //! receipt, and cascade crates. use base64::Engine as _; use serde::{Deserialize, Serialize}; /// Errors from canonical encoding / addressing. #[derive(Debug, thiserror::Error)] pub enum SchemaError { /// Canonical CBOR encoding failed. #[error("canonical encoding failed: {0}")] Encode(String), } /// A content address: `BLAKE3-256` of a canonical serialization, rendered as /// `"b3:" + unpadded base64url` (SPEC §3). #[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct ContentId(pub String); impl ContentId { /// The address of raw canonical bytes. pub fn of_bytes(canonical: &[u8]) -> Self { let hash = blake3::hash(canonical); let b64 = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(hash.as_bytes()); ContentId(format!("b3:{b64}")) } /// The address of any value via its canonical CBOR form (SPEC §4). pub fn of_canonical(value: &T) -> Result { Ok(Self::of_bytes(&canonical_cbor(value)?)) } /// The textual form, e.g. `"b3:AAAA..."`. pub fn as_str(&self) -> &str { &self.0 } } impl std::fmt::Display for ContentId { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(&self.0) } } /// Canonical serialization (SPEC §4): deterministic CBOR. Our load-bearing /// types are structs with a fixed field order and contain no floats and no /// hash-randomized maps, so `ciborium`'s encoding is deterministic across /// platforms for equal values. pub fn canonical_cbor(value: &T) -> Result, SchemaError> { let mut buf = Vec::new(); ciborium::ser::into_writer(value, &mut buf).map_err(|e| SchemaError::Encode(e.to_string()))?; Ok(buf) } /// The energy-tiered cascade tiers (SPEC §1.1). Inherited from JouleClaw. #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] pub enum Tier { /// Content-addressed cache hit (picojoules). L0, /// Deterministic primitive (nanojoules). L1, /// Embedding + hybrid retrieval (sub-millijoules). L2, /// Local model (joules). L3, /// Remote frontier agent over ACP (tens of joules); the escape hatch. L4, } impl Tier { /// The wire tag, e.g. `"L3"`. pub fn wire_tag(self) -> &'static str { match self { Tier::L0 => "L0", Tier::L1 => "L1", Tier::L2 => "L2", Tier::L3 => "L3", Tier::L4 => "L4", } } } impl std::fmt::Display for Tier { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(self.wire_tag()) } } /// The one-byte algorithm identifier for signatures (SPEC §2). `0x01` = /// Ed25519 (RFC 8032). A verifier MUST fail closed on an unknown value. pub const ALG_ED25519: u8 = 0x01; #[cfg(test)] mod tests { use super::*; #[test] fn content_id_is_stable_and_prefixed() { let a = ContentId::of_bytes(b"hello"); let b = ContentId::of_bytes(b"hello"); assert_eq!(a, b); assert!(a.as_str().starts_with("b3:")); } #[test] fn canonical_cbor_is_deterministic() { let x = (1u8, Tier::L3, "edit"); assert_eq!(canonical_cbor(&x).unwrap(), canonical_cbor(&x).unwrap()); } }