soma-som-core 0.1.0

Universal soma(som) structural primitives — Quad / Tree / Ring / Genesis / Fingerprint / TemporalLedger / CrossingRecord
Documentation
// SPDX-License-Identifier: LGPL-3.0-only
#![allow(missing_docs)]

//! The Quad — universal structural element of SOMA/SOM.
//!
//! ## Spec traceability
//! - Definition 1 (Spec §2): Q = (R, P, T)
//! - Definition 3 (Spec §2.4): Tree as sphere of available references
//! - Tree could be implemented as `HashMap<String, Vec<u8>>`

use std::collections::BTreeMap;

use serde::{Deserialize, Serialize};

/// The Tree element: a flat key-value store of available references.
///
/// `BTreeMap` is used instead of `HashMap`
/// instead for **deterministic iteration order**, which is critical for
/// fingerprint computation (Spec Definition 7): the fingerprint must be
/// identical regardless of insertion order.
///
/// Keys are dot-delimited paths (e.g., `"session.token"`, `"env.connection_id"`).
/// Values are opaque byte vectors.
///
/// # Example
///
/// ```
/// use soma_som_core::quad::Tree;
///
/// let mut tree = Tree::new();
/// tree.insert("command.type".into(), b"session.logout".to_vec());
/// tree.insert("command.request_id".into(), b"req-42".to_vec());
///
/// // BTreeMap guarantees deterministic key ordering — critical for fingerprints.
/// let keys: Vec<&String> = tree.keys().collect();
/// assert_eq!(keys, vec!["command.request_id", "command.type"]);
/// ```
pub type Tree = BTreeMap<String, Vec<u8>>;

/// A Quad: the universal structural element of SOMA/SOM.
///
/// ## Spec Definition 1
///
/// > A Quad is an ordered triple: Q = (R, P, T) where R (Root) is position,
/// > P (Pointer) is focus and binding key to the next layer, and T (Tree) is
/// > the sphere of available references.
///
/// The Quad is used at two structural levels:
/// - **SOM Quad**: one per (unit, layer) pair — 24 total. Carries layer-specific state.
/// - **SOMA Quad**: one per unit — 6 total. Carries unit-level structural state on the vertical axis.
///
/// ## The body problem (Spec §2.3)
///
/// A Quad embedded in the ring has an *external key* — the predecessor's Pointer
/// value that references it. This external key is **not part of the Quad**.
/// The type system enforces this: `Quad` contains only `(root, pointer, tree)`.
/// There is no field for the external key, and there cannot be one without
/// violating the ring's directed topology (Proposition 2, Spec §2.3).
///
/// # Example
///
/// ```
/// use soma_som_core::quad::{Quad, Tree};
///
/// // Empty Quad — used as the genesis-cycle placeholder.
/// let q0 = Quad::empty();
/// assert!(q0.is_empty());
///
/// // Build a Quad from string-hashed root/pointer plus a Tree payload.
/// let mut tree = Tree::new();
/// tree.insert("env.connection_id".into(), b"abc-123".to_vec());
/// let q1 = Quad::from_strings("origin", "next", tree);
///
/// // content_hash is deterministic over (root, pointer, tree).
/// assert_eq!(q1.content_hash(), q1.content_hash());
/// ```
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Quad {
    /// Root (R): position / origin / current key. Where you stand.
    ///
    /// Represented as a 32-byte BLAKE3 hash in the protocol.
    /// BLAKE3 hash of the position's semantic content.
    pub root: [u8; 32],

    /// Pointer (P): focus / binding key to the next layer.
    ///
    /// The Pointer is the key that opens the next SOM layer.
    /// Data.P → Server, Server.P → Client, Client.P → Interface.
    ///
    /// In a vertical transition, the Server's Pointer routes to the
    /// next unit's Server (Invariant 4: server backbone).
    pub pointer: [u8; 32],

    /// Tree (T): the sphere of available references.
    ///
    /// A flat key-value store. Contains the actual state and
    /// key material at this position. The effective key space of the
    /// system scales with Tree cardinality (Spec §8.6).
    pub tree: Tree,
}

impl Quad {
    /// Create a new Quad with the given root, pointer, and tree.
    pub fn new(root: [u8; 32], pointer: [u8; 32], tree: Tree) -> Self {
        Self {
            root,
            pointer,
            tree,
        }
    }

    /// Create an empty Quad (all zeros, empty tree).
    /// Used as a placeholder during genesis initialization.
    pub fn empty() -> Self {
        Self {
            root: [0u8; 32],
            pointer: [0u8; 32],
            tree: Tree::new(),
        }
    }

    /// Create a Quad from string values, hashing them into the 32-byte fields.
    /// Convenience for testing and stub processors.
    pub fn from_strings(root: &str, pointer: &str, tree: Tree) -> Self {
        Self {
            root: blake3::hash(root.as_bytes()).into(),
            pointer: blake3::hash(pointer.as_bytes()).into(),
            tree,
        }
    }

    /// Compute a deterministic hash of this Quad's complete content.
    ///
    /// This is used in fingerprint computation (Spec Definition 7).
    /// The hash includes root, pointer, and all tree entries in
    /// deterministic key order (guaranteed by BTreeMap).
    pub fn content_hash(&self) -> [u8; 32] {
        let mut hasher = blake3::Hasher::new();
        hasher.update(&self.root);
        hasher.update(&self.pointer);
        // Tree entries in deterministic order (BTreeMap guarantees this)
        for (key, value) in &self.tree {
            hasher.update(key.as_bytes());
            hasher.update(&(value.len() as u64).to_le_bytes());
            hasher.update(value);
        }
        *hasher.finalize().as_bytes()
    }

    /// Returns true if this Quad has no meaningful content.
    pub fn is_empty(&self) -> bool {
        self.root == [0u8; 32] && self.pointer == [0u8; 32] && self.tree.is_empty()
    }
}

impl Default for Quad {
    fn default() -> Self {
        Self::empty()
    }
}

// inline: exercises module-private items via super::*
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn empty_quad_is_empty() {
        let q = Quad::empty();
        assert!(q.is_empty());
    }

    #[test]
    fn from_strings_produces_nonzero_hashes() {
        let q = Quad::from_strings("origin", "next", Tree::new());
        assert_ne!(q.root, [0u8; 32]);
        assert_ne!(q.pointer, [0u8; 32]);
    }

    #[test]
    fn content_hash_is_deterministic() {
        let mut tree = Tree::new();
        tree.insert("a".into(), vec![1, 2, 3]);
        tree.insert("b".into(), vec![4, 5]);
        let q = Quad::from_strings("root", "ptr", tree.clone());

        // Same content → same hash
        let q2 = Quad::from_strings("root", "ptr", tree);
        assert_eq!(q.content_hash(), q2.content_hash());
    }

    #[test]
    fn content_hash_changes_on_any_modification() {
        let mut tree = Tree::new();
        tree.insert("key".into(), vec![1, 2, 3]);
        let q1 = Quad::from_strings("root", "ptr", tree.clone());

        // Change root
        let q2 = Quad::from_strings("root2", "ptr", tree.clone());
        assert_ne!(q1.content_hash(), q2.content_hash());

        // Change pointer
        let q3 = Quad::from_strings("root", "ptr2", tree.clone());
        assert_ne!(q1.content_hash(), q3.content_hash());

        // Change tree value
        let mut tree2 = tree.clone();
        tree2.insert("key".into(), vec![1, 2, 4]);
        let q4 = Quad::from_strings("root", "ptr", tree2);
        assert_ne!(q1.content_hash(), q4.content_hash());

        // Add tree key
        let mut tree3 = tree;
        tree3.insert("key2".into(), vec![9]);
        let q5 = Quad::from_strings("root", "ptr", tree3);
        assert_ne!(q1.content_hash(), q5.content_hash());
    }

    #[test]
    fn quad_does_not_have_external_key_field() {
        // This is a compile-time guarantee, but we document the intent:
        // The Quad type has exactly three fields: root, pointer, tree.
        // There is no field for the predecessor's reference (the external key).
        // This is the type-level encoding of the body problem (Spec §2.3).
        let q = Quad::empty();
        let _ = q.root;
        let _ = q.pointer;
        let _ = q.tree;
        // No q.external_key — it does not exist in the type.
    }
}