soma-som-ring 0.1.0

Standalone ring execution engine for soma(som): cycle lifecycle, extension registration, boundary mediation
Documentation
// SPDX-License-Identifier: LGPL-3.0-only
#![allow(missing_docs)]

//! Abstract command dispatch and policy provider traits.
//!
//! These traits decouple the ring engine from application-layer organ
//! implementations. The application provides concrete implementations
//! that wire its organs to these abstractions.
//!
//! ## Design
//!
//! The ring engine defines abstractions; applications provide implementations.
//! This preserves Dependency Inversion: the foundation layer does not
//! import application-layer types.

use std::fmt;

// ── CommandDispatcher ──────────────────────────────────────────────

/// Error type for command dispatch failures.
#[derive(Debug)]
#[non_exhaustive]
pub enum CommandDispatchError {
    /// Command type not recognized by any organ.
    UnknownCommand(String),
    /// Organ returned an error during execution.
    ExecutionFailed(String),
    /// Serialization/deserialization failure.
    SerializationError(String),
}

impl fmt::Display for CommandDispatchError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::UnknownCommand(cmd) => write!(f, "unknown command: {cmd}"),
            Self::ExecutionFailed(msg) => write!(f, "dispatch failed: {msg}"),
            Self::SerializationError(msg) => write!(f, "serde error: {msg}"),
        }
    }
}

impl std::error::Error for CommandDispatchError {}

/// Abstract command dispatch — OU delegates here instead of
/// instantiating organ managers directly.
///
/// The ring engine calls `dispatch()` with a command type string
/// and serialized payload. The application layer routes to the
/// correct organ and returns the serialized result.
///
/// # Example
///
/// ```ignore
/// struct AppDispatcher { /* application-specific state */ }
///
/// impl CommandDispatcher for AppDispatcher {
///     fn dispatch(&self, cmd: &str, payload: &[u8]) -> Result<Vec<u8>, CommandDispatchError> {
///         match cmd {
///             "resource.create" => { /* deserialize, call organ handler */ }
///             _ => Err(CommandDispatchError::UnknownCommand(cmd.into())),
///         }
///     }
/// }
/// ```
pub trait CommandDispatcher: Send + Sync {
    /// Dispatch a command identified by type string and JSON payload.
    /// Returns a serialized JSON result or error.
    fn dispatch(&self, command_type: &str, payload: &[u8])
    -> Result<Vec<u8>, CommandDispatchError>;
}

// ── PolicyProvider ─────────────────────────────────────────────────

/// Abstract RBAC policy lookup — CU evaluates authorization through
/// this trait instead of calling organ-specific functions.
///
/// Each method returns `None` if the command type has no policy
/// constraint (i.e., the command is unrestricted for that dimension).
///
/// # AEQ dimensions
///
/// - **Activity Maximum**: highest activity level this command allows
/// - **Security Floor**: minimum security level required
/// - **Decision Tier**: how significant the action is (Routine/Significant/Critical)
///
/// # Example
///
/// ```ignore
/// struct AppPolicyProvider { /* application-specific policy table */ }
///
/// impl PolicyProvider for AppPolicyProvider {
///     fn required_permission(&self, cmd: &str) -> Option<String> {
///         match cmd {
///             "resource.delete" => Some("role.admin".into()),
///             _ => None,
///         }
///     }
///     // ... implement remaining methods per application policy
/// }
/// ```
pub trait PolicyProvider: Send + Sync {
    /// Required permission string for this command type, if any.
    fn required_permission(&self, command_type: &str) -> Option<String>;

    /// AEQ: maximum activity level for this command (0-4).
    fn activity_maximum(&self, command_type: &str) -> Option<u8>;

    /// AEQ: minimum security floor for this command (0-4).
    fn security_floor(&self, command_type: &str) -> Option<u8>;

    /// AEQ: decision tier for this command ("Routine", "Significant", "Critical").
    fn decision_tier(&self, command_type: &str) -> Option<String>;

    /// Map a role string to its authorization level (0-4).
    /// Returns `None` if the role is not recognized.
    fn role_authorization_level(&self, role: &str) -> Option<u8>;
}

// ── Tests ──────────────────────────────────────────────────────────

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

    struct EchoDispatcher;

    impl CommandDispatcher for EchoDispatcher {
        fn dispatch(
            &self,
            command_type: &str,
            payload: &[u8],
        ) -> Result<Vec<u8>, CommandDispatchError> {
            if command_type == "unknown" {
                return Err(CommandDispatchError::UnknownCommand(command_type.into()));
            }
            Ok(payload.to_vec())
        }
    }

    struct TestPolicyProvider;

    impl PolicyProvider for TestPolicyProvider {
        fn required_permission(&self, command_type: &str) -> Option<String> {
            match command_type {
                "user.create" => Some("users:manage".into()),
                _ => None,
            }
        }

        fn activity_maximum(&self, command_type: &str) -> Option<u8> {
            match command_type {
                "user.create" => Some(3),
                _ => None,
            }
        }

        fn security_floor(&self, command_type: &str) -> Option<u8> {
            match command_type {
                "user.create" => Some(2),
                _ => None,
            }
        }

        fn decision_tier(&self, command_type: &str) -> Option<String> {
            match command_type {
                "user.create" => Some("Significant".into()),
                _ => None,
            }
        }

        fn role_authorization_level(&self, role: &str) -> Option<u8> {
            match role {
                "Admin" => Some(4),
                "Operator" => Some(3),
                "Viewer" => Some(1),
                _ => None,
            }
        }
    }

    #[test]
    fn dispatcher_echo_returns_payload() {
        let d = EchoDispatcher;
        let result = d.dispatch("user.create", b"test-payload").unwrap();
        assert_eq!(result, b"test-payload");
    }

    #[test]
    fn dispatcher_unknown_command_errors() {
        let d = EchoDispatcher;
        let err = d.dispatch("unknown", b"").unwrap_err();
        assert!(matches!(err, CommandDispatchError::UnknownCommand(_)));
        assert!(err.to_string().contains("unknown"));
    }

    #[test]
    fn policy_required_permission() {
        let p = TestPolicyProvider;
        assert_eq!(
            p.required_permission("user.create"),
            Some("users:manage".into())
        );
        assert_eq!(p.required_permission("noop"), None);
    }

    #[test]
    fn policy_aeq_dimensions() {
        let p = TestPolicyProvider;
        assert_eq!(p.activity_maximum("user.create"), Some(3));
        assert_eq!(p.security_floor("user.create"), Some(2));
        assert_eq!(p.decision_tier("user.create"), Some("Significant".into()));
    }

    #[test]
    fn policy_role_authorization_level() {
        let p = TestPolicyProvider;
        assert_eq!(p.role_authorization_level("Admin"), Some(4));
        assert_eq!(p.role_authorization_level("Viewer"), Some(1));
        assert_eq!(p.role_authorization_level("Ghost"), None);
    }

    #[test]
    fn dispatch_error_display() {
        let e1 = CommandDispatchError::UnknownCommand("foo".into());
        let e2 = CommandDispatchError::ExecutionFailed("bar".into());
        let e3 = CommandDispatchError::SerializationError("baz".into());
        assert_eq!(e1.to_string(), "unknown command: foo");
        assert_eq!(e2.to_string(), "dispatch failed: bar");
        assert_eq!(e3.to_string(), "serde error: baz");
    }
}