soma-som-ring 0.1.0

Standalone ring execution engine for soma(som): cycle lifecycle, extension registration, boundary mediation
Documentation

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:

Phase What happens
BEFORE Registered BeforeRing extensions prepare the unit envelope
THROUGH RingProcessor::process transforms FU.Data
AFTER Registered AfterRing extensions observe the result
AROUND AroundRing extensions wrap the whole unit execution
WITHIN WithinRing 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

Type Where Description
RingEngine engine Core struct: runs genesis + standard cycles
RingProcessor processor Trait: unit execution contract
PassthroughProcessor passthrough Default: pass-through for tests + scaffolding
CycleObserver / NoOpObserver engine Hook: observe each completed cycle
GenesisReport engine Result type for engine.genesis()
CycleReport engine Result type for engine.cycle()
RingEngineError error Error enum covering all engine failure modes
RingCommand command Command envelope: type + payload + actor
ViewIntent command Read-side intent (query without mutation)
CommandDispatcher dispatch Route commands to registered handlers
FederationBridge federation Ring-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 reference What it covers
§2, §11 Structural boundary: foundation primitives vs. execution mechanics
§3 The five ring relationships
§8 Crossing attestation: canonical signing-input format
§10 Genesis protocol
§13.1 The seven pluggable governance trait interfaces
Contracts §2.3 The 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.