aranya-runtime 0.24.0

The Aranya core runtime
Documentation
//! Interfaces for an application to begin a runtime.
//!
//! A [`PolicyStore`] stores policies for an application. A [`Policy`] is required
//! to process [`Command`]s and defines how the runtime's graph is constructed.

use buggy::Bug;
use rend::u64_le;

use crate::{
    Address,
    command::{CmdId, Command},
    storage::{FactPerspective, Perspective},
};

/// An error returned by a runtime policy store or policy.
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum PolicyError {
    #[error("read error")]
    Read,
    #[error("write error")]
    Write,
    #[error("check error")]
    Check,
    #[error("panic")]
    Panic,
    #[error("internal error")]
    InternalError,
    #[error(transparent)]
    Bug(#[from] Bug),
}

impl From<core::convert::Infallible> for PolicyError {
    fn from(error: core::convert::Infallible) -> Self {
        match error {}
    }
}

#[derive(
    Copy,
    Clone,
    Debug,
    Ord,
    PartialOrd,
    Eq,
    PartialEq,
    serde::Serialize,
    serde::Deserialize,
    rkyv::Archive,
    rkyv::Serialize,
    rkyv::Deserialize,
    rkyv::Portable,
    rkyv::bytecheck::CheckBytes,
)]
#[rkyv(as = Self)]
#[bytecheck(crate = rkyv::bytecheck)]
#[serde(transparent)]
#[repr(transparent)]
pub struct PolicyId(#[serde(with = "crate::util::u64_le_serde")] u64_le);

impl PolicyId {
    pub fn new(id: u64) -> Self {
        Self(id.into())
    }
}

/// The [`PolicyStore`] manages storing and retrieving [`Policy`].
pub trait PolicyStore {
    type Policy: Policy<Effect = Self::Effect>;

    type Effect;

    /// Add a policy to this runtime.
    ///
    /// # Arguments
    ///
    /// * `policy` - Byte slice that holds a policy.
    fn add_policy(&mut self, policy: &[u8]) -> Result<PolicyId, PolicyError>;

    /// Get a policy from this runtime.
    ///
    /// # Arguments
    ///
    /// * `policy` - Byte slice representing a [`PolicyId`].
    fn get_policy(&self, id: PolicyId) -> Result<&Self::Policy, PolicyError>;
}

/// The [`Sink`] transactionally consumes effects from evaluating [`Policy`].
pub trait Sink<Eff> {
    fn begin(&mut self);
    fn consume(&mut self, effect: Eff);
    fn rollback(&mut self);
    fn commit(&mut self);
}

pub struct NullSink;

impl<Eff> Sink<Eff> for NullSink {
    fn begin(&mut self) {}

    fn consume(&mut self, _effect: Eff) {}

    fn rollback(&mut self) {}

    fn commit(&mut self) {}
}

/// The IDs to a merge command in sorted order.
pub struct MergeIds {
    // left < right
    left: Address,
    right: Address,
}

impl MergeIds {
    /// Create [`MergeIds`] by ordering two [`Address`]s and ensuring they are different.
    pub fn new(a: Address, b: Address) -> Option<Self> {
        use core::cmp::Ordering;
        match a.id.cmp(&b.id) {
            Ordering::Less => Some(Self { left: a, right: b }),
            Ordering::Equal => None,
            Ordering::Greater => Some(Self { left: b, right: a }),
        }
    }
}

impl From<MergeIds> for (CmdId, CmdId) {
    /// Convert [`MergeIds`] into an ordered pair of [`CmdId`]s.
    fn from(value: MergeIds) -> Self {
        (value.left.id, value.right.id)
    }
}

impl From<MergeIds> for (Address, Address) {
    /// Convert [`MergeIds`] into an ordered pair of [`Address`]s.
    fn from(value: MergeIds) -> Self {
        (value.left, value.right)
    }
}

/// [`Policy`] evaluates actions and [`Command`]s on the graph, emitting effects
/// as a result.
pub trait Policy {
    type Action<'a>;
    type Effect;
    type Command<'a>: Command;

    /// Policies have a serial number which can be used to order them.
    /// This is used to support inband policy upgrades.
    fn serial(&self) -> u32;

    /// Evaluate a command at the given perspective. If the command is accepted, effects may
    /// be emitted to the sink and facts may be written to the perspective. Returns an error
    /// for a rejected command.
    fn call_rule(
        &self,
        command: &impl Command,
        facts: &mut impl FactPerspective,
        sink: &mut impl Sink<Self::Effect>,
        placement: CommandPlacement,
    ) -> Result<(), PolicyError>;

    /// Process an action checking each published command against the policy and emitting
    /// effects to the sink. All published commands are handled transactionally where if any
    /// published command is rejected no commands are added to the storage.
    fn call_action(
        &self,
        action: Self::Action<'_>,
        facts: &mut impl Perspective,
        sink: &mut impl Sink<Self::Effect>,
        placement: ActionPlacement,
    ) -> Result<(), PolicyError>;

    /// Produces a merge message serialized to target. The `struct` representing the
    /// Command is returned.
    fn merge<'a>(
        &self,
        target: &'a mut [u8],
        ids: MergeIds,
    ) -> Result<Self::Command<'a>, PolicyError>;
}

/// Describes the placement when calling an action.
#[derive(Copy, Clone, Debug)]
pub enum ActionPlacement {
    /// The action is being called on-graph and will be persisted.
    OnGraph,
    /// The action is being called off-graph in an ephemeral session.
    OffGraph,
}

#[derive(Copy, Clone, Debug)]
/// Describes the placement when evaluating a command.
pub enum CommandPlacement {
    /// The command is being evaluated in its original location in the graph.
    OnGraphAtOrigin,
    /// The command is being evaluated during a braid of the graph.
    OnGraphInBraid,
    /// The command is being evaluated off-graph in an ephemeral session.
    OffGraph,
}

mod impls {
    use alloc::boxed::Box;

    use super::{PolicyError, PolicyId, PolicyStore, Sink};

    impl<PS: PolicyStore> PolicyStore for &mut PS {
        type Policy = PS::Policy;
        type Effect = PS::Effect;

        fn add_policy(&mut self, policy: &[u8]) -> Result<PolicyId, PolicyError> {
            PS::add_policy(self, policy)
        }

        fn get_policy(&self, id: PolicyId) -> Result<&Self::Policy, PolicyError> {
            PS::get_policy(self, id)
        }
    }

    impl<PS: PolicyStore> PolicyStore for Box<PS> {
        type Policy = PS::Policy;
        type Effect = PS::Effect;

        fn add_policy(&mut self, policy: &[u8]) -> Result<PolicyId, PolicyError> {
            PS::add_policy(self, policy)
        }

        fn get_policy(&self, id: PolicyId) -> Result<&Self::Policy, PolicyError> {
            PS::get_policy(self, id)
        }
    }

    impl<S: Sink<Eff>, Eff> Sink<Eff> for &mut S {
        fn begin(&mut self) {
            S::begin(self);
        }

        fn consume(&mut self, effect: Eff) {
            S::consume(self, effect);
        }

        fn rollback(&mut self) {
            S::rollback(self);
        }

        fn commit(&mut self) {
            S::commit(self);
        }
    }

    impl<S: Sink<Eff>, Eff> Sink<Eff> for Box<S> {
        fn begin(&mut self) {
            S::begin(self);
        }

        fn consume(&mut self, effect: Eff) {
            S::consume(self, effect);
        }

        fn rollback(&mut self) {
            S::rollback(self);
        }

        fn commit(&mut self) {
            S::commit(self);
        }
    }
}