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

//! Genesis protocol: bootstrapping the ring from pipeline to closed loop.
//!
//! ## Spec traceability
//! - Spec §10.1: The bootstrapping problem
//! - Spec §10.2: Genesis cycle specification (Definition 9)
//! - Spec §10.3: Formal properties (genesis hash, standard operation, instance identity)
//!
//! ## Design notes
//!
//! The Genesis struct manages the state machine for the first cycle (t = 0).
//! During genesis, the ring operates as a directed pipeline (FU → MU → ... → HU).
//! Upon HU's completion, the system fingerprint is computed and deposited in FU's
//! Data layer. The ring then transitions permanently to closed-loop operation.

use crate::error::SomaError;
use crate::fingerprint::ring_system_fingerprint;
use crate::quad::Tree;
use crate::ring::{Ring, RingState};
use crate::types::{Layer, UnitId};
use tracing::instrument;

/// The genesis seed: an opaque byte vector.
///
/// Spec §10.2: "FU initializes with a seed value s₀ (implementation-defined)."
/// Spec §10.3: "The genesis seed functions as a root certificate in a PKI hierarchy."
pub type Seed = Vec<u8>;

/// Genesis protocol state machine.
///
/// Tracks progress through the genesis pipeline and manages the
/// transition from pipeline mode to ring mode.
#[derive(Debug, Clone)]
pub struct Genesis {
    /// The genesis seed s₀.
    seed: Seed,

    /// Which unit has been completed. `None` means no unit has been processed yet.
    /// The pipeline progresses: FU → MU → CU → OU → SU → HU.
    last_completed: Option<UnitId>,
}

impl Genesis {
    /// Create a new genesis protocol instance with the given seed.
    ///
    /// The seed is implementation-defined (Spec §14.2) and trusted by
    /// stipulation (Spec §10.2). Its entropy and generation mechanism
    /// are implementation choices.
    pub fn new(seed: Seed) -> Self {
        Self {
            seed,
            last_completed: None,
        }
    }

    /// Get the genesis seed.
    pub fn seed(&self) -> &[u8] {
        &self.seed
    }

    /// The next unit that must be processed in the genesis pipeline.
    /// Returns `None` if all units have been processed (ready for closure).
    pub fn next_unit(&self) -> Option<UnitId> {
        match self.last_completed {
            None => Some(UnitId::FU),
            Some(UnitId::FU) => Some(UnitId::MU),
            Some(UnitId::MU) => Some(UnitId::CU),
            Some(UnitId::CU) => Some(UnitId::OU),
            Some(UnitId::OU) => Some(UnitId::SU),
            Some(UnitId::SU) => Some(UnitId::HU),
            Some(UnitId::HU) => None, // Pipeline complete
        }
    }

    /// Is the genesis pipeline complete (all 6 units processed)?
    pub fn is_pipeline_complete(&self) -> bool {
        self.last_completed == Some(UnitId::HU)
    }

    /// Mark a unit as completed in the genesis pipeline.
    ///
    /// Enforces sequential processing: units must be completed in ring order.
    pub fn mark_completed(&mut self, unit: UnitId) -> Result<(), SomaError> {
        let expected = self.next_unit().ok_or(SomaError::GenesisAlreadyComplete)?;
        if unit != expected {
            return Err(SomaError::GenesisOutOfOrder {
                expected,
                received: unit,
            });
        }
        self.last_completed = Some(unit);
        Ok(())
    }

    /// Initialize the ring for genesis: set state to Genesis, seed FU.
    ///
    /// This is Step 1 of the genesis cycle (Spec §10.2):
    /// "FU initializes with a seed value s₀."
    #[instrument(skip_all, name = "core.genesis.initialize_ring")]
    pub fn initialize_ring(&self, ring: &mut Ring) -> Result<(), SomaError> {
        if ring.state != RingState::Uninitialized {
            return Err(SomaError::GenesisAlreadyStarted);
        }

        ring.state = RingState::Genesis;
        ring.cycle_index = 0;

        // Seed FU's Data layer with s₀
        let seed_hash = blake3::hash(&self.seed);
        let mut seed_tree = Tree::new();
        seed_tree.insert("genesis.seed".into(), self.seed.clone());
        seed_tree.insert("genesis.seed_hash".into(), seed_hash.as_bytes().to_vec());

        let fu_data = ring.unit_mut(UnitId::FU).som_quad_mut(Layer::Data);
        fu_data.root = *seed_hash.as_bytes();
        fu_data.pointer = *blake3::hash(b"genesis.fu.data.pointer").as_bytes();
        fu_data.tree = seed_tree;

        Ok(())
    }

    /// Close the ring: compute system fingerprint, deposit in FU, transition to Ring mode.
    ///
    /// This is Steps 6–8 of the genesis cycle (Spec §10.2):
    /// - Compute F_system^(0) from all 90 positions
    /// - Deposit F_system^(0) and s₀ in FU's Data layer
    /// - Transition permanently from pipeline mode to ring mode
    ///
    /// ## Spec §10.3: Formal properties
    ///
    /// - Genesis hash: SOM_0.hash = H(SOM_0.state ‖ s₀)
    /// - Instance identity: F_system^(0) is the identity of this SOMA instance
    /// - Irreversibility: once closed, there is no mechanism to return to genesis
    pub fn close_ring(&self, ring: &mut Ring) -> Result<GenesisResult, SomaError> {
        if ring.state != RingState::Genesis {
            return Err(SomaError::GenesisNotInProgress);
        }
        if !self.is_pipeline_complete() {
            return Err(SomaError::GenesisPipelineIncomplete);
        }

        // Compute the system fingerprint from all 90 positions
        let system_fp = ring_system_fingerprint(ring);

        // Compute the genesis hash: H(SOM_0.state ‖ s₀)
        let mut hasher = blake3::Hasher::new();
        hasher.update(&system_fp);
        hasher.update(&self.seed);
        let genesis_hash: [u8; 32] = *hasher.finalize().as_bytes();

        // Deposit in FU's Data layer
        let fu_data = ring.unit_mut(UnitId::FU).som_quad_mut(Layer::Data);
        fu_data
            .tree
            .insert("genesis.system_fingerprint".into(), system_fp.to_vec());
        fu_data
            .tree
            .insert("genesis.hash".into(), genesis_hash.to_vec());
        fu_data.tree.insert("genesis.closed".into(), vec![1]);

        // Transition to ring mode — this is irreversible (Spec §10.2, Condition 3)
        ring.state = RingState::Ring;

        Ok(GenesisResult {
            system_fingerprint: system_fp,
            genesis_hash,
            seed_hash: *blake3::hash(&self.seed).as_bytes(),
        })
    }
}

/// Result of a successful genesis closure.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GenesisResult {
    /// The system fingerprint F_system^(0): identity of this SOMA instance.
    pub system_fingerprint: [u8; 32],

    /// The genesis hash: H(SOM_0.state ‖ s₀). Root of the temporal chain.
    pub genesis_hash: [u8; 32],

    /// Hash of the genesis seed for reference.
    pub seed_hash: [u8; 32],
}

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

    /// Simulate a minimal genesis pipeline: each unit gets a basic Quad.
    /// FU's Data layer is NOT overwritten — it retains the seed from initialize_ring.
    fn run_genesis_pipeline(ring: &mut Ring, genesis: &mut Genesis) {
        for &unit in &UnitId::ALL {
            let us = ring.unit_mut(unit);
            us.soma_quad = Quad::from_strings(
                &format!("{unit}.soma.root"),
                &format!("{unit}.soma.ptr"),
                {
                    let mut t = Tree::new();
                    t.insert("unit".into(), format!("{unit}").into_bytes());
                    t
                },
            );
            for &layer in &Layer::ALL {
                // Skip FU.Data — it already holds the genesis seed from initialize_ring.
                // Overwriting it would erase the seed, making all instances identical.
                if unit == UnitId::FU && layer == Layer::Data {
                    continue;
                }
                let mut tree = Tree::new();
                tree.insert("origin".into(), format!("{unit}.{layer}").into_bytes());
                *us.som_quad_mut(layer) = Quad::from_strings(
                    &format!("{unit}.{layer}.root"),
                    &format!("{unit}.{layer}.ptr"),
                    tree,
                );
            }
            genesis.mark_completed(unit).unwrap();
        }
    }

    #[test]
    fn genesis_pipeline_order() {
        let genesis = Genesis::new(b"test-seed".to_vec());
        assert_eq!(genesis.next_unit(), Some(UnitId::FU));
    }

    #[test]
    fn genesis_rejects_out_of_order() {
        let mut genesis = Genesis::new(b"seed".to_vec());
        // Must start with FU, not MU
        assert!(genesis.mark_completed(UnitId::MU).is_err());
    }

    #[test]
    fn genesis_full_lifecycle() {
        let mut ring = Ring::new();
        let mut genesis = Genesis::new(b"soma-genesis-seed-v1".to_vec());

        // Initialize
        genesis.initialize_ring(&mut ring).unwrap();
        assert_eq!(ring.state, RingState::Genesis);

        // Run pipeline
        run_genesis_pipeline(&mut ring, &mut genesis);
        assert!(genesis.is_pipeline_complete());

        // Close ring
        let result = genesis.close_ring(&mut ring).unwrap();
        assert_eq!(ring.state, RingState::Ring);
        assert_ne!(result.system_fingerprint, [0u8; 32]);
        assert_ne!(result.genesis_hash, [0u8; 32]);

        // Verify FU Data layer has genesis deposits
        let fu_data = ring.unit(UnitId::FU).som_quad(Layer::Data);
        assert!(fu_data.tree.contains_key("genesis.system_fingerprint"));
        assert!(fu_data.tree.contains_key("genesis.hash"));
        assert!(fu_data.tree.contains_key("genesis.closed"));
    }

    #[test]
    fn genesis_is_irreversible() {
        let mut ring = Ring::new();
        let mut genesis = Genesis::new(b"seed".to_vec());
        genesis.initialize_ring(&mut ring).unwrap();
        run_genesis_pipeline(&mut ring, &mut genesis);
        genesis.close_ring(&mut ring).unwrap();

        // Cannot re-initialize after closure
        let genesis2 = Genesis::new(b"another-seed".to_vec());
        assert!(genesis2.initialize_ring(&mut ring).is_err());
    }

    #[test]
    fn different_seeds_produce_different_identities() {
        // Spec §10.3: "Two instances with different seeds produce different
        // identities, even if their structural topology is identical."
        let result1 = {
            let mut ring = Ring::new();
            let mut genesis = Genesis::new(b"seed-alpha".to_vec());
            genesis.initialize_ring(&mut ring).unwrap();
            run_genesis_pipeline(&mut ring, &mut genesis);
            genesis.close_ring(&mut ring).unwrap()
        };

        let result2 = {
            let mut ring = Ring::new();
            let mut genesis = Genesis::new(b"seed-beta".to_vec());
            genesis.initialize_ring(&mut ring).unwrap();
            run_genesis_pipeline(&mut ring, &mut genesis);
            genesis.close_ring(&mut ring).unwrap()
        };

        assert_ne!(result1.genesis_hash, result2.genesis_hash);
        assert_ne!(result1.seed_hash, result2.seed_hash);
        // System fingerprints will also differ because FU's Data layer
        // contains the seed, which differs between instances.
        assert_ne!(result1.system_fingerprint, result2.system_fingerprint);
    }
}