Skip to main content

Crate soma_som_ring

Crate soma_som_ring 

Source
Expand description

§soma-som-ring

Standalone ring execution engine for soma(som) — owns the cycle lifecycle (genesis + standard cycles) and provides the registration API for ring extensions.

Crates.io docs.rs License: LGPL-3.0-only

§What is this?

soma-som-ring is the ring execution engine in the soma(som) 2-crate foundation set. It implements:

  • The cycle lifecycle — genesis (boot) + standard cycles (continuous operation)
  • The five extension relationships — BEFORE / THROUGH / AFTER / AROUND / WITHIN
  • The command gatewayRingCommand envelope, dispatch, and schema validation
  • Boundary attestation — Ed25519-attested CrossingRecord on every unit transition (canonical signing-input format per OPUS §8)
  • Federation — ring-to-ring crossing protocol via FederationBridge
  • PassthroughProcessor — default no-op unit processor for testing and scaffolding

soma-som-ring sits at the Platform layer. Application-specific behaviour (governance, command handlers, persistence backends) lives in the Application layer above it.

Application layer            — your ring-native application
          │
          ▼
Platform layer (LGPL-3.0)    — soma-som-ring        <- THIS CRATE
          │
          ▼
Foundation layer (LGPL-3.0)  — soma-som-core

§Getting started

[dependencies]
soma-som-ring = "0.1"
soma-som-core = "0.1"
use soma_som_core::TimingConfig;
use soma_som_core::types::UnitId;
use soma_som_ring::{RingEngine, RingProcessor, RingEngineError};
use soma_som_core::quad::Quad;

struct Echo;
impl RingProcessor for Echo {
    fn process(&mut self, _u: UnitId, _c: u64, input: &Quad, _d: &Quad) -> Result<Quad, RingEngineError> {
        Ok(input.clone())
    }
}

// 1. Construct the engine
let mut engine = RingEngine::new(TimingConfig::default());

// 2. Register a processor for each unit
engine.set_processor(UnitId::FU, Box::new(Echo));
engine.set_processor(UnitId::SU, Box::new(Echo));

// 3. Run genesis (one-time ring boot)
let genesis_report = engine.genesis().expect("genesis failed");
assert!(!genesis_report.crossing_records.is_empty());

// 4. Run standard cycles
let cycle_report = engine.cycle().expect("cycle failed");
assert!(cycle_report.cycle_index >= 1);

§The cycle model

Each standard ring cycle processes all units in ring order. For each unit:

PhaseWhat happens
BEFORERegistered BeforeRing extensions prepare the unit envelope
THROUGHRingProcessor::process transforms FU.Data
AFTERRegistered AfterRing extensions observe the result
AROUNDAroundRing extensions wrap the whole unit execution
WITHINWithinRing extensions operate inside the processor call

Extensions are registered via engine.register_extension(...). All five relationships are optional — a ring with only a processor is valid.

§Extension registration

use soma_som_core::extension::{BeforeRing, Extension, GateRejection};
use soma_som_core::quad::Tree;

struct Auditor;

impl Extension for Auditor {
    fn name(&self) -> &str { "auditor" }
}

impl BeforeRing for Auditor {
    fn evaluate(&self, _context: &Tree) -> Result<(), GateRejection> {
        // Inspect the unit's input tree before processing.
        Ok(())
    }
}

// engine.register_before(Box::new(Auditor));

Registering lexicon providers enables vocabulary validation:

use soma_som_core::lexicon::{LexiconProvider, LexiconEntry, TermDescription};
use soma_som_core::types::{UnitId, World};

struct MyVocab;
impl LexiconProvider for MyVocab {
    fn domain(&self) -> &str { "my" }
    fn declared_unit(&self) -> UnitId { UnitId::FU }
    fn primary_world(&self) -> World { World::WAI }
    fn vocabulary(&self) -> Vec<LexiconEntry> { vec![] }
    fn describe(&self, _key: &str) -> Option<TermDescription> { None }
}

// engine.register_lexicon(Box::new(MyVocab))?;

register_lexicon returns Result<(), RegistrationError> — coordinate mismatches are caught at registration time, not at runtime.

§Public API

TypeWhereDescription
RingEngineengineCore struct: runs genesis + standard cycles
RingProcessorprocessorTrait: unit execution contract
PassthroughProcessorpassthroughDefault: pass-through for tests + scaffolding
CycleObserver / NoOpObserverengineHook: observe each completed cycle
GenesisReportengineResult type for engine.genesis()
CycleReportengineResult type for engine.cycle()
RingEngineErrorerrorError enum covering all engine failure modes
RingCommandcommandCommand envelope: type + payload + actor
ViewIntentcommandRead-side intent (query without mutation)
CommandDispatcherdispatchRoute commands to registered handlers
FederationBridgefederationRing-to-ring crossing

§Architecture

soma-som-ring implements the soma(som) ring protocol. The deep architectural reading lives in the ARC42 Solitaire Architecture Document at docs/soma-som-ring-sad.md; the canonical specification is the soma(som) v1.0 OPUS paper (State of Mind: A Circular Architecture for Human-Machine Integration).

Key design properties:

  • Zero application-specific dependencies — no governance policy, no command handlers, no persistence backends. These belong in the Application layer.
  • soma-som-core is the only inter-crate dependency — structural primitives live there; execution mechanics live here.
  • Boundary attestation inlined — crossing records are created inside the engine, not delegated to a separate crate.
  • Governance traits injected — the seven §13.1 governance trait interfaces (AuthorizationProvider, AutonomyEvaluator, etc.) are defined in soma-som-core with no-op defaults. Concrete policies live in the Application layer.

§Dependencies

soma-som-core  -->  soma-som-ring  -->  your application

Runtime dependencies: soma-som-core, blake3, serde, serde_json.

No async runtime, no OS threads, no file I/O, no network. The engine is synchronous by design; the calling application manages concurrency.

§Implementing RingProcessor

The RingProcessor trait is the minimum surface the engine drives:

use soma_som_ring::{RingProcessor, RingEngineError};
use soma_som_core::quad::Quad;
use soma_som_core::types::UnitId;

struct MyProcessor;

impl RingProcessor for MyProcessor {
    fn process(
        &mut self,
        _unit: UnitId,
        _cycle: u64,
        input: &Quad,
        _data: &Quad,
    ) -> Result<Quad, RingEngineError> {
        // Transform the unit's FU.Data and return the OU.Data.
        Ok(input.clone())
    }
}

The input Quad carries the unit’s FU.Data in its tree field. The output Quad becomes the unit’s OU.Data (and, for SU, feeds the next cycle’s FU.Data).

§Spec traceability — OPUS paper

OPUS referenceWhat it covers
§2, §11Structural boundary: foundation primitives vs. execution mechanics
§3The five ring relationships
§8Crossing attestation: canonical signing-input format
§10Genesis protocol
§13.1The seven pluggable governance trait interfaces
Contracts §2.3The twelve crossings of the ring

§License

soma-som-ring is licensed under LGPL-3.0-only.

Copyright 2025–2026 somasom.io

§Contributing

See CONTRIBUTING.md and CODE_OF_CONDUCT.md. soma-som-ring — the standalone ring execution engine for soma(som).

This crate owns the cycle lifecycle (genesis + standard cycles) and the registration API for the five extension relationships (BEFORE / THROUGH / AFTER / AROUND / WITHIN) defined in OPUS §3.

§What lives here

ComponentModule
RingEngineengine
RingProcessor traitprocessor
CycleObserver, NoOpObserverengine
GenesisReport, CycleReportengine
RingEngineErrorerror
RingCommand, ViewIntent, CommandDispatchercommand, dispatch
FederationBridgefederation
Boundary (crossing attestation)boundary

Structural primitives (Quad, Tree, Ring, UnitId, the extension trait family, persistence abstractions, governance trait interfaces) live in the foundation crate soma_som_core. The split is the layer-purity boundary: foundation primitives below, execution mechanics above.

§Dependencies

soma-som-core ◄─── soma-som-ring

Zero framework dependencies: no async runtime, no HTTP server, no libc bindings. Signing is injected at construction via the soma_som_core::CrossingSigner trait so the engine never names a concrete crypto backend.

§Example

Construct a RingEngine, register a passthrough processor for each unit, and run a single genesis cycle:

use soma_som_core::TimingConfig;
use soma_som_core::quad::Quad;
use soma_som_core::types::{Layer, UnitId};
use soma_som_ring::{RingEngine, RingEngineError, RingProcessor};

// Minimal RingProcessor — passes input through unchanged.
struct Echo;
impl RingProcessor for Echo {
    fn process(
        &mut self,
        _unit: UnitId,
        _cycle: u64,
        input: &Quad,
        _data: &Quad,
    ) -> Result<Quad, RingEngineError> {
        Ok(input.clone())
    }
}

let mut engine = RingEngine::new(TimingConfig::default());
engine.set_all_processors(|| Box::new(Echo));
assert_eq!(engine.active_units().len(), 6);

// Genesis: bootstrap the ring with a seed.
let report = engine.genesis(b"example-seed")?;
assert!(engine.is_genesis_complete());
assert_eq!(report.crossing_records.len(), 12);

After genesis, drive standard cycles (each cycle traverses all 6 units + the 12 crossings of the ring topology):

let mut engine = RingEngine::new(TimingConfig::default());
engine.set_all_processors(|| Box::new(Echo));
engine.genesis(b"seed")?;

let cycle = engine.cycle()?;
assert_eq!(cycle.cycle_index, 1);
assert_eq!(cycle.crossing_records.len(), 12);

// Run 3 more cycles in a batch.
let reports = engine.run_cycles(3);
assert_eq!(reports.len(), 3);
assert!(reports.iter().all(|r| r.is_ok()));
assert_eq!(engine.cycle_index(), 4);

Re-exports§

pub use engine::CommandRegistry;
pub use engine::CommandRegistryEntry;
pub use engine::CycleObserver;
pub use engine::CycleReport;
pub use engine::GenesisReport;
pub use engine::NoOpObserver;
pub use engine::RingEngine;
pub use error::RingEngineError;
pub use error::RingEngineResult;
pub use federation::FederationBridge;
pub use federation::LocalDescriptor;
pub use command::COMMAND_PREFIX;
pub use command::CommandResult;
pub use command::CommandStatus;
pub use command::RESULT_PREFIX;
pub use command::RingCommand;
pub use command::VIEW_PREFIX;
pub use command::ViewIntent;
pub use command::inject_command;
pub use command::inject_view_intent;
pub use command::validate_payload;
pub use dispatch::CommandDispatchError;
pub use dispatch::CommandDispatcher;
pub use dispatch::PolicyProvider;
pub use passthrough::PassthroughProcessor;
pub use processor::RingProcessor;

Modules§

boundary
Protocol boundary: structural discontinuity at every ring crossing.
command
Ring-mediated command protocol — types, injection, and extraction.
dispatch
Abstract command dispatch and policy provider traits.
engine
The ring execution engine: cycle lifecycle, extension registration, boundary mediation.
error
Error types for the ring execution engine.
federation
FederationBridge: Two Doors compliant cross-ring data injection.
passthrough
Neutral default unit processor.
processor
RingProcessor: the ring engine’s minimal processing contract.