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)]

//! Ring-level cycle traceability types.
//!
//! ## Extensible-by-convention vocabulary
//!
//! [`CycleKind`] is the shared transport-key vocabulary for ring cycles. Genesis
//! and Heartbeat are universal §13.1 ring-mechanism kinds present in every
//! soma(som) ring application. Login / Logout / Command / GateTransition are
//! application-tier protocol cycles whose names are reserved here as the
//! conventional shared vocabulary — applications consume them per their protocol,
//! other ring applications interpret per their own protocol or define
//! additional discriminator-disjoint variants in their application crate.
//!
//! The enum stays in soma-som-core because every ring application needs a stable
//! discriminator type for cycle classification; only the **interpretation** of
//! the application-tier variants is application-defined. This matches the
//! filename-vs-content + concept-vs-colocation cut-disciplines recorded at
//! 
//!
//! `CycleContext` is the per-cycle annotation payload (semantic hash + context
//! string); the `CycleAnnotator` trait in soma-governance produces it via
//! application-specific logic. soma-som-core defines the structural carrier;
//! soma-governance defines the application interpretation.

use serde::{Deserialize, Serialize};

/// The structural kind of a ring cycle.
///
/// **Spec:** OPUS §6 (Cycle) + §10.1 (audit trail).
/// Variants `Genesis` / `Heartbeat` are universal §13.1 ring-mechanism; other
/// variants are extensible-by-convention shared transport vocabulary (see module
/// doc).
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum CycleKind {
    Genesis,
    Heartbeat,
    Login,
    Logout,
    Command,
    GateTransition,
}

impl CycleKind {
    pub fn discriminator(&self) -> u8 {
        match self {
            CycleKind::Genesis => 0,
            CycleKind::Heartbeat => 1,
            CycleKind::Login => 2,
            CycleKind::Logout => 3,
            CycleKind::Command => 4,
            CycleKind::GateTransition => 5,
        }
    }
}

impl std::fmt::Display for CycleKind {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            CycleKind::Genesis => write!(f, "genesis"),
            CycleKind::Heartbeat => write!(f, "heartbeat"),
            CycleKind::Login => write!(f, "login"),
            CycleKind::Logout => write!(f, "logout"),
            CycleKind::Command => write!(f, "command"),
            CycleKind::GateTransition => write!(f, "gate_transition"),
        }
    }
}

/// Semantic envelope for a ring cycle — tamper-evident traceability context.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CycleContext {
    pub cycle_class: CycleKind,
    pub command_type: Option<String>,
    pub actor: Option<String>,
    pub request_id: Option<String>,
    pub authorization_outcome: Option<String>,
    pub detail: Option<String>,
}

impl CycleContext {
    pub fn genesis() -> Self {
        Self { cycle_class: CycleKind::Genesis, command_type: None, actor: None, request_id: None, authorization_outcome: None, detail: None }
    }
    pub fn heartbeat() -> Self {
        Self { cycle_class: CycleKind::Heartbeat, command_type: None, actor: None, request_id: None, authorization_outcome: None, detail: None }
    }
    pub fn login(actor: impl Into<String>, outcome: impl Into<String>) -> Self {
        Self { cycle_class: CycleKind::Login, command_type: Some("login".into()), actor: Some(actor.into()), request_id: None, authorization_outcome: Some(outcome.into()), detail: None }
    }
    pub fn logout(actor: impl Into<String>) -> Self {
        Self { cycle_class: CycleKind::Logout, command_type: Some("session.logout".into()), actor: Some(actor.into()), request_id: None, authorization_outcome: Some("invalidated".into()), detail: None }
    }
    pub fn command(command_type: impl Into<String>, actor: impl Into<String>, request_id: impl Into<String>, authorization_outcome: impl Into<String>) -> Self {
        Self { cycle_class: CycleKind::Command, command_type: Some(command_type.into()), actor: Some(actor.into()), request_id: Some(request_id.into()), authorization_outcome: Some(authorization_outcome.into()), detail: None }
    }
    pub fn gate_transition(actor: impl Into<String>, request_id: impl Into<String>, authorization_outcome: impl Into<String>, detail: impl Into<String>) -> Self {
        Self { cycle_class: CycleKind::GateTransition, command_type: Some("gate.transition".into()), actor: Some(actor.into()), request_id: Some(request_id.into()), authorization_outcome: Some(authorization_outcome.into()), detail: Some(detail.into()) }
    }
    pub fn with_detail(mut self, detail: impl Into<String>) -> Self {
        self.detail = Some(detail.into());
        self
    }
    pub fn semantic_hash(&self) -> [u8; 32] {
        let mut hasher = blake3::Hasher::new();
        hasher.update(b"soma.traceability.v1");
        hasher.update(&[self.cycle_class.discriminator()]);
        hash_optional(&mut hasher, &self.command_type);
        hash_optional(&mut hasher, &self.actor);
        hash_optional(&mut hasher, &self.request_id);
        hash_optional(&mut hasher, &self.authorization_outcome);
        hash_optional(&mut hasher, &self.detail);
        *hasher.finalize().as_bytes()
    }
}

fn hash_optional(hasher: &mut blake3::Hasher, field: &Option<String>) {
    match field {
        None => { hasher.update(&[0x00]); }
        Some(s) => {
            hasher.update(&[0x01]);
            hasher.update(&(s.len() as u32).to_le_bytes());
            hasher.update(s.as_bytes());
        }
    }
}

pub const LEGACY_SEMANTIC_HASH: [u8; 32] = [0u8; 32];

use crate::quad::Tree;

/// Pluggable cycle traceability annotation.
///
/// The ring appends the returned bytes to each crossing record verbatim.
/// Interpretation is application-defined.
pub trait CycleAnnotator: Send + Sync {
    fn annotate(&self, tree: &Tree, cycle: u64) -> Vec<u8>;
}

/// No-op default — emits empty annotations.
pub struct NullAnnotator;

impl CycleAnnotator for NullAnnotator {
    fn annotate(&self, _tree: &Tree, _cycle: u64) -> Vec<u8> {
        vec![]
    }
}