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 Envelope: foundational wire type for ring crossings (Contracts §2).
//!
//! ## Spec traceability
//! - Contracts §2: "The Quad *is* the envelope. No wrapper is needed."
//! - Contracts §2.1: crossing metadata is NOT part of the Quad
//! - SAD §7.1: wire envelope structure
//!
//! `CrossingRecord` is defined in soma-som-core. The Envelope follows:
//! it wraps only soma-som-core types (Quad, CrossingRecord, UnitId, CrossingType,
//! Layer) with no UDS or transport dependency in the struct itself.

use serde::{Deserialize, Serialize};

use crate::crossing::CrossingRecord;
use crate::quad::{Quad, Tree};
use crate::types::{CrossingType, Layer, UnitId};

/// The wire envelope — what crosses the IPC boundary.
///
/// ## Structure (SAD Listing 1, adapted)
///
/// ```text
/// Envelope {
///     cycle_index:     u64     — which cycle this belongs to
///     source_unit:     UnitId  — the producing unit
///     quad:            Quad    — the structural payload (R, P, T)
///     crossing_record: Option<CrossingRecord> — filled by boundary
///     target_layer:    Option<Layer> — processing dispatch hint (M4 P2)
/// }
/// ```
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Envelope {
    /// The cycle index this envelope belongs to (0 = genesis).
    pub cycle_index: u64,

    /// The unit that produced this envelope.
    pub source_unit: UnitId,

    /// Vertical or horizontal crossing type.
    pub crossing_type: CrossingType,

    /// The structural payload: Q = (R, P, T).
    pub quad: Quad,

    /// The crossing record, filled by the boundary. None when produced.
    pub crossing_record: Option<CrossingRecord>,

    /// Processing dispatch hint for multi-process mode (M4 Phase 2).
    #[serde(default)]
    pub target_layer: Option<Layer>,
}

impl Envelope {
    /// Create a new envelope from a producer (vertical, no crossing record).
    pub fn new(cycle_index: u64, source_unit: UnitId, quad: Quad) -> Self {
        Self {
            cycle_index,
            source_unit,
            crossing_type: CrossingType::Vertical,
            quad,
            crossing_record: None,
            target_layer: None,
        }
    }

    /// Create with explicit crossing type.
    pub fn with_crossing_type(
        cycle_index: u64,
        source_unit: UnitId,
        crossing_type: CrossingType,
        quad: Quad,
    ) -> Self {
        Self {
            cycle_index,
            source_unit,
            crossing_type,
            quad,
            crossing_record: None,
            target_layer: None,
        }
    }

    /// Create with explicit target layer (M4 Phase 2, multi-process mode).
    pub fn with_target_layer(
        cycle_index: u64,
        source_unit: UnitId,
        quad: Quad,
        target_layer: Layer,
    ) -> Self {
        Self {
            cycle_index,
            source_unit,
            crossing_type: CrossingType::Vertical,
            quad,
            crossing_record: None,
            target_layer: Some(target_layer),
        }
    }

    /// Create a genesis envelope from the seed (FU, cycle 0).
    pub fn genesis(seed: &[u8; 32]) -> Self {
        let seed_hash = blake3::hash(seed);
        let quad = Quad::new(
            *seed_hash.as_bytes(),
            *seed_hash.as_bytes(),
            Tree::new(),
        );
        Self {
            cycle_index: 0,
            source_unit: UnitId::FU,
            crossing_type: CrossingType::Vertical,
            quad,
            crossing_record: None,
            target_layer: None,
        }
    }

    /// Returns true if this envelope has been stamped by the boundary.
    pub fn has_crossing_record(&self) -> bool {
        self.crossing_record.is_some()
    }

    /// The destination unit (σ(source) for vertical; source for horizontal).
    pub fn destination(&self) -> UnitId {
        match self.crossing_type {
            CrossingType::Vertical => self.source_unit.successor(),
            CrossingType::Horizontal => self.source_unit,
        }
    }

    /// Compute a content hash of the envelope's Quad.
    pub fn quad_hash(&self) -> [u8; 32] {
        self.quad.content_hash()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn new_envelope_has_no_crossing_record() {
        let env = Envelope::new(1, UnitId::FU, Quad::empty());
        assert!(!env.has_crossing_record());
    }

    #[test]
    fn destination_follows_ring_topology_for_vertical() {
        let env = Envelope::new(1, UnitId::FU, Quad::empty());
        assert_eq!(env.destination(), UnitId::MU);
        let env = Envelope::new(1, UnitId::HU, Quad::empty());
        assert_eq!(env.destination(), UnitId::FU);
    }

    #[test]
    fn destination_is_self_for_horizontal() {
        let env =
            Envelope::with_crossing_type(1, UnitId::FU, CrossingType::Horizontal, Quad::empty());
        assert_eq!(env.destination(), UnitId::FU);
    }

    #[test]
    fn genesis_envelope_derives_from_seed() {
        let seed = [42u8; 32];
        let env = Envelope::genesis(&seed);
        assert_eq!(env.cycle_index, 0);
        assert_eq!(env.source_unit, UnitId::FU);
        assert_ne!(env.quad.root, [0u8; 32]);
        assert_eq!(env.quad.root, env.quad.pointer);
    }
}