use crate::boundary::{Boundary, CrossingResult};
use soma_som_core::timing::TimingConfig;
use soma_som_core::NoopSigner;
use std::collections::HashSet;
use std::sync::Arc;
use tracing::{instrument, warn};
use soma_som_core::extension::{
AfterRing, AroundRing, BeforeRing, CommandSchema, RegistrationError, SiblingManifest,
ThroughRing, ViewRing,
};
use soma_som_core::fingerprint::{ring_awareness_fingerprint, ring_fingerprint, ring_system_fingerprint};
use soma_som_core::genesis::{Genesis, GenesisResult};
use soma_som_core::ledger::{LedgerEntry, TemporalLedger};
use soma_som_core::lexicon::{
LexiconEntry, LexiconProvider, Medium, RenderingPack, ResolvedRendering, TermDescription,
};
use soma_som_core::perspectival::{PerspectivalChain, cross_verification_digest, unit_state_hash};
use soma_som_core::quad::{Quad, Tree};
use soma_som_core::ring::Ring;
use soma_som_core::ring_store::RingStore;
use soma_som_core::authorization::PermissionRequirement;
use soma_som_core::traceability::{CycleKind as CycleClass, CycleContext};
use soma_som_core::types::{CrossingType, Layer, UnitId};
use soma_som_core::world::{OrganEntry, WorldRegistry};
#[derive(Debug, Clone)]
pub struct CommandRegistryEntry {
pub delegate_name: String,
pub permissions: Vec<PermissionRequirement>,
pub prefixes: Vec<String>,
pub schemas: Vec<CommandSchema>,
}
#[derive(Debug, Clone, Default)]
pub struct CommandRegistry {
entries: Vec<CommandRegistryEntry>,
}
impl CommandRegistry {
pub fn new() -> Self {
Self::default()
}
pub fn register_entry(
&mut self,
delegate_name: &str,
permissions: Vec<PermissionRequirement>,
prefixes: Vec<String>,
schemas: Vec<CommandSchema>,
) {
self.entries.push(CommandRegistryEntry {
delegate_name: delegate_name.to_string(),
permissions,
prefixes,
schemas,
});
}
pub fn lookup(&self, command_type: &str) -> Option<&CommandRegistryEntry> {
self.entries
.iter()
.find(|e| e.permissions.iter().any(|p| p.command == command_type))
}
pub fn entries(&self) -> &[CommandRegistryEntry] {
&self.entries
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn permission_count(&self) -> usize {
self.entries.iter().map(|e| e.permissions.len()).sum()
}
pub fn lookup_schema(&self, command_type: &str) -> Option<&CommandSchema> {
self.entries
.iter()
.flat_map(|e| &e.schemas)
.find(|s| s.command_type == command_type)
}
pub fn schema_count(&self) -> usize {
self.entries.iter().map(|e| e.schemas.len()).sum()
}
pub fn find_prefix_collision<'a>(&'a self, new_prefixes: &'a [String]) -> Option<(&'a str, &'a str)> {
for new_prefix in new_prefixes {
for entry in &self.entries {
for existing in &entry.prefixes {
if new_prefix.starts_with(existing.as_str())
|| existing.starts_with(new_prefix.as_str())
{
return Some((new_prefix.as_str(), entry.delegate_name.as_str()));
}
}
}
}
None
}
}
use soma_som_core::crossing::CrossingRecord;
use soma_som_core::envelope::Envelope;
use crate::error::{RingEngineError, RingEngineResult};
use crate::processor::RingProcessor;
pub trait CycleObserver: Send {
fn on_unit_enter(&self, unit: UnitId, cycle_index: u64, step: u32);
fn on_unit_exit(&self, unit: UnitId, cycle_index: u64, step: u32, elapsed_ms: u64);
}
pub struct NoOpObserver;
impl CycleObserver for NoOpObserver {
#[inline(always)]
fn on_unit_enter(&self, _unit: UnitId, _cycle_index: u64, _step: u32) {}
#[inline(always)]
fn on_unit_exit(&self, _unit: UnitId, _cycle_index: u64, _step: u32, _elapsed_ms: u64) {}
}
#[derive(Debug, Clone)]
pub struct GenesisReport {
pub genesis: GenesisResult,
pub crossing_records: Vec<CrossingRecord>,
pub boundary_verifying_key: [u8; 32],
pub cross_verification_digest: [u8; 32],
pub ring_fingerprint: [u8; 32],
}
#[derive(Debug, Clone)]
pub struct CycleReport {
pub cycle_index: u64,
pub awareness_fingerprint: [u8; 32],
pub system_fingerprint: [u8; 32],
pub chain_hash: [u8; 32],
pub cross_verification_digest: [u8; 32],
pub elapsed_ms: u64,
pub crossing_records: Vec<CrossingRecord>,
pub context: CycleContext,
pub ring_fingerprint: [u8; 32],
}
pub struct RingEngine {
ring: Ring,
boundary: Boundary,
ledger: TemporalLedger,
perspectival_chains: [PerspectivalChain; 6],
genesis_seed: Vec<u8>,
latest_cross_verification: Option<[u8; 32]>,
genesis_complete: bool,
genesis_ring_fingerprint: [u8; 32],
processors: [Option<Box<dyn RingProcessor>>; 6],
before_gates: Vec<Box<dyn BeforeRing>>,
through_delegates: Vec<Arc<dyn ThroughRing>>,
view_delegates: Vec<Arc<dyn ViewRing>>,
after_observers: Vec<Box<dyn AfterRing>>,
around_extensions: Vec<Box<dyn AroundRing>>,
within_store: Option<Box<dyn RingStore>>,
lexicon_providers: Vec<Box<dyn LexiconProvider>>,
rendering_packs: Vec<RenderingPack>,
world_registry: WorldRegistry,
command_registry: CommandRegistry,
registered_names: HashSet<String>,
}
#[allow(missing_docs)]
impl RingEngine {
pub fn new(timing_config: TimingConfig) -> Self {
Self {
ring: Ring::new(),
boundary: Boundary::new(Box::new(NoopSigner), timing_config),
ledger: TemporalLedger::new(),
perspectival_chains: [
PerspectivalChain::new(UnitId::FU),
PerspectivalChain::new(UnitId::MU),
PerspectivalChain::new(UnitId::CU),
PerspectivalChain::new(UnitId::OU),
PerspectivalChain::new(UnitId::SU),
PerspectivalChain::new(UnitId::HU),
],
genesis_seed: Vec::new(),
latest_cross_verification: None,
genesis_complete: false,
genesis_ring_fingerprint: [0u8; 32],
processors: [None, None, None, None, None, None],
before_gates: Vec::new(),
through_delegates: Vec::new(),
view_delegates: Vec::new(),
after_observers: Vec::new(),
around_extensions: Vec::new(),
within_store: None,
lexicon_providers: Vec::new(),
rendering_packs: Vec::new(),
command_registry: CommandRegistry::new(),
world_registry: WorldRegistry::new(),
registered_names: HashSet::new(),
}
}
pub fn with_test_config(_boundary_secret: &[u8; 32]) -> Self {
Self {
ring: Ring::new(),
boundary: Boundary::new(Box::new(NoopSigner), TimingConfig::default()),
ledger: TemporalLedger::new(),
perspectival_chains: [
PerspectivalChain::new(UnitId::FU),
PerspectivalChain::new(UnitId::MU),
PerspectivalChain::new(UnitId::CU),
PerspectivalChain::new(UnitId::OU),
PerspectivalChain::new(UnitId::SU),
PerspectivalChain::new(UnitId::HU),
],
genesis_seed: Vec::new(),
latest_cross_verification: None,
genesis_complete: false,
genesis_ring_fingerprint: [0u8; 32],
processors: [None, None, None, None, None, None],
before_gates: Vec::new(),
through_delegates: Vec::new(),
view_delegates: Vec::new(),
after_observers: Vec::new(),
around_extensions: Vec::new(),
within_store: None,
lexicon_providers: Vec::new(),
rendering_packs: Vec::new(),
command_registry: CommandRegistry::new(),
world_registry: WorldRegistry::new(),
registered_names: HashSet::new(),
}
}
pub fn set_processor(&mut self, unit: UnitId, processor: Box<dyn RingProcessor>) {
self.processors[unit.index()] = Some(processor);
}
pub fn set_all_processors<F>(&mut self, factory: F)
where
F: Fn() -> Box<dyn RingProcessor>,
{
for &unit in &UnitId::ALL {
self.processors[unit.index()] = Some(factory());
}
}
pub fn disable(&mut self, unit: UnitId) {
self.processors[unit.index()] = None;
}
pub fn is_unit_enabled(&self, unit: UnitId) -> bool {
self.processors[unit.index()].is_some()
}
pub fn active_units(&self) -> Vec<UnitId> {
UnitId::ALL
.iter()
.filter(|&&u| self.is_unit_enabled(u))
.copied()
.collect()
}
pub fn register_before(&mut self, ext: Box<dyn BeforeRing>) {
self.registered_names.insert(ext.name().to_string());
self.before_gates.push(ext);
}
pub fn register_after(&mut self, ext: Box<dyn AfterRing>) {
self.registered_names.insert(ext.name().to_string());
self.after_observers.push(ext);
}
pub fn register_through(
&mut self,
ext: Arc<dyn ThroughRing>,
) -> Result<(), RegistrationError> {
let prefixes = ext.claimed_prefixes();
if !prefixes.is_empty() {
if let Some((prefix, claimed_by)) =
self.command_registry.find_prefix_collision(&prefixes)
{
return Err(RegistrationError::NamespaceConflict {
prefix: prefix.to_string(),
claimed_by: claimed_by.to_string(),
});
}
}
let schemas = ext.command_schemas();
self.registered_names.insert(ext.name().to_string());
self.command_registry
.register_entry(ext.name(), ext.permission_requirements(), prefixes, schemas);
self.through_delegates.push(ext);
Ok(())
}
pub fn register_around(&mut self, ext: Box<dyn AroundRing>) {
self.registered_names.insert(ext.name().to_string());
self.around_extensions.push(ext);
}
pub fn register_view(
&mut self,
ext: Arc<dyn ViewRing>,
) -> Result<(), RegistrationError> {
let prefixes = ext.claimed_view_prefixes();
if !prefixes.is_empty() {
for existing in &self.view_delegates {
for existing_prefix in existing.claimed_view_prefixes() {
for new_prefix in &prefixes {
if existing_prefix.starts_with(new_prefix.as_str())
|| new_prefix.starts_with(existing_prefix.as_str())
{
return Err(RegistrationError::NamespaceConflict {
prefix: new_prefix.clone(),
claimed_by: existing.name().to_string(),
});
}
}
}
}
}
self.registered_names.insert(ext.name().to_string());
self.view_delegates.push(ext);
Ok(())
}
pub fn register_within(&mut self, store: Box<dyn RingStore>) {
self.within_store = Some(store);
}
pub fn register_lexicon(
&mut self,
provider: Box<dyn LexiconProvider>,
) -> Result<(), RegistrationError> {
let domain = provider.domain().to_string();
provider.validate_coordinates().map_err(|e| {
RegistrationError::CoordinateValidation(format!("domain '{domain}': {e:?}"))
})?;
let world = provider.primary_world();
let new_packs = provider.renderings();
self.lexicon_providers.retain(|p| p.domain() != domain);
let organ_scope = format!("organ:{domain}");
self.rendering_packs.retain(|p| p.scope != organ_scope);
self.rendering_packs.extend(new_packs);
self.world_registry.register(
world,
OrganEntry {
name: domain.to_uppercase(),
namespace: domain.clone(),
},
);
self.lexicon_providers.push(provider);
Ok(())
}
pub fn lexicon_vocabulary(&self) -> Vec<LexiconEntry> {
self.lexicon_providers
.iter()
.flat_map(|p| p.vocabulary())
.collect()
}
pub fn lexicon_describe(&self, key: &str) -> Option<TermDescription> {
for provider in &self.lexicon_providers {
let domain = provider.domain();
if key.starts_with(&format!("{domain}.")) {
return provider.describe(key);
}
}
None
}
pub fn lexicon_domains(&self) -> Vec<&str> {
self.lexicon_providers.iter().map(|p| p.domain()).collect()
}
pub fn lexicon_rendering_packs(&self) -> &[RenderingPack] {
&self.rendering_packs
}
pub fn lexicon_resolve(
&self,
term_key: &str,
medium: Medium,
locale: &str,
) -> ResolvedRendering {
let vocabulary = self.lexicon_vocabulary();
soma_som_core::lexicon::resolve_rendering(
term_key,
medium,
locale,
&self.rendering_packs,
&vocabulary,
)
}
pub fn world_registry(&self) -> &WorldRegistry {
&self.world_registry
}
pub const RING_VERSION: &'static str = "1.0.0";
pub fn register_sibling<F>(
&mut self,
manifest: &dyn SiblingManifest,
provisioner: Option<F>,
) -> Result<(), RegistrationError>
where
F: FnMut(&soma_som_core::extension::DomainRequest) -> Result<(), String>,
{
let sibling_id = manifest.id();
if self.registered_names.contains(sibling_id) {
return Err(RegistrationError::DuplicateId(sibling_id.to_string()));
}
let min_version = manifest.min_ring_version();
if !Self::version_compatible(min_version, Self::RING_VERSION) {
return Err(RegistrationError::IncompatibleVersion {
required: min_version.to_string(),
running: Self::RING_VERSION.to_string(),
});
}
let prefix = format!("sibling.{sibling_id}.");
let probe = format!("{prefix}__probe__");
for delegate in &self.through_delegates {
if delegate.handles(&probe) {
return Err(RegistrationError::NamespaceConflict {
prefix,
claimed_by: delegate.name().to_string(),
});
}
}
if let Some(ref handler) = manifest.through_handler() {
for delegate in &self.through_delegates {
let existing_name = delegate.name();
let existing_probe = format!("sibling.{existing_name}.__probe__");
if handler.handles(&existing_probe) {
return Err(RegistrationError::NamespaceConflict {
prefix: prefix.clone(),
claimed_by: existing_name.to_string(),
});
}
}
}
let domains = manifest.persistence_domains();
if !domains.is_empty() {
if let Some(mut prov) = provisioner {
for domain in &domains {
prov(domain).map_err(RegistrationError::ProvisionFailed)?;
}
}
}
if let Some(through) = manifest.through_handler() {
self.registered_names.insert(through.name().to_string());
let prefixes = through.claimed_prefixes();
let schemas = through.command_schemas();
self.command_registry
.register_entry(through.name(), through.permission_requirements(), prefixes, schemas);
self.through_delegates.push(through);
}
if let Some(before) = manifest.before_gate() {
self.registered_names.insert(before.name().to_string());
self.before_gates.push(before);
}
if let Some(after) = manifest.after_observer() {
self.registered_names.insert(after.name().to_string());
self.after_observers.push(after);
}
if let Some(around) = manifest.around_extension() {
self.registered_names.insert(around.name().to_string());
self.around_extensions.push(around);
}
self.registered_names.insert(sibling_id.to_string());
Ok(())
}
fn version_compatible(required: &str, running: &str) -> bool {
let parse = |s: &str| -> (u32, u32, u32) {
let mut parts = s.split('.');
let major = parts.next().and_then(|p| p.parse().ok()).unwrap_or(0);
let minor = parts.next().and_then(|p| p.parse().ok()).unwrap_or(0);
let patch = parts.next().and_then(|p| p.parse().ok()).unwrap_or(0);
(major, minor, patch)
};
let req = parse(required);
let run = parse(running);
run >= req
}
pub fn ring(&self) -> &Ring {
&self.ring
}
pub fn ring_mut(&mut self) -> &mut Ring {
&mut self.ring
}
pub fn ledger(&self) -> &TemporalLedger {
&self.ledger
}
pub fn boundary_verifying_key(&self) -> [u8; 32] {
self.boundary.verifying_key_bytes()
}
pub fn is_genesis_complete(&self) -> bool {
self.genesis_complete
}
pub fn cycle_index(&self) -> u64 {
self.ring.cycle_index
}
pub fn persist_store(&self) -> Option<&dyn RingStore> {
self.within_store.as_deref()
}
pub fn has_persistence(&self) -> bool {
self.within_store.is_some()
}
pub fn perspectival_chain(&self, unit: UnitId) -> &PerspectivalChain {
&self.perspectival_chains[unit.index()]
}
pub fn cross_verification_digest(&self) -> Option<[u8; 32]> {
self.latest_cross_verification
}
pub fn awareness_fingerprint(&self) -> [u8; 32] {
ring_awareness_fingerprint(&self.ring)
}
pub fn system_fingerprint(&self) -> [u8; 32] {
ring_system_fingerprint(&self.ring)
}
pub fn ring_fingerprint(&self) -> [u8; 32] {
self.genesis_ring_fingerprint
}
pub fn before_gate_count(&self) -> usize {
self.before_gates.len()
}
pub fn after_observer_count(&self) -> usize {
self.after_observers.len()
}
pub fn through_delegate_count(&self) -> usize {
self.through_delegates.len()
}
pub fn probe_namespace(&self, command_type: &str) -> Option<&str> {
for delegate in &self.through_delegates {
if delegate.handles(command_type) {
return Some(delegate.name());
}
}
None
}
pub fn command_registry(&self) -> &CommandRegistry {
&self.command_registry
}
pub fn around_extension_count(&self) -> usize {
self.around_extensions.len()
}
pub fn registered_names(&self) -> &HashSet<String> {
&self.registered_names
}
pub fn has_registered_name(&self, name: &str) -> bool {
self.registered_names.contains(name)
}
#[instrument(skip_all, name = "ring.genesis")]
pub fn genesis(&mut self, seed: &[u8]) -> RingEngineResult<GenesisReport> {
if self.genesis_complete {
return Err(RingEngineError::GenesisAlreadyComplete);
}
for &unit in &UnitId::ALL {
if !self.is_unit_enabled(unit) {
return Err(RingEngineError::UnitDisabled { unit });
}
}
let mut genesis = Genesis::new(seed.to_vec());
genesis.initialize_ring(&mut self.ring)?;
let seed_hash = *blake3::hash(seed).as_bytes();
self.boundary.begin_cycle(0, seed_hash)?;
let mut current_quad = self.build_genesis_input_quad(seed);
for &unit in &UnitId::ALL {
let successor = unit.successor();
self.boundary.start_unit_timer(unit)?;
let output_quad = self.run_processor(unit, 0, ¤t_quad)?;
self.boundary.end_unit_timer(unit)?;
self.deposit_server_output(unit, &output_quad);
let envelope = Envelope::new(0, unit, output_quad.clone());
let crossing_result = self.boundary.process_crossing(&envelope, unit, successor)?;
let h_envelope = Envelope::with_crossing_type(
0,
unit,
CrossingType::Horizontal,
output_quad.clone(),
);
let h_crossing = self.boundary.process_crossing(&h_envelope, unit, unit)?;
self.run_horizontal_path(unit, 0, &h_crossing)?;
genesis.mark_completed(unit)?;
current_quad = crossing_result.delivery_envelope.quad;
}
let (_final_chain_hash, crossing_records) = self.boundary.end_cycle()?;
let genesis_result = genesis.close_ring(&mut self.ring)?;
let mut persp_hashes = [[0u8; 32]; 6];
for (i, &unit) in UnitId::ALL.iter().enumerate() {
let state_hash = unit_state_hash(self.ring.unit(unit));
let entry = self.perspectival_chains[i].commit_genesis(state_hash, seed)?;
persp_hashes[i] = entry.perspectival_hash;
}
let xv_digest = cross_verification_digest(&persp_hashes);
self.latest_cross_verification = Some(xv_digest);
self.genesis_seed = seed.to_vec();
let fu_data = self.ring.unit_mut(UnitId::FU).som_quad_mut(Layer::Data);
fu_data.tree.insert(
"perspectival.cross_verification_digest".into(),
xv_digest.to_vec(),
);
let awareness_fp = ring_awareness_fingerprint(&self.ring);
self.ledger.record_genesis(
awareness_fp,
genesis_result.system_fingerprint,
genesis_result.genesis_hash,
CycleContext::genesis(),
)?;
self.genesis_complete = true;
self.persist_after_genesis(&crossing_records, seed);
self.genesis_ring_fingerprint = ring_fingerprint(
&genesis_result.system_fingerprint,
&self.boundary.verifying_key_bytes(),
);
Ok(GenesisReport {
genesis: genesis_result,
crossing_records,
boundary_verifying_key: self.boundary.verifying_key_bytes(),
cross_verification_digest: xv_digest,
ring_fingerprint: self.genesis_ring_fingerprint,
})
}
#[instrument(skip_all, name = "ring.cycle")]
pub fn cycle(&mut self) -> RingEngineResult<CycleReport> {
self.cycle_observed(&NoOpObserver)
}
#[instrument(skip_all, name = "ring.cycle_observed")]
pub fn cycle_observed(
&mut self,
observer: &dyn CycleObserver,
) -> RingEngineResult<CycleReport> {
if !self.genesis_complete {
return Err(RingEngineError::NotInitialized);
}
let cycle_index = self.ring.cycle_index + 1;
let fu_data_tree = &self.ring.unit(UnitId::FU).som_quad(Layer::Data).tree;
for gate in &self.before_gates {
gate.evaluate(fu_data_tree)?;
}
{
let fu_data = &mut self
.ring
.unit_mut(UnitId::FU)
.som_quad_mut(Layer::Data)
.tree;
for around in &self.around_extensions {
around.inject(fu_data);
}
}
let prev_chain_hash = self
.ledger
.chain_head()
.ok_or(RingEngineError::NotInitialized)?;
self.boundary.begin_cycle(cycle_index, prev_chain_hash)?;
let fu_data = self.ring.unit(UnitId::FU).som_quad(Layer::Data).clone();
let mut current_quad = fu_data;
for (step_idx, &unit) in UnitId::ALL.iter().enumerate() {
let step = (step_idx as u32) + 1;
let successor = unit.successor();
if !self.is_unit_enabled(unit) {
let _ = self.boundary.end_cycle();
return Err(RingEngineError::UnitDisabled { unit });
}
observer.on_unit_enter(unit, cycle_index, step);
let unit_start = std::time::Instant::now();
self.boundary.start_unit_timer(unit)?;
let server_output = self.run_processor(unit, cycle_index, ¤t_quad)?;
self.boundary.end_unit_timer(unit)?;
self.deposit_server_output(unit, &server_output);
let v_envelope = Envelope::with_crossing_type(
cycle_index,
unit,
CrossingType::Vertical,
server_output.clone(),
);
let v_crossing = self
.boundary
.process_crossing(&v_envelope, unit, successor)?;
current_quad = v_crossing.delivery_envelope.quad;
let h_envelope = Envelope::with_crossing_type(
cycle_index,
unit,
CrossingType::Horizontal,
server_output.clone(),
);
let h_crossing = self.boundary.process_crossing(&h_envelope, unit, unit)?;
self.run_horizontal_path(unit, cycle_index, &h_crossing)?;
let unit_elapsed_ms = unit_start.elapsed().as_millis() as u64;
observer.on_unit_exit(unit, cycle_index, step, unit_elapsed_ms);
}
let (_final_chain_hash, crossing_records) = self.boundary.end_cycle()?;
let elapsed_ms = match (crossing_records.first(), crossing_records.last()) {
(Some(first), Some(last)) => {
last.timestamp_ns.saturating_sub(first.timestamp_ns) / 1_000_000
}
_ => 0,
};
let pre_deposit_awareness = ring_awareness_fingerprint(&self.ring);
let pre_deposit_system = ring_system_fingerprint(&self.ring);
let mut persp_hashes = [[0u8; 32]; 6];
for (i, &unit) in UnitId::ALL.iter().enumerate() {
let state_hash = unit_state_hash(self.ring.unit(unit));
let entry = self.perspectival_chains[i].commit(state_hash)?;
persp_hashes[i] = entry.perspectival_hash;
}
let xv_digest = cross_verification_digest(&persp_hashes);
self.latest_cross_verification = Some(xv_digest);
let cycle_context = self.extract_cycle_context();
let entry: LedgerEntry = self.ledger.record_cycle(
pre_deposit_awareness,
pre_deposit_system,
cycle_context.clone(),
)?;
self.deposit_cycle_result(cycle_index, &entry);
let fu_data = self.ring.unit_mut(UnitId::FU).som_quad_mut(Layer::Data);
fu_data.tree.insert(
"perspectival.cross_verification_digest".into(),
xv_digest.to_vec(),
);
self.ring.cycle_index = cycle_index;
let awareness_fp = ring_awareness_fingerprint(&self.ring);
let system_fp = ring_system_fingerprint(&self.ring);
self.persist_after_cycle(cycle_index, &crossing_records);
{
let ou_output = &self.ring.unit(UnitId::OU).som_quad(Layer::Server).tree;
let su_output = &self.ring.unit(UnitId::SU).som_quad(Layer::Server).tree;
for after in &self.after_observers {
after.on_cycle_complete(cycle_index, ou_output);
after.on_crossings(cycle_index, &crossing_records);
}
for around in &self.around_extensions {
around.observe(cycle_index, ou_output, su_output);
}
}
{
let ou_output = &self.ring.unit(UnitId::OU).som_quad(Layer::Server).tree;
for gate in &self.before_gates {
gate.feedback(cycle_index, ou_output);
}
}
Ok(CycleReport {
cycle_index,
awareness_fingerprint: awareness_fp,
system_fingerprint: system_fp,
chain_hash: entry.chain_hash,
cross_verification_digest: xv_digest,
elapsed_ms,
crossing_records,
context: cycle_context,
ring_fingerprint: self.genesis_ring_fingerprint,
})
}
pub fn run_cycles(&mut self, count: u32) -> Vec<RingEngineResult<CycleReport>> {
(0..count).map(|_| self.cycle()).collect()
}
pub fn verify_chain(&self) -> RingEngineResult<()> {
self.ledger.verify_chain()?;
Ok(())
}
pub fn verify_perspectival_chains(&self) -> RingEngineResult<()> {
for chain in &self.perspectival_chains {
chain.verify_chain(&self.genesis_seed)?;
}
Ok(())
}
fn extract_cycle_context(&self) -> CycleContext {
let fu_tree = &self.ring.unit(UnitId::FU).som_quad(Layer::Data).tree;
let ou_tree = &self.ring.unit(UnitId::OU).som_quad(Layer::Server).tree;
let read = |tree: &Tree, key: &str| -> Option<String> {
tree.get(key)
.and_then(|v| String::from_utf8(v.clone()).ok())
};
let command_type = read(fu_tree, "command.type");
let event_type = read(fu_tree, "event.type");
match command_type.as_deref() {
Some("session.logout") => {
let actor = read(fu_tree, "command.admin");
let request_id = read(fu_tree, "command.request_id");
CycleContext {
cycle_class: CycleClass::Logout,
command_type: Some("session.logout".into()),
actor,
request_id,
authorization_outcome: Some("invalidated".into()),
detail: None,
}
}
Some("gate.transition") => {
let actor =
read(fu_tree, "command.admin").or_else(|| read(fu_tree, "gate.requesting_wp"));
let request_id = read(fu_tree, "command.request_id");
let auth_outcome = read(ou_tree, "authorization.status")
.or_else(|| read(ou_tree, "result.status"));
let gate_name = read(fu_tree, "gate.name");
let evidence_count = fu_tree
.keys()
.filter(|k| k.starts_with("gate.evidence."))
.count();
let detail = match gate_name {
Some(name) => format!("gate={name} evidence={evidence_count}"),
None => format!("evidence={evidence_count}"),
};
CycleContext {
cycle_class: CycleClass::GateTransition,
command_type: Some("gate.transition".into()),
actor,
request_id,
authorization_outcome: auth_outcome,
detail: Some(detail),
}
}
Some(cmd) => {
let actor = read(fu_tree, "command.admin");
let request_id = read(fu_tree, "command.request_id");
let auth_outcome = read(ou_tree, "authorization.status")
.or_else(|| read(ou_tree, "result.status"));
CycleContext {
cycle_class: CycleClass::Command,
command_type: Some(cmd.into()),
actor,
request_id,
authorization_outcome: auth_outcome,
detail: None,
}
}
None => {
match event_type.as_deref() {
Some("login_attempt") => {
let actor = read(fu_tree, "event.source");
let auth_outcome = read(ou_tree, "authorization.status")
.or_else(|| read(ou_tree, "result.status"));
CycleContext {
cycle_class: CycleClass::Login,
command_type: Some("login".into()),
actor,
request_id: None,
authorization_outcome: auth_outcome,
detail: None,
}
}
_ => {
CycleContext::heartbeat()
}
}
}
}
}
fn build_genesis_input_quad(&self, seed: &[u8]) -> Quad {
let seed_hash = *blake3::hash(seed).as_bytes();
let mut tree = Tree::new();
tree.insert("genesis.seed".into(), seed.to_vec());
tree.insert("genesis.seed_hash".into(), seed_hash.to_vec());
Quad::new(seed_hash, [0u8; 32], tree)
}
fn run_processor(
&mut self,
unit: UnitId,
cycle_index: u64,
input: &Quad,
) -> RingEngineResult<Quad> {
let data_quad = self.ring.unit(unit).som_quad(Layer::Data).clone();
let processor = self.processors[unit.index()]
.as_mut()
.ok_or(RingEngineError::UnitDisabled { unit })?;
let output = processor.process(unit, cycle_index, input, &data_quad)?;
if let Some(new_data) = processor.externalize_state(unit) {
if unit == UnitId::FU {
let fu_data = self.ring.unit_mut(unit).som_quad_mut(Layer::Data);
for (k, v) in new_data.tree {
fu_data.tree.insert(k, v);
}
if new_data.root != [0u8; 32] {
fu_data.root = new_data.root;
}
if new_data.pointer != [0u8; 32] {
fu_data.pointer = new_data.pointer;
}
} else {
*self.ring.unit_mut(unit).som_quad_mut(Layer::Data) = new_data;
}
}
Ok(output)
}
fn run_horizontal_path(
&mut self,
unit: UnitId,
cycle_index: u64,
h_crossing: &CrossingResult,
) -> RingEngineResult<()> {
let stripped_h_quad = &h_crossing.delivery_envelope.quad;
let client_quad = {
let processor = self.processors[unit.index()]
.as_mut()
.ok_or(RingEngineError::UnitDisabled { unit })?;
processor.process_layer(unit, cycle_index, Layer::Client, stripped_h_quad)?
};
*self.ring.unit_mut(unit).som_quad_mut(Layer::Client) = client_quad.clone();
let interface_quad = {
let processor = self.processors[unit.index()]
.as_mut()
.ok_or(RingEngineError::UnitDisabled { unit })?;
processor.process_layer(unit, cycle_index, Layer::Interface, &client_quad)?
};
*self.ring.unit_mut(unit).som_quad_mut(Layer::Interface) = interface_quad;
Ok(())
}
fn deposit_server_output(&mut self, unit: UnitId, server_output: &Quad) {
let us = self.ring.unit_mut(unit);
*us.som_quad_mut(Layer::Server) = server_output.clone();
us.soma_quad = Quad::new(server_output.root, server_output.pointer, {
let mut t = Tree::new();
t.insert("soma.unit".into(), format!("{unit}").into_bytes());
t.insert(
"soma.root_hash".into(),
server_output.content_hash().to_vec(),
);
t
});
}
fn deposit_cycle_result(&mut self, cycle_index: u64, entry: &LedgerEntry) {
let fu_data = self.ring.unit_mut(UnitId::FU).som_quad_mut(Layer::Data);
fu_data
.tree
.insert("cycle.index".into(), cycle_index.to_le_bytes().to_vec());
fu_data.tree.insert(
"cycle.awareness_fingerprint".into(),
entry.awareness_fingerprint.to_vec(),
);
fu_data.tree.insert(
"cycle.system_fingerprint".into(),
entry.system_fingerprint.to_vec(),
);
fu_data
.tree
.insert("cycle.chain_hash".into(), entry.chain_hash.to_vec());
fu_data.root = entry.chain_hash;
}
fn persist_after_genesis(&self, crossing_records: &[CrossingRecord], seed: &[u8]) {
if let Some(ref store) = self.within_store {
let Some(ledger_entry) = self.ledger.genesis() else {
warn!("persist_after_genesis called without ledger genesis entry");
return;
};
if let Err(e) = store.persist_genesis(
&self.ring,
crossing_records,
ledger_entry,
seed,
&self.boundary.verifying_key_bytes(),
) {
warn!(error = %e, "failed to persist genesis");
}
self.persist_perspectival(0);
}
}
fn persist_after_cycle(&self, cycle_index: u64, crossing_records: &[CrossingRecord]) {
if let Some(ref store) = self.within_store {
let Some(ledger_entry) = self.ledger.latest() else {
warn!(cycle_index, "persist_after_cycle called without ledger entry");
return;
};
if let Err(e) =
store.persist_cycle(&self.ring, cycle_index, crossing_records, ledger_entry)
{
warn!(cycle_index, error = %e, "failed to persist cycle");
}
self.persist_perspectival(cycle_index);
}
}
fn persist_perspectival(&self, cycle_index: u64) {
if let Some(ref store) = self.within_store {
let entries: Vec<(UnitId, _)> = UnitId::ALL
.iter()
.filter_map(|&unit| {
self.perspectival_chains[unit.index()]
.entry(cycle_index)
.map(|e| (unit, e.clone()))
})
.collect();
if let Some(xv) = self.latest_cross_verification {
if let Err(e) = store.persist_perspectival(cycle_index, &entries, &xv) {
warn!(cycle_index, error = %e, "failed to persist perspectival data");
}
}
}
}
}
impl std::fmt::Debug for RingEngine {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("RingEngine")
.field("cycle_index", &self.ring.cycle_index)
.field("genesis_complete", &self.genesis_complete)
.field(
"processors",
&self
.processors
.iter()
.map(|p| p.is_some())
.collect::<Vec<_>>(),
)
.field("before_gates", &self.before_gates.len())
.field("after_observers", &self.after_observers.len())
.field("through_delegate_count", &self.through_delegates.len())
.field("around_extensions", &self.around_extensions.len())
.field("within_store", &self.within_store.is_some())
.field("command_registry_entries", &self.command_registry.len())
.field("registered_names", &self.registered_names)
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
use soma_som_core::extension::{Extension, GateRejection};
use soma_som_core::lexicon::VocabCoord;
use soma_som_core::ring::RingState;
use soma_som_core::types::{Element, World};
use std::sync::{Arc, Mutex};
const TEST_SEED: &[u8] = b"soma-ring-engine-test-seed-v1";
const TEST_SECRET: [u8; 32] = [42u8; 32];
struct StubRingProcessor {
invocation_count: u64,
}
impl StubRingProcessor {
fn new() -> Self {
Self {
invocation_count: 0,
}
}
}
impl RingProcessor for StubRingProcessor {
fn process(
&mut self,
unit: UnitId,
cycle: u64,
input: &Quad,
_data: &Quad,
) -> Result<Quad, RingEngineError> {
self.invocation_count += 1;
let root = {
let mut h = blake3::Hasher::new();
h.update(&[unit as u8]);
h.update(&cycle.to_le_bytes());
h.update(&input.root);
*h.finalize().as_bytes()
};
let pointer = {
let mut h = blake3::Hasher::new();
h.update(&[unit as u8]);
h.update(b"pointer");
h.update(&cycle.to_le_bytes());
*h.finalize().as_bytes()
};
let mut tree = input.tree.clone();
tree.insert("processor.unit".into(), format!("{unit}").into_bytes());
tree.insert("processor.cycle".into(), cycle.to_le_bytes().to_vec());
tree.insert("processor.name".into(), b"StubRingProcessor".to_vec());
Ok(Quad::new(root, pointer, tree))
}
fn externalize_state(&self, unit: UnitId) -> Option<Quad> {
let mut tree = Tree::new();
tree.insert("stub.unit".into(), format!("{unit}").into_bytes());
tree.insert(
"stub.invocations".into(),
self.invocation_count.to_le_bytes().to_vec(),
);
let root = {
let mut h = blake3::Hasher::new();
h.update(b"stub.data");
h.update(&[unit as u8]);
*h.finalize().as_bytes()
};
Some(Quad::new(root, [0u8; 32], tree))
}
fn process_layer(
&mut self,
unit: UnitId,
cycle: u64,
layer: Layer,
input: &Quad,
) -> Result<Quad, RingEngineError> {
let root = {
let mut h = blake3::Hasher::new();
h.update(&[unit as u8]);
h.update(&[layer as u8]);
h.update(&cycle.to_le_bytes());
h.update(&input.root);
*h.finalize().as_bytes()
};
let pointer = {
let mut h = blake3::Hasher::new();
h.update(&[unit as u8]);
h.update(&[layer as u8]);
h.update(b"layer-pointer");
h.update(&cycle.to_le_bytes());
*h.finalize().as_bytes()
};
let mut tree = Tree::new();
tree.insert("layer".into(), format!("{layer:?}").into_bytes());
tree.insert("unit".into(), format!("{unit}").into_bytes());
Ok(Quad::new(root, pointer, tree))
}
}
struct FailingRingProcessor {
message: String,
}
impl FailingRingProcessor {
fn new(msg: &str) -> Self {
Self {
message: msg.into(),
}
}
}
impl RingProcessor for FailingRingProcessor {
fn process(
&mut self,
unit: UnitId,
_cycle: u64,
_input: &Quad,
_data: &Quad,
) -> Result<Quad, RingEngineError> {
Err(RingEngineError::ProcessorFailed {
unit,
reason: self.message.clone(),
})
}
}
fn make_engine() -> RingEngine {
let mut engine = RingEngine::with_test_config(&TEST_SECRET);
engine.set_all_processors(|| Box::new(StubRingProcessor::new()));
engine
}
#[test]
fn genesis_succeeds() {
let mut engine = make_engine();
let report = engine.genesis(TEST_SEED).unwrap();
assert_ne!(report.genesis.system_fingerprint, [0u8; 32]);
assert_ne!(report.genesis.genesis_hash, [0u8; 32]);
assert_eq!(report.crossing_records.len(), 12);
assert!(engine.is_genesis_complete());
}
#[test]
fn genesis_populates_ledger() {
let mut engine = make_engine();
engine.genesis(TEST_SEED).unwrap();
assert_eq!(engine.ledger().len(), 1);
assert_eq!(engine.ledger().genesis().unwrap().cycle_index, 0);
}
#[test]
fn genesis_transitions_ring_to_ring_mode() {
let mut engine = make_engine();
engine.genesis(TEST_SEED).unwrap();
assert_eq!(engine.ring().state, RingState::Ring);
}
#[test]
fn double_genesis_fails() {
let mut engine = make_engine();
engine.genesis(TEST_SEED).unwrap();
assert!(matches!(
engine.genesis(TEST_SEED),
Err(RingEngineError::GenesisAlreadyComplete)
));
}
#[test]
fn genesis_without_processors_fails() {
let mut engine = RingEngine::with_test_config(&TEST_SECRET);
assert!(matches!(
engine.genesis(TEST_SEED),
Err(RingEngineError::UnitDisabled { .. })
));
}
#[test]
fn different_seeds_produce_different_genesis() {
let mut e1 = make_engine();
let r1 = e1.genesis(b"seed-alpha").unwrap();
let mut e2 = make_engine();
let r2 = e2.genesis(b"seed-beta").unwrap();
assert_ne!(r1.genesis.genesis_hash, r2.genesis.genesis_hash);
}
#[test]
fn cycle_after_genesis_succeeds() {
let mut engine = make_engine();
engine.genesis(TEST_SEED).unwrap();
let report = engine.cycle().unwrap();
assert_eq!(report.cycle_index, 1);
assert_ne!(report.awareness_fingerprint, [0u8; 32]);
assert_eq!(report.crossing_records.len(), 12);
}
#[test]
fn cycle_without_genesis_fails() {
let mut engine = make_engine();
assert!(matches!(
engine.cycle(),
Err(RingEngineError::NotInitialized)
));
}
#[test]
fn multiple_cycles_build_chain() {
let mut engine = make_engine();
engine.genesis(TEST_SEED).unwrap();
let r1 = engine.cycle().unwrap();
let r2 = engine.cycle().unwrap();
let r3 = engine.cycle().unwrap();
assert_eq!(r1.cycle_index, 1);
assert_eq!(r2.cycle_index, 2);
assert_eq!(r3.cycle_index, 3);
assert_ne!(r1.chain_hash, r2.chain_hash);
assert_eq!(engine.ledger().len(), 4);
}
#[test]
fn ledger_chain_is_verifiable_after_cycles() {
let mut engine = make_engine();
engine.genesis(TEST_SEED).unwrap();
for _ in 0..10 {
engine.cycle().unwrap();
}
assert!(engine.verify_chain().is_ok());
assert_eq!(engine.ledger().len(), 11);
}
#[test]
fn consecutive_cycles_produce_different_fingerprints() {
let mut engine = make_engine();
engine.genesis(TEST_SEED).unwrap();
let r1 = engine.cycle().unwrap();
let r2 = engine.cycle().unwrap();
assert_ne!(r1.awareness_fingerprint, r2.awareness_fingerprint);
}
#[test]
fn disabled_unit_causes_cycle_failure() {
let mut engine = make_engine();
engine.genesis(TEST_SEED).unwrap();
engine.disable(UnitId::CU);
match engine.cycle().unwrap_err() {
RingEngineError::UnitDisabled { unit } => assert_eq!(unit, UnitId::CU),
other => panic!("Expected UnitDisabled, got {other:?}"),
}
}
#[test]
fn criterion_1_all_six_units() {
for &target in &UnitId::ALL {
let mut engine = make_engine();
engine.genesis(TEST_SEED).unwrap();
engine.disable(target);
assert!(engine.cycle().is_err(), "Disabling {target} must fail");
}
}
#[test]
fn failing_processor_causes_explicit_error() {
let mut engine = make_engine();
engine.set_processor(
UnitId::MU,
Box::new(FailingRingProcessor::new("MU crashed")),
);
engine.genesis(TEST_SEED).unwrap_err();
let mut engine = make_engine();
engine.genesis(TEST_SEED).unwrap();
engine.set_processor(
UnitId::MU,
Box::new(FailingRingProcessor::new("MU crashed")),
);
assert!(matches!(
engine.cycle(),
Err(RingEngineError::ProcessorFailed {
unit: UnitId::MU,
..
})
));
}
#[test]
fn crossing_records_form_valid_chain() {
let mut engine = make_engine();
engine.genesis(TEST_SEED).unwrap();
let report = engine.cycle().unwrap();
let records = &report.crossing_records;
assert_eq!(records.len(), 12);
for r in records {
assert!(r.verify_chain_hash());
}
for i in 1..records.len() {
assert_eq!(records[i].prev_hash, records[i - 1].chain_hash);
}
}
#[test]
fn crossing_records_have_correct_routing() {
let mut engine = make_engine();
engine.genesis(TEST_SEED).unwrap();
let report = engine.cycle().unwrap();
let expected: Vec<(UnitId, UnitId, CrossingType)> = vec![
(UnitId::FU, UnitId::MU, CrossingType::Vertical),
(UnitId::FU, UnitId::FU, CrossingType::Horizontal),
(UnitId::MU, UnitId::CU, CrossingType::Vertical),
(UnitId::MU, UnitId::MU, CrossingType::Horizontal),
(UnitId::CU, UnitId::OU, CrossingType::Vertical),
(UnitId::CU, UnitId::CU, CrossingType::Horizontal),
(UnitId::OU, UnitId::SU, CrossingType::Vertical),
(UnitId::OU, UnitId::OU, CrossingType::Horizontal),
(UnitId::SU, UnitId::HU, CrossingType::Vertical),
(UnitId::SU, UnitId::SU, CrossingType::Horizontal),
(UnitId::HU, UnitId::FU, CrossingType::Vertical),
(UnitId::HU, UnitId::HU, CrossingType::Horizontal),
];
for (i, (record, (src, dst, ct))) in report
.crossing_records
.iter()
.zip(expected.iter())
.enumerate()
{
assert_eq!(record.source, *src, "Record {} source", i + 1);
assert_eq!(record.destination, *dst, "Record {} dest", i + 1);
assert_eq!(record.crossing_type, *ct, "Record {} type", i + 1);
}
}
#[test]
fn twelve_crossings_alternate_v_h() {
let mut engine = make_engine();
engine.genesis(TEST_SEED).unwrap();
let report = engine.cycle().unwrap();
for (i, record) in report.crossing_records.iter().enumerate() {
let expected = if (i + 1) % 2 == 1 {
CrossingType::Vertical
} else {
CrossingType::Horizontal
};
assert_eq!(record.crossing_type, expected, "nseq {}", i + 1);
}
}
#[test]
fn horizontal_crossings_are_self_referential() {
let mut engine = make_engine();
engine.genesis(TEST_SEED).unwrap();
let report = engine.cycle().unwrap();
let h: Vec<_> = report
.crossing_records
.iter()
.filter(|r| r.crossing_type == CrossingType::Horizontal)
.collect();
assert_eq!(h.len(), 6);
for r in &h {
assert_eq!(r.source, r.destination);
}
}
#[test]
fn ring_is_populated_after_genesis() {
let mut engine = make_engine();
engine.genesis(TEST_SEED).unwrap();
assert!(engine.ring().is_populated());
}
#[test]
fn fingerprint_accessors_match_report() {
let mut engine = make_engine();
engine.genesis(TEST_SEED).unwrap();
let report = engine.cycle().unwrap();
assert_eq!(engine.awareness_fingerprint(), report.awareness_fingerprint);
assert_eq!(engine.system_fingerprint(), report.system_fingerprint);
}
#[test]
fn criterion_2_different_units_different_fingerprints() {
let mut engine = make_engine();
engine.genesis(TEST_SEED).unwrap();
engine.cycle().unwrap();
let mut roots: Vec<_> = UnitId::ALL
.iter()
.map(|&u| engine.ring().unit(u).som_quad(Layer::Server).root)
.collect();
roots.sort();
roots.dedup();
assert_eq!(roots.len(), 6);
}
#[test]
fn client_and_interface_populated_after_cycle() {
let mut engine = make_engine();
engine.genesis(TEST_SEED).unwrap();
engine.cycle().unwrap();
for &unit in &UnitId::ALL {
assert!(!engine.ring().unit(unit).som_quad(Layer::Client).is_empty());
assert!(
!engine
.ring()
.unit(unit)
.som_quad(Layer::Interface)
.is_empty()
);
}
}
#[test]
fn all_72_som_positions_populated_after_cycle() {
let mut engine = make_engine();
engine.genesis(TEST_SEED).unwrap();
engine.cycle().unwrap();
for &unit in &UnitId::ALL {
for &layer in &[Layer::Server, Layer::Client, Layer::Interface] {
assert!(
!engine.ring().unit(unit).som_quad(layer).is_empty(),
"{unit}.{layer:?} non-empty"
);
}
}
}
#[test]
fn all_30_quads_populated_after_cycle() {
let mut engine = make_engine();
engine.genesis(TEST_SEED).unwrap();
engine.cycle().unwrap();
for &unit in &UnitId::ALL {
for &layer in &Layer::ALL {
assert!(
!engine.ring().unit(unit).som_quad(layer).is_empty(),
"{unit}.{layer:?} must be non-empty (30-Quad completeness)",
);
}
}
}
#[test]
fn fu_data_merge_preserves_around_injected_keys() {
let mut engine = make_engine();
engine.genesis(TEST_SEED).unwrap();
{
let fu_data = &mut engine
.ring_mut()
.unit_mut(UnitId::FU)
.som_quad_mut(Layer::Data)
.tree;
fu_data.insert("command.type".into(), b"user.create".to_vec());
}
engine.cycle().unwrap();
let fu_data = &engine.ring().unit(UnitId::FU).som_quad(Layer::Data).tree;
assert!(
fu_data.contains_key("stub.unit"),
"FU.Data must contain externalized stub keys"
);
assert!(
fu_data.contains_key("cycle.index"),
"FU.Data must contain cycle metadata from deposit_cycle_result"
);
}
#[test]
fn genesis_initializes_perspectival_chains() {
let mut engine = make_engine();
let report = engine.genesis(TEST_SEED).unwrap();
for &unit in &UnitId::ALL {
assert_eq!(engine.perspectival_chain(unit).len(), 1);
}
assert_ne!(report.cross_verification_digest, [0u8; 32]);
assert_eq!(
engine.cross_verification_digest(),
Some(report.cross_verification_digest)
);
}
#[test]
fn perspectival_chains_verify_after_cycles() {
let mut engine = make_engine();
engine.genesis(TEST_SEED).unwrap();
for _ in 0..10 {
engine.cycle().unwrap();
}
assert!(engine.verify_perspectival_chains().is_ok());
}
#[test]
fn cross_verification_changes_each_cycle() {
let mut engine = make_engine();
let gr = engine.genesis(TEST_SEED).unwrap();
let mut prev = gr.cross_verification_digest;
for _ in 1..=5 {
let r = engine.cycle().unwrap();
assert_ne!(r.cross_verification_digest, prev);
prev = r.cross_verification_digest;
}
}
#[test]
fn cross_verification_deposited_in_fu_data() {
let mut engine = make_engine();
engine.genesis(TEST_SEED).unwrap();
let report = engine.cycle().unwrap();
let deposited = engine
.ring()
.unit(UnitId::FU)
.som_quad(Layer::Data)
.tree
.get("perspectival.cross_verification_digest")
.unwrap();
assert_eq!(deposited.as_slice(), &report.cross_verification_digest[..]);
}
#[test]
fn both_ledgers_verify_after_extended_run() {
let mut engine = make_engine();
engine.genesis(TEST_SEED).unwrap();
for _ in 0..20 {
engine.cycle().unwrap();
}
assert!(engine.verify_chain().is_ok());
assert!(engine.verify_perspectival_chains().is_ok());
assert_eq!(engine.ledger().len(), 21);
}
struct RecordingObserver {
entries: Arc<Mutex<Vec<String>>>,
}
impl RecordingObserver {
fn new() -> (Self, Arc<Mutex<Vec<String>>>) {
let e = Arc::new(Mutex::new(Vec::new()));
(Self { entries: e.clone() }, e)
}
}
impl CycleObserver for RecordingObserver {
fn on_unit_enter(&self, unit: UnitId, ci: u64, step: u32) {
self.entries
.lock()
.unwrap()
.push(format!("enter:{unit}:{ci}:{step}"));
}
fn on_unit_exit(&self, unit: UnitId, ci: u64, step: u32, ms: u64) {
self.entries
.lock()
.unwrap()
.push(format!("exit:{unit}:{ci}:{step}:{ms}ms"));
}
}
#[test]
fn cycle_observed_records_all_unit_events() {
let mut engine = make_engine();
engine.genesis(TEST_SEED).unwrap();
let (obs, entries) = RecordingObserver::new();
let report = engine.cycle_observed(&obs).unwrap();
let log = entries.lock().unwrap();
assert_eq!(log.len(), 12);
for i in 0..6 {
assert!(log[i * 2].starts_with("enter:"));
assert!(log[i * 2 + 1].starts_with("exit:"));
}
assert_eq!(report.cycle_index, 1);
}
#[test]
fn noop_observer_produces_same_result() {
let mut engine = make_engine();
engine.genesis(TEST_SEED).unwrap();
let r1 = engine.cycle_observed(&NoOpObserver).unwrap();
let r2 = engine.cycle().unwrap();
assert_eq!(r1.cycle_index, 1);
assert_eq!(r2.cycle_index, 2);
}
struct TestGate {
reject: bool,
feedback_log: Arc<Mutex<Vec<u64>>>,
}
impl TestGate {
fn new(reject: bool) -> (Self, Arc<Mutex<Vec<u64>>>) {
let log = Arc::new(Mutex::new(Vec::new()));
(
Self {
reject,
feedback_log: log.clone(),
},
log,
)
}
}
impl Extension for TestGate {
fn name(&self) -> &str {
"test-gate"
}
}
impl BeforeRing for TestGate {
fn evaluate(&self, _ctx: &Tree) -> Result<(), GateRejection> {
if self.reject {
Err(GateRejection {
source: "test".into(),
reason: "rejected".into(),
})
} else {
Ok(())
}
}
fn feedback(&self, ci: u64, _output: &Tree) {
self.feedback_log.lock().unwrap().push(ci);
}
}
#[test]
fn before_gate_rejects_cycle() {
let mut engine = make_engine();
engine.genesis(TEST_SEED).unwrap();
let (gate, _) = TestGate::new(true);
engine.register_before(Box::new(gate));
assert!(matches!(
engine.cycle(),
Err(RingEngineError::GateRejected(_))
));
}
#[test]
fn before_gate_allows_and_receives_feedback() {
let mut engine = make_engine();
engine.genesis(TEST_SEED).unwrap();
let (gate, log) = TestGate::new(false);
engine.register_before(Box::new(gate));
engine.cycle().unwrap();
assert_eq!(*log.lock().unwrap(), vec![1]);
}
#[test]
fn before_gate_short_circuits_on_first_rejection() {
let mut engine = make_engine();
engine.genesis(TEST_SEED).unwrap();
let (pass, pass_log) = TestGate::new(false);
let (reject, _) = TestGate::new(true);
engine.register_before(Box::new(pass));
engine.register_before(Box::new(reject));
assert!(engine.cycle().is_err());
assert!(pass_log.lock().unwrap().is_empty());
}
struct TestAround {
key: String,
val: Vec<u8>,
log: Arc<Mutex<Vec<u64>>>,
}
impl TestAround {
fn new(k: &str, v: &[u8]) -> (Self, Arc<Mutex<Vec<u64>>>) {
let log = Arc::new(Mutex::new(Vec::new()));
(
Self {
key: k.into(),
val: v.to_vec(),
log: log.clone(),
},
log,
)
}
}
impl Extension for TestAround {
fn name(&self) -> &str {
"test-around"
}
}
impl AroundRing for TestAround {
fn inject(&self, fu: &mut Tree) {
fu.insert(self.key.clone(), self.val.clone());
}
fn observe(&self, ci: u64, _ou: &Tree, _su: &Tree) {
self.log.lock().unwrap().push(ci);
}
}
#[test]
fn around_injects_and_observes() {
let mut engine = make_engine();
engine.genesis(TEST_SEED).unwrap();
let (around, log) = TestAround::new("test.injected", b"hello");
engine.register_around(Box::new(around));
engine.cycle().unwrap();
assert_eq!(*log.lock().unwrap(), vec![1]);
}
#[test]
fn multiple_around_compose_additively() {
let mut engine = make_engine();
engine.genesis(TEST_SEED).unwrap();
let (a1, l1) = TestAround::new("test.k1", b"v1");
let (a2, l2) = TestAround::new("test.k2", b"v2");
engine.register_around(Box::new(a1));
engine.register_around(Box::new(a2));
engine.cycle().unwrap();
assert_eq!(*l1.lock().unwrap(), vec![1]);
assert_eq!(*l2.lock().unwrap(), vec![1]);
}
struct TestAfter {
log: Arc<Mutex<Vec<u64>>>,
}
impl TestAfter {
fn new() -> (Self, Arc<Mutex<Vec<u64>>>) {
let log = Arc::new(Mutex::new(Vec::new()));
(Self { log: log.clone() }, log)
}
}
impl Extension for TestAfter {
fn name(&self) -> &str {
"test-after"
}
}
impl AfterRing for TestAfter {
fn on_cycle_complete(&self, ci: u64, _output: &Tree) {
self.log.lock().unwrap().push(ci);
}
}
#[test]
fn after_observer_notified() {
let mut engine = make_engine();
engine.genesis(TEST_SEED).unwrap();
let (after, log) = TestAfter::new();
engine.register_after(Box::new(after));
engine.cycle().unwrap();
engine.cycle().unwrap();
assert_eq!(*log.lock().unwrap(), vec![1, 2]);
}
#[test]
fn full_extension_lifecycle() {
let mut engine = make_engine();
engine.genesis(TEST_SEED).unwrap();
let (gate, gate_fb) = TestGate::new(false);
let (around, around_log) = TestAround::new("ext.d", b"x");
let (after, after_log) = TestAfter::new();
engine.register_before(Box::new(gate));
engine.register_around(Box::new(around));
engine.register_after(Box::new(after));
engine.cycle().unwrap();
engine.cycle().unwrap();
assert_eq!(*gate_fb.lock().unwrap(), vec![1, 2]);
assert_eq!(*around_log.lock().unwrap(), vec![1, 2]);
assert_eq!(*after_log.lock().unwrap(), vec![1, 2]);
}
#[test]
fn run_cycles_batch() {
let mut engine = make_engine();
engine.genesis(TEST_SEED).unwrap();
let results = engine.run_cycles(5);
assert_eq!(results.len(), 5);
for (i, r) in results.iter().enumerate() {
assert_eq!(r.as_ref().unwrap().cycle_index, (i + 1) as u64);
}
}
#[test]
fn debug_format() {
let engine = make_engine();
let debug = format!("{engine:?}");
assert!(debug.contains("RingEngine"));
}
#[test]
fn cycle_report_carries_context() {
let mut engine = make_engine();
engine.genesis(b"m1d-seed").unwrap();
let report = engine.cycle().unwrap();
assert_eq!(report.context.cycle_class, CycleClass::Heartbeat);
assert!(report.context.command_type.is_none());
assert!(report.context.actor.is_none());
}
#[test]
fn genesis_report_context_does_not_affect_chain_hash() {
let mut e1 = make_engine();
let mut e2 = make_engine();
let r1 = e1.genesis(b"same-seed").unwrap();
let r2 = e2.genesis(b"same-seed").unwrap();
assert_eq!(r1.genesis.genesis_hash, r2.genesis.genesis_hash);
}
#[test]
fn different_semantic_contexts_produce_different_chain_hashes() {
let mut e1 = make_engine();
e1.genesis(b"seed-diff-ctx").unwrap();
let r1 = e1.cycle().unwrap();
let mut e2 = make_engine();
e2.genesis(b"seed-diff-ctx").unwrap();
{
let fu_data = &mut e2
.ring_mut()
.unit_mut(UnitId::FU)
.som_quad_mut(Layer::Data)
.tree;
fu_data.insert("command.type".into(), b"user.create".to_vec());
fu_data.insert("command.admin".into(), b"testuser".to_vec());
fu_data.insert("command.request_id".into(), b"req-42".to_vec());
}
let r2 = e2.cycle().unwrap();
assert_ne!(r1.chain_hash, r2.chain_hash);
assert_eq!(r1.context.cycle_class, CycleClass::Heartbeat);
assert_eq!(r2.context.cycle_class, CycleClass::Command);
assert_eq!(r2.context.command_type.as_deref(), Some("user.create"));
assert_eq!(r2.context.actor.as_deref(), Some("testuser"));
}
#[test]
fn login_event_extraction() {
let mut engine = make_engine();
engine.genesis(b"login-seed").unwrap();
{
let fu_data = &mut engine
.ring_mut()
.unit_mut(UnitId::FU)
.som_quad_mut(Layer::Data)
.tree;
fu_data.insert("event.type".into(), b"login_attempt".to_vec());
fu_data.insert("event.source".into(), b"alice".to_vec());
}
let report = engine.cycle().unwrap();
assert_eq!(report.context.cycle_class, CycleClass::Login);
assert_eq!(report.context.command_type.as_deref(), Some("login"));
assert_eq!(report.context.actor.as_deref(), Some("alice"));
}
#[test]
fn gate_transition_extraction() {
let mut engine = make_engine();
engine.genesis(b"gate-seed").unwrap();
{
let fu_data = &mut engine
.ring_mut()
.unit_mut(UnitId::FU)
.som_quad_mut(Layer::Data)
.tree;
fu_data.insert("command.type".into(), b"gate.transition".to_vec());
fu_data.insert("command.admin".into(), b"operator1".to_vec());
fu_data.insert("command.request_id".into(), b"req-gate".to_vec());
fu_data.insert("gate.name".into(), b"G1".to_vec());
fu_data.insert("gate.evidence.design_review".into(), b"present".to_vec());
fu_data.insert("gate.evidence.test_report".into(), b"present".to_vec());
}
let report = engine.cycle().unwrap();
assert_eq!(report.context.cycle_class, CycleClass::GateTransition);
assert_eq!(
report.context.command_type.as_deref(),
Some("gate.transition")
);
assert_eq!(report.context.actor.as_deref(), Some("operator1"));
assert!(report.context.detail.as_ref().unwrap().contains("gate=G1"));
assert!(
report
.context
.detail
.as_ref()
.unwrap()
.contains("evidence=2")
);
}
use soma_som_core::extension::{
DelegationError, DomainRequest, RegistrationError, SiblingManifest,
};
struct TestSiblingManifest {
id: String,
min_ring_version: String,
domains: Vec<DomainRequest>,
}
impl TestSiblingManifest {
fn new(id: &str) -> Self {
Self {
id: id.to_string(),
min_ring_version: "1.0.0".into(),
domains: vec![],
}
}
fn with_min_version(mut self, v: &str) -> Self {
self.min_ring_version = v.to_string();
self
}
fn with_domain(mut self, id: &str) -> Self {
self.domains.push(DomainRequest {
sibling_id: id.to_string(),
display_name: id.to_string(),
version: "1.0.0".into(),
max_capacity_bytes: None,
});
self
}
}
impl SiblingManifest for TestSiblingManifest {
fn id(&self) -> &str {
&self.id
}
fn display_name(&self) -> &str {
&self.id
}
fn version(&self) -> &str {
"1.0.0"
}
fn min_ring_version(&self) -> &str {
&self.min_ring_version
}
fn through_handler(&self) -> Option<Arc<dyn ThroughRing>> {
let id = self.id.clone();
Some(Arc::new(TestSiblingThrough { id }))
}
fn persistence_domains(&self) -> Vec<DomainRequest> {
self.domains.clone()
}
}
struct TestSiblingThrough {
id: String,
}
impl Extension for TestSiblingThrough {
fn name(&self) -> &str {
&self.id
}
}
impl ThroughRing for TestSiblingThrough {
fn handles(&self, command_type: &str) -> bool {
command_type.starts_with(&format!("sibling.{}.", self.id))
}
fn dispatch(&self, input: &Tree) -> Result<Tree, DelegationError> {
let mut result = Tree::new();
result.insert("result.status".into(), b"ok".to_vec());
result.insert("result.source".into(), self.id.as_bytes().to_vec());
if let Some(req_id) = input.get("command.request_id") {
result.insert("result.request_id".into(), req_id.clone());
}
Ok(result)
}
}
#[test]
fn register_sibling_happy_path() {
let mut engine = RingEngine::new(TimingConfig::default());
let manifest = TestSiblingManifest::new("qi");
let result =
engine.register_sibling(&manifest, None::<fn(&DomainRequest) -> Result<(), String>>);
assert!(result.is_ok());
assert!(engine.has_registered_name("qi"));
assert_eq!(engine.through_delegate_count(), 1);
assert_eq!(engine.probe_namespace("sibling.qi.analyze"), Some("qi"));
}
#[test]
fn register_sibling_duplicate_rejected() {
let mut engine = RingEngine::new(TimingConfig::default());
let m1 = TestSiblingManifest::new("qi");
let m2 = TestSiblingManifest::new("qi");
engine
.register_sibling(&m1, None::<fn(&DomainRequest) -> Result<(), String>>)
.unwrap();
let result = engine.register_sibling(&m2, None::<fn(&DomainRequest) -> Result<(), String>>);
assert!(matches!(result, Err(RegistrationError::DuplicateId(ref id)) if id == "qi"));
}
#[test]
fn register_sibling_namespace_collision_detected() {
let mut engine = RingEngine::new(TimingConfig::default());
struct ProjectLegacyThrough;
impl Extension for ProjectLegacyThrough {
fn name(&self) -> &str {
"project-legacy"
}
}
impl ThroughRing for ProjectLegacyThrough {
fn handles(&self, cmd: &str) -> bool {
cmd.starts_with("sibling.project.")
}
fn dispatch(&self, _input: &Tree) -> Result<Tree, DelegationError> {
Ok(Tree::new())
}
}
engine.register_through(Arc::new(ProjectLegacyThrough)).unwrap();
let manifest = TestSiblingManifest::new("project");
let result =
engine.register_sibling(&manifest, None::<fn(&DomainRequest) -> Result<(), String>>);
assert!(matches!(
result,
Err(RegistrationError::NamespaceConflict { .. })
));
}
#[test]
fn register_sibling_incompatible_version_rejected() {
let mut engine = RingEngine::new(TimingConfig::default());
let manifest = TestSiblingManifest::new("future").with_min_version("99.0.0");
let result =
engine.register_sibling(&manifest, None::<fn(&DomainRequest) -> Result<(), String>>);
assert!(matches!(
result,
Err(RegistrationError::IncompatibleVersion { .. })
));
}
#[test]
fn register_sibling_provision_called() {
let mut engine = RingEngine::new(TimingConfig::default());
let manifest = TestSiblingManifest::new("qi").with_domain("qi");
let provisioned = Arc::new(Mutex::new(Vec::new()));
let prov_clone = Arc::clone(&provisioned);
let result = engine.register_sibling(
&manifest,
Some(move |req: &DomainRequest| {
prov_clone.lock().unwrap().push(req.sibling_id.clone());
Ok(())
}),
);
assert!(result.is_ok());
assert_eq!(provisioned.lock().unwrap().as_slice(), &["qi"]);
}
#[test]
fn register_sibling_provision_failure_rolls_back() {
let mut engine = RingEngine::new(TimingConfig::default());
let manifest = TestSiblingManifest::new("qi").with_domain("qi");
let result = engine.register_sibling(
&manifest,
Some(|_: &DomainRequest| Err("disk full".to_string())),
);
assert!(matches!(result, Err(RegistrationError::ProvisionFailed(_))));
assert!(!engine.has_registered_name("qi"));
assert_eq!(engine.through_delegate_count(), 0);
}
#[test]
fn register_multiple_siblings() {
let mut engine = RingEngine::new(TimingConfig::default());
for id in &["qi", "project", "work", "ai"] {
let manifest = TestSiblingManifest::new(id);
engine
.register_sibling(&manifest, None::<fn(&DomainRequest) -> Result<(), String>>)
.unwrap();
}
assert_eq!(engine.through_delegate_count(), 4);
assert!(engine.has_registered_name("qi"));
assert!(engine.has_registered_name("project"));
assert!(engine.has_registered_name("work"));
assert!(engine.has_registered_name("ai"));
}
#[test]
fn registered_names_tracks_all_extension_types() {
let mut engine = RingEngine::new(TimingConfig::default());
let (gate, _) = TestGate::new(false);
engine.register_before(Box::new(gate));
assert!(engine.has_registered_name("test-gate"));
engine.register_through(Arc::new(TestSiblingThrough {
id: "direct-through".into(),
})).unwrap();
assert!(engine.has_registered_name("direct-through"));
}
#[test]
fn version_compatible_checks() {
assert!(RingEngine::version_compatible("1.0.0", "1.0.0"));
assert!(RingEngine::version_compatible("1.0.0", "1.1.0"));
assert!(RingEngine::version_compatible("1.0.0", "2.0.0"));
assert!(!RingEngine::version_compatible("2.0.0", "1.0.0"));
assert!(!RingEngine::version_compatible("1.1.0", "1.0.0"));
assert!(RingEngine::version_compatible("0.1.0", "1.0.0"));
}
#[test]
fn validation_failure_prevents_provision() {
let mut engine = RingEngine::new(TimingConfig::default());
struct QiLegacy;
impl Extension for QiLegacy {
fn name(&self) -> &str {
"qi-legacy"
}
}
impl ThroughRing for QiLegacy {
fn handles(&self, cmd: &str) -> bool {
cmd.starts_with("sibling.qi.")
}
fn dispatch(&self, _: &Tree) -> Result<Tree, DelegationError> {
Ok(Tree::new())
}
}
engine.register_through(Arc::new(QiLegacy)).unwrap();
let manifest = TestSiblingManifest::new("qi").with_domain("qi");
let result = engine.register_sibling(
&manifest,
Some(|_: &DomainRequest| {
panic!("provisioner should never be called when validation fails");
}),
);
assert!(matches!(
result,
Err(RegistrationError::NamespaceConflict { .. })
));
}
#[test]
fn probe_namespace_returns_none_for_unclaimed() {
let mut engine = RingEngine::new(TimingConfig::default());
let manifest = TestSiblingManifest::new("qi");
engine
.register_sibling(&manifest, None::<fn(&DomainRequest) -> Result<(), String>>)
.unwrap();
assert_eq!(engine.probe_namespace("sibling.qi.analyze"), Some("qi"));
assert_eq!(engine.probe_namespace("sibling.project.create"), None);
assert_eq!(engine.probe_namespace("user.create"), None);
assert_eq!(engine.probe_namespace("unrelated"), None);
}
#[test]
fn register_sibling_then_dispatch_through_ring_cycle() {
let mut engine = make_engine();
engine.genesis(TEST_SEED).unwrap();
let manifest = TestSiblingManifest::new("qi");
engine
.register_sibling(&manifest, None::<fn(&DomainRequest) -> Result<(), String>>)
.unwrap();
{
let fu_data = &mut engine
.ring_mut()
.unit_mut(UnitId::FU)
.som_quad_mut(Layer::Data)
.tree;
fu_data.insert("command.type".into(), b"sibling.qi.analyze".to_vec());
fu_data.insert("command.payload".into(), b"{}".to_vec());
fu_data.insert("command.request_id".into(), b"test-001".to_vec());
fu_data.insert("command.admin".into(), b"operator".to_vec());
}
let report = engine.cycle().unwrap();
assert!(report.cycle_index > 0);
assert!(!report.crossing_records.is_empty());
}
#[test]
fn before_gate_blocks_sibling_command() {
let mut engine = make_engine();
engine.genesis(TEST_SEED).unwrap();
let manifest = TestSiblingManifest::new("qi");
engine
.register_sibling(&manifest, None::<fn(&DomainRequest) -> Result<(), String>>)
.unwrap();
let (gate, _) = TestGate::new(true);
engine.register_before(Box::new(gate));
{
let fu_data = &mut engine
.ring_mut()
.unit_mut(UnitId::FU)
.som_quad_mut(Layer::Data)
.tree;
fu_data.insert("command.type".into(), b"sibling.qi.analyze".to_vec());
fu_data.insert("command.admin".into(), b"operator".to_vec());
}
assert!(matches!(
engine.cycle(),
Err(RingEngineError::GateRejected(_))
));
}
struct TestLexiconProvider {
domain: &'static str,
unit: UnitId,
}
impl LexiconProvider for TestLexiconProvider {
fn domain(&self) -> &str {
self.domain
}
fn declared_unit(&self) -> UnitId {
self.unit
}
fn primary_world(&self) -> World {
World::from_unit(self.unit)
}
fn vocabulary(&self) -> Vec<LexiconEntry> {
vec![LexiconEntry {
key: format!("{}.term.one", self.domain),
coordinate: VocabCoord { unit: self.unit, layer: Layer::Data, element: Element::Root },
default_en: "Term One".into(),
related: vec![],
ordinal: None,
invariant: false,
}]
}
fn describe(&self, key: &str) -> Option<TermDescription> {
if key == format!("{}.term.one", self.domain) {
Some(TermDescription {
summary: "The first term".into(),
explanation: "A test term for validation.".into(),
examples: vec![],
constraints: vec![],
})
} else {
None
}
}
}
#[test]
fn lexicon_register_and_query_vocabulary() {
let mut engine = make_engine();
engine.register_lexicon(Box::new(TestLexiconProvider { domain: "alpha", unit: UnitId::FU })).unwrap();
let vocab = engine.lexicon_vocabulary();
assert_eq!(vocab.len(), 1);
assert_eq!(vocab[0].key, "alpha.term.one");
}
#[test]
fn lexicon_multiple_providers() {
let mut engine = make_engine();
engine.register_lexicon(Box::new(TestLexiconProvider { domain: "alpha", unit: UnitId::FU })).unwrap();
engine.register_lexicon(Box::new(TestLexiconProvider { domain: "beta", unit: UnitId::CU })).unwrap();
let vocab = engine.lexicon_vocabulary();
assert_eq!(vocab.len(), 2);
let domains = engine.lexicon_domains();
assert_eq!(domains.len(), 2);
assert!(domains.contains(&"alpha"));
assert!(domains.contains(&"beta"));
}
#[test]
fn lexicon_describe_routes_to_correct_provider() {
let mut engine = make_engine();
engine.register_lexicon(Box::new(TestLexiconProvider { domain: "alpha", unit: UnitId::FU })).unwrap();
engine.register_lexicon(Box::new(TestLexiconProvider { domain: "beta", unit: UnitId::CU })).unwrap();
let desc = engine.lexicon_describe("alpha.term.one").unwrap();
assert_eq!(desc.summary, "The first term");
let desc = engine.lexicon_describe("beta.term.one").unwrap();
assert_eq!(desc.summary, "The first term");
assert!(engine.lexicon_describe("gamma.term.one").is_none());
}
#[test]
fn lexicon_idempotent_registration_replaces_provider() {
let mut engine = make_engine();
engine.register_lexicon(Box::new(TestLexiconProvider { domain: "alpha", unit: UnitId::FU })).unwrap();
engine.register_lexicon(Box::new(TestLexiconProvider { domain: "alpha", unit: UnitId::FU })).unwrap();
let vocab = engine.lexicon_vocabulary();
assert_eq!(vocab.len(), 1);
assert_eq!(engine.lexicon_domains().len(), 1);
}
#[test]
fn lexicon_empty_by_default() {
let engine = make_engine();
assert!(engine.lexicon_vocabulary().is_empty());
assert!(engine.lexicon_domains().is_empty());
assert!(engine.lexicon_describe("any.key").is_none());
}
#[test]
fn lexicon_register_wrong_unit_rejects_mount() {
struct WrongUnitProvider;
impl LexiconProvider for WrongUnitProvider {
fn domain(&self) -> &str { "bad" }
fn declared_unit(&self) -> UnitId { UnitId::FU }
fn primary_world(&self) -> World { World::WAI }
fn vocabulary(&self) -> Vec<LexiconEntry> {
vec![LexiconEntry {
key: "bad.term".into(),
coordinate: VocabCoord { unit: UnitId::MU, layer: Layer::Data, element: Element::Root },
default_en: "bad".into(),
related: vec![],
ordinal: None,
invariant: false,
}]
}
fn describe(&self, _key: &str) -> Option<TermDescription> { None }
}
let mut engine = make_engine();
let result = engine.register_lexicon(Box::new(WrongUnitProvider));
assert!(matches!(result, Err(RegistrationError::CoordinateValidation(_))));
}
#[test]
fn world_registry_empty_by_default() {
let engine = make_engine();
let reg = engine.world_registry();
assert!(reg.organs().is_empty());
assert!(reg.active_worlds().is_empty());
assert_eq!(reg.vacant_worlds().len(), 6);
}
#[test]
fn world_registry_built_during_registration() {
let mut engine = make_engine();
engine.register_lexicon(Box::new(TestLexiconProvider { domain: "alpha", unit: UnitId::FU })).unwrap();
let reg = engine.world_registry();
assert_eq!(reg.organs().len(), 1);
assert_eq!(reg.organs()[0].namespace, "alpha");
assert_eq!(reg.organs()[0].name, "ALPHA");
assert_eq!(reg.organs_by_world(World::WAI).len(), 1);
assert_eq!(reg.organs_by_world(World::WCN).len(), 0);
assert_eq!(reg.active_worlds().len(), 1);
assert_eq!(reg.vacant_worlds().len(), 5);
}
#[test]
fn world_registry_multiple_organs_different_worlds() {
let mut engine = make_engine();
engine.register_lexicon(Box::new(TestLexiconProvider { domain: "alpha", unit: UnitId::FU })).unwrap();
engine.register_lexicon(Box::new(TestLexiconProvider { domain: "beta", unit: UnitId::CU })).unwrap();
let reg = engine.world_registry();
assert_eq!(reg.organs().len(), 2);
assert_eq!(reg.organs_by_world(World::WAI).len(), 1);
assert_eq!(reg.organs_by_world(World::WCN).len(), 1);
assert_eq!(reg.active_worlds().len(), 2);
assert_eq!(reg.vacant_worlds().len(), 4);
}
#[test]
fn world_registry_two_organs_same_world() {
let mut engine = make_engine();
engine.register_lexicon(Box::new(TestLexiconProvider { domain: "wall", unit: UnitId::OU })).unwrap();
engine.register_lexicon(Box::new(TestLexiconProvider { domain: "store", unit: UnitId::OU })).unwrap();
let reg = engine.world_registry();
assert_eq!(reg.organs().len(), 2);
assert_eq!(reg.organs_by_world(World::WAY).len(), 2);
assert_eq!(reg.active_worlds().len(), 1);
}
#[test]
fn world_registry_is_reference_not_allocation() {
let mut engine = make_engine();
engine.register_lexicon(Box::new(TestLexiconProvider { domain: "alpha", unit: UnitId::FU })).unwrap();
let reg1 = engine.world_registry();
let reg2 = engine.world_registry();
assert_eq!(reg1.organs().len(), reg2.organs().len());
assert!(std::ptr::eq(reg1.organs(), reg2.organs()));
}
struct TestLexiconWithRenderings;
impl LexiconProvider for TestLexiconWithRenderings {
fn domain(&self) -> &str {
"guard"
}
fn declared_unit(&self) -> UnitId {
UnitId::OU
}
fn primary_world(&self) -> World {
World::WAY
}
fn vocabulary(&self) -> Vec<LexiconEntry> {
vec![LexiconEntry {
key: "guard.severity.critical".into(),
default_en: "Critical".into(),
coordinate: VocabCoord { unit: UnitId::OU, layer: Layer::Data, element: Element::Root },
related: vec![],
ordinal: Some(3),
invariant: false,
}]
}
fn describe(&self, _key: &str) -> Option<TermDescription> {
None
}
fn renderings(&self) -> Vec<RenderingPack> {
let mut text_de = RenderingPack::new("organ:guard", Medium::Text, "de");
text_de.insert("guard.severity.critical", "Kritisch");
let mut voice_de = RenderingPack::new("organ:guard", Medium::Voice, "de");
voice_de.insert(
"guard.severity.critical",
"<emphasis>Kritisch</emphasis>",
);
vec![text_de, voice_de]
}
}
#[test]
fn engine_resolve_with_rendering_pack() {
let mut engine = make_engine();
engine.register_lexicon(Box::new(TestLexiconWithRenderings)).unwrap();
let result = engine.lexicon_resolve("guard.severity.critical", Medium::Text, "de");
assert_eq!(result.content, "Kritisch");
assert!(result.pack_used);
}
#[test]
fn engine_resolve_fallback_to_default_en() {
let mut engine = make_engine();
engine.register_lexicon(Box::new(TestLexiconWithRenderings)).unwrap();
let result = engine.lexicon_resolve("guard.severity.critical", Medium::Text, "ja");
assert_eq!(result.content, "Critical");
assert!(!result.pack_used);
}
#[test]
fn engine_resolve_voice_medium() {
let mut engine = make_engine();
engine.register_lexicon(Box::new(TestLexiconWithRenderings)).unwrap();
let result = engine.lexicon_resolve("guard.severity.critical", Medium::Voice, "de");
assert_eq!(result.content, "<emphasis>Kritisch</emphasis>");
assert_eq!(result.medium, Medium::Voice);
}
#[test]
fn engine_rendering_packs_collected() {
let mut engine = make_engine();
engine.register_lexicon(Box::new(TestLexiconWithRenderings)).unwrap();
assert_eq!(engine.lexicon_rendering_packs().len(), 2);
}
#[test]
fn engine_rendering_packs_replaced_on_re_registration() {
let mut engine = make_engine();
engine.register_lexicon(Box::new(TestLexiconWithRenderings)).unwrap();
assert_eq!(engine.lexicon_rendering_packs().len(), 2);
engine.register_lexicon(Box::new(TestLexiconWithRenderings)).unwrap();
assert_eq!(engine.lexicon_rendering_packs().len(), 2);
}
#[test]
fn engine_rendering_packs_empty_by_default() {
let engine = make_engine();
assert!(engine.lexicon_rendering_packs().is_empty());
}
struct DeclaredThrough {
name: String,
prefix: String,
perms: Vec<PermissionRequirement>,
}
impl Extension for DeclaredThrough {
fn name(&self) -> &str {
&self.name
}
}
impl ThroughRing for DeclaredThrough {
fn handles(&self, command_type: &str) -> bool {
command_type.starts_with(&self.prefix)
}
fn dispatch(
&self,
_input: &Tree,
) -> Result<Tree, soma_som_core::extension::DelegationError> {
Ok(Tree::new())
}
fn claimed_prefixes(&self) -> Vec<String> {
vec![self.prefix.clone()]
}
fn permission_requirements(&self) -> Vec<PermissionRequirement> {
self.perms.clone()
}
}
fn make_store_through() -> Arc<DeclaredThrough> {
Arc::new(DeclaredThrough {
name: "store".into(),
prefix: "store.".into(),
perms: vec![
PermissionRequirement::new(
"store.artifact.create",
"auth.role.operator",
"A2",
"significant",
"A1",
),
PermissionRequirement::new(
"store.artifact.read",
"auth.role.viewer",
"A1",
"routine",
"A1",
),
],
})
}
fn make_guard_through() -> Arc<DeclaredThrough> {
Arc::new(DeclaredThrough {
name: "guard".into(),
prefix: "guard.".into(),
perms: vec![PermissionRequirement::new(
"guard.policy.evaluate",
"auth.role.admin",
"A3",
"critical",
"A2",
)],
})
}
#[test]
fn command_registry_empty_by_default() {
let engine = RingEngine::new(TimingConfig::default());
assert!(engine.command_registry().is_empty());
assert_eq!(engine.command_registry().len(), 0);
assert_eq!(engine.command_registry().permission_count(), 0);
}
#[test]
fn command_registry_populated_on_register_through() {
let mut engine = RingEngine::new(TimingConfig::default());
engine.register_through(make_store_through()).unwrap();
assert_eq!(engine.command_registry().len(), 1);
assert_eq!(engine.command_registry().permission_count(), 2);
let entry = &engine.command_registry().entries()[0];
assert_eq!(entry.delegate_name, "store");
assert_eq!(entry.permissions.len(), 2);
}
#[test]
fn command_registry_multiple_delegates() {
let mut engine = RingEngine::new(TimingConfig::default());
engine.register_through(make_store_through()).unwrap();
engine.register_through(make_guard_through()).unwrap();
assert_eq!(engine.command_registry().len(), 2);
assert_eq!(engine.command_registry().permission_count(), 3);
}
#[test]
fn command_registry_lookup_exact_match() {
let mut engine = RingEngine::new(TimingConfig::default());
engine.register_through(make_store_through()).unwrap();
engine.register_through(make_guard_through()).unwrap();
let result = engine.command_registry().lookup("store.artifact.create");
assert!(result.is_some());
assert_eq!(result.unwrap().delegate_name, "store");
let result = engine.command_registry().lookup("guard.policy.evaluate");
assert!(result.is_some());
assert_eq!(result.unwrap().delegate_name, "guard");
}
#[test]
fn command_registry_lookup_no_match() {
let mut engine = RingEngine::new(TimingConfig::default());
engine.register_through(make_store_through()).unwrap();
assert!(engine.command_registry().lookup("nonexistent.command").is_none());
}
#[test]
fn command_registry_delegate_with_no_permissions() {
let mut engine = RingEngine::new(TimingConfig::default());
let bare = Arc::new(DeclaredThrough {
name: "bare".into(),
prefix: "bare.".into(),
perms: vec![],
});
engine.register_through(bare).unwrap();
assert_eq!(engine.command_registry().len(), 1);
assert_eq!(engine.command_registry().permission_count(), 0);
assert!(engine.command_registry().lookup("bare.something").is_none());
}
#[test]
fn command_registry_sibling_registration() {
use soma_som_core::extension::DomainRequest;
let mut engine = RingEngine::new(TimingConfig::default());
let manifest = TestSiblingManifest::new("qi");
engine
.register_sibling(&manifest, None::<fn(&DomainRequest) -> Result<(), String>>)
.unwrap();
assert_eq!(engine.command_registry().len(), 1);
assert_eq!(engine.command_registry().entries()[0].delegate_name, "qi");
assert_eq!(engine.command_registry().permission_count(), 0);
}
#[test]
fn command_registry_entries_order_matches_registration() {
let mut engine = RingEngine::new(TimingConfig::default());
engine.register_through(make_guard_through()).unwrap();
engine.register_through(make_store_through()).unwrap();
let names: Vec<&str> = engine
.command_registry()
.entries()
.iter()
.map(|e| e.delegate_name.as_str())
.collect();
assert_eq!(names, vec!["guard", "store"]);
}
#[test]
fn command_registry_lookup_returns_correct_permissions() {
let mut engine = RingEngine::new(TimingConfig::default());
engine.register_through(make_store_through()).unwrap();
let entry = engine.command_registry().lookup("store.artifact.read").unwrap();
let perm = entry.permissions.iter().find(|p| p.command == "store.artifact.read").unwrap();
assert_eq!(perm.min_role_key, "auth.role.viewer");
}
#[test]
fn register_through_identical_prefix_collision() {
let mut engine = RingEngine::new(TimingConfig::default());
engine.register_through(make_store_through()).unwrap();
let dup = Arc::new(DeclaredThrough {
name: "store-dup".into(),
prefix: "store.".into(),
perms: vec![],
});
let result = engine.register_through(dup);
assert!(matches!(
result,
Err(RegistrationError::NamespaceConflict { .. })
));
}
#[test]
fn register_through_subset_prefix_collision() {
let mut engine = RingEngine::new(TimingConfig::default());
engine.register_through(make_store_through()).unwrap();
let subset = Arc::new(DeclaredThrough {
name: "store-artifact-only".into(),
prefix: "store.artifact.".into(),
perms: vec![],
});
let result = engine.register_through(subset);
assert!(matches!(
result,
Err(RegistrationError::NamespaceConflict { .. })
));
}
#[test]
fn register_through_superset_prefix_collision() {
let mut engine = RingEngine::new(TimingConfig::default());
let narrow = Arc::new(DeclaredThrough {
name: "store-narrow".into(),
prefix: "store.artifact.".into(),
perms: vec![],
});
engine.register_through(narrow).unwrap();
let broad = Arc::new(DeclaredThrough {
name: "store-broad".into(),
prefix: "store.".into(),
perms: vec![],
});
let result = engine.register_through(broad);
assert!(matches!(
result,
Err(RegistrationError::NamespaceConflict { .. })
));
}
#[test]
fn register_through_disjoint_prefixes_ok() {
let mut engine = RingEngine::new(TimingConfig::default());
engine.register_through(make_store_through()).unwrap();
engine.register_through(make_guard_through()).unwrap();
assert_eq!(engine.command_registry().len(), 2);
}
#[test]
fn register_through_empty_prefixes_skips_check() {
let mut engine = RingEngine::new(TimingConfig::default());
engine.register_through(make_store_through()).unwrap();
let legacy = Arc::new(TestSiblingThrough {
id: "legacy-no-prefixes".into(),
});
engine.register_through(legacy).unwrap();
assert_eq!(engine.command_registry().len(), 2);
}
#[test]
fn register_through_prefixes_stored_in_registry() {
let mut engine = RingEngine::new(TimingConfig::default());
engine.register_through(make_store_through()).unwrap();
let entry = &engine.command_registry().entries()[0];
assert_eq!(entry.prefixes, vec!["store.".to_string()]);
}
#[test]
fn command_registry_find_prefix_collision_direct() {
let mut registry = CommandRegistry::new();
registry.register_entry(
"store",
vec![],
vec!["store.".to_string()],
vec![],
);
assert!(registry
.find_prefix_collision(&["store.".to_string()])
.is_some());
assert!(registry
.find_prefix_collision(&["store.artifact.".to_string()])
.is_some());
assert!(registry
.find_prefix_collision(&["guard.".to_string()])
.is_none());
}
#[test]
fn schema_captured_on_register_through() {
use soma_som_core::extension::{CommandSchema, SchemaField, SchemaFieldType};
struct SchemaThrough;
impl Extension for SchemaThrough {
fn name(&self) -> &str { "schema-test" }
}
impl ThroughRing for SchemaThrough {
fn handles(&self, cmd: &str) -> bool { cmd.starts_with("test.") }
fn dispatch(&self, _: &Tree) -> Result<Tree, DelegationError> { Ok(Tree::new()) }
fn claimed_prefixes(&self) -> Vec<String> { vec!["test.".into()] }
fn command_schemas(&self) -> Vec<CommandSchema> {
vec![
CommandSchema::new("test.create")
.field(SchemaField::required("name", SchemaFieldType::String)),
]
}
}
let mut engine = RingEngine::new(TimingConfig::default());
engine.register_through(Arc::new(SchemaThrough)).unwrap();
let entry = &engine.command_registry().entries()[0];
assert_eq!(entry.schemas.len(), 1);
assert_eq!(entry.schemas[0].command_type, "test.create");
assert_eq!(entry.schemas[0].fields.len(), 1);
}
#[test]
fn schema_lookup_by_command_type() {
use soma_som_core::extension::{CommandSchema, SchemaField, SchemaFieldType};
let mut registry = CommandRegistry::new();
registry.register_entry(
"store",
vec![],
vec!["store.".into()],
vec![
CommandSchema::new("store.artifact.create")
.field(SchemaField::required("name", SchemaFieldType::String)),
CommandSchema::new("store.artifact.delete")
.field(SchemaField::required("artifact_id", SchemaFieldType::String)),
],
);
let schema = registry.lookup_schema("store.artifact.create");
assert!(schema.is_some());
assert_eq!(schema.unwrap().fields.len(), 1);
let schema = registry.lookup_schema("store.artifact.delete");
assert!(schema.is_some());
assert!(registry.lookup_schema("store.artifact.list").is_none());
}
#[test]
fn schema_count() {
use soma_som_core::extension::{CommandSchema, SchemaField, SchemaFieldType};
let mut registry = CommandRegistry::new();
registry.register_entry(
"store",
vec![],
vec![],
vec![
CommandSchema::new("store.create")
.field(SchemaField::required("name", SchemaFieldType::String)),
CommandSchema::new("store.delete"),
],
);
registry.register_entry("guard", vec![], vec![], vec![CommandSchema::new("guard.eval")]);
assert_eq!(registry.schema_count(), 3);
}
#[test]
fn empty_schemas_backward_compatible() {
let mut engine = RingEngine::new(TimingConfig::default());
engine.register_through(make_store_through()).unwrap();
let entry = &engine.command_registry().entries()[0];
assert!(entry.schemas.is_empty());
}
#[test]
fn cycle_report_includes_ring_fingerprint() {
let mut engine = make_engine();
engine.genesis(TEST_SEED).unwrap();
let report = engine.cycle().unwrap();
assert_ne!(report.ring_fingerprint, [0u8; 32]);
}
#[test]
fn genesis_report_includes_ring_fingerprint() {
let mut engine = make_engine();
let report = engine.genesis(TEST_SEED).unwrap();
assert_ne!(report.ring_fingerprint, [0u8; 32]);
}
#[test]
fn cycle_report_ring_fingerprint_matches_genesis_derivation() {
use soma_som_core::fingerprint::ring_fingerprint;
let mut engine = make_engine();
let genesis = engine.genesis(TEST_SEED).unwrap();
let report = engine.cycle().unwrap();
let expected = ring_fingerprint(
&genesis.genesis.system_fingerprint,
&engine.boundary_verifying_key(),
);
assert_eq!(report.ring_fingerprint, expected);
assert_eq!(report.ring_fingerprint, genesis.ring_fingerprint);
}
#[test]
fn ring_fingerprint_stable_across_cycles() {
let mut engine = make_engine();
engine.genesis(TEST_SEED).unwrap();
let r1 = engine.cycle().unwrap();
let r2 = engine.cycle().unwrap();
let r3 = engine.cycle().unwrap();
assert_eq!(r1.ring_fingerprint, r2.ring_fingerprint);
assert_eq!(r2.ring_fingerprint, r3.ring_fingerprint);
}
}