aranya_runtime/
engine.rs

1//! Interfaces for an application to begin a runtime.
2//!
3//! An [`Engine`] stores policies for an application. A [`Policy`] is required
4//! to process [`Command`]s and defines how the runtime's graph is constructed.
5
6use buggy::Bug;
7use serde::{Deserialize, Serialize};
8
9use crate::{
10    Address,
11    command::{CmdId, Command},
12    storage::{FactPerspective, Perspective},
13};
14
15/// An error returned by the runtime engine.
16#[derive(Debug, PartialEq, Eq, thiserror::Error)]
17pub enum EngineError {
18    #[error("read error")]
19    Read,
20    #[error("write error")]
21    Write,
22    #[error("check error")]
23    Check,
24    #[error("panic")]
25    Panic,
26    #[error("internal error")]
27    InternalError,
28    #[error(transparent)]
29    Bug(#[from] Bug),
30}
31
32impl From<core::convert::Infallible> for EngineError {
33    fn from(error: core::convert::Infallible) -> Self {
34        match error {}
35    }
36}
37
38#[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Serialize, Deserialize)]
39pub struct PolicyId(usize);
40
41impl PolicyId {
42    pub fn new(id: usize) -> Self {
43        Self(id)
44    }
45}
46
47/// The [`Engine`] manages storing and retrieving [`Policy`].
48pub trait Engine {
49    type Policy: Policy<Effect = Self::Effect>;
50
51    type Effect;
52
53    /// Add a policy to this runtime.
54    ///
55    /// # Arguments
56    ///
57    /// * `policy` - Byte slice that holds a policy.
58    fn add_policy(&mut self, policy: &[u8]) -> Result<PolicyId, EngineError>;
59
60    /// Get a policy from this runtime.
61    ///
62    /// # Arguments
63    ///
64    /// * `policy` - Byte slice representing a [`PolicyId`].
65    fn get_policy(&self, id: PolicyId) -> Result<&Self::Policy, EngineError>;
66}
67
68/// The [`Sink`] transactionally consumes effects from evaluating [`Policy`].
69pub trait Sink<E> {
70    fn begin(&mut self);
71    fn consume(&mut self, effect: E);
72    fn rollback(&mut self);
73    fn commit(&mut self);
74}
75
76pub struct NullSink;
77
78impl<E> Sink<E> for NullSink {
79    fn begin(&mut self) {}
80
81    fn consume(&mut self, _effect: E) {}
82
83    fn rollback(&mut self) {}
84
85    fn commit(&mut self) {}
86}
87
88/// The IDs to a merge command in sorted order.
89pub struct MergeIds {
90    // left < right
91    left: Address,
92    right: Address,
93}
94
95impl MergeIds {
96    /// Create [`MergeIds`] by ordering two [`Address`]s and ensuring they are different.
97    pub fn new(a: Address, b: Address) -> Option<Self> {
98        use core::cmp::Ordering;
99        match a.id.cmp(&b.id) {
100            Ordering::Less => Some(Self { left: a, right: b }),
101            Ordering::Equal => None,
102            Ordering::Greater => Some(Self { left: b, right: a }),
103        }
104    }
105}
106
107impl From<MergeIds> for (CmdId, CmdId) {
108    /// Convert [`MergeIds`] into an ordered pair of [`CmdId`]s.
109    fn from(value: MergeIds) -> Self {
110        (value.left.id, value.right.id)
111    }
112}
113
114impl From<MergeIds> for (Address, Address) {
115    /// Convert [`MergeIds`] into an ordered pair of [`Address`]s.
116    fn from(value: MergeIds) -> Self {
117        (value.left, value.right)
118    }
119}
120
121/// [`Policy`] evaluates actions and [`Command`]s on the graph, emitting effects
122/// as a result.
123pub trait Policy {
124    type Action<'a>;
125    type Effect;
126    type Command<'a>: Command;
127
128    /// Policies have a serial number which can be used to order them.
129    /// This is used to support inband policy upgrades.
130    fn serial(&self) -> u32;
131
132    /// Evaluate a command at the given perspective. If the command is accepted, effects may
133    /// be emitted to the sink and facts may be written to the perspective. Returns an error
134    /// for a rejected command.
135    fn call_rule(
136        &self,
137        command: &impl Command,
138        facts: &mut impl FactPerspective,
139        sink: &mut impl Sink<Self::Effect>,
140        placement: CommandPlacement,
141    ) -> Result<(), EngineError>;
142
143    /// Process an action checking each published command against the policy and emitting
144    /// effects to the sink. All published commands are handled transactionally where if any
145    /// published command is rejected no commands are added to the storage.
146    fn call_action(
147        &self,
148        action: Self::Action<'_>,
149        facts: &mut impl Perspective,
150        sink: &mut impl Sink<Self::Effect>,
151        placement: ActionPlacement,
152    ) -> Result<(), EngineError>;
153
154    /// Produces a merge message serialized to target. The `struct` representing the
155    /// Command is returned.
156    fn merge<'a>(
157        &self,
158        target: &'a mut [u8],
159        ids: MergeIds,
160    ) -> Result<Self::Command<'a>, EngineError>;
161}
162
163/// Describes the placement when calling an action.
164#[derive(Copy, Clone, Debug)]
165pub enum ActionPlacement {
166    /// The action is being called on-graph and will be persisted.
167    OnGraph,
168    /// The action is being called off-graph in an ephemeral session.
169    OffGraph,
170}
171
172#[derive(Copy, Clone, Debug)]
173/// Describes the placement when evaluating a command.
174pub enum CommandPlacement {
175    /// The command is being evaluated in its original location in the graph.
176    OnGraphAtOrigin,
177    /// The command is being evaluated during a braid of the graph.
178    OnGraphInBraid,
179    /// The command is being evaluated off-graph in an ephemeral session.
180    OffGraph,
181}