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.
§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 gateway —
RingCommandenvelope, dispatch, and schema validation - Boundary attestation — Ed25519-attested
CrossingRecordon 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-coreis 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 insoma-som-corewith no-op defaults. Concrete policies live in the Application layer.
§Dependencies
soma-som-core --> soma-som-ring --> your applicationRuntime 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.
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
| Component | Module |
|---|---|
RingEngine | engine |
RingProcessor trait | processor |
CycleObserver, NoOpObserver | engine |
GenesisReport, CycleReport | engine |
RingEngineError | error |
RingCommand, ViewIntent, CommandDispatcher | command, dispatch |
FederationBridge | federation |
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-ringZero 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.