Skip to main content

aranya_runtime/
policy.rs

1//! Interfaces for an application to begin a runtime.
2//!
3//! A [`PolicyStore`] 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 rend::u64_le;
8
9use crate::{
10    Address,
11    command::{CmdId, Command},
12    storage::{FactPerspective, Perspective},
13};
14
15/// An error returned by a runtime policy store or policy.
16#[derive(Debug, thiserror::Error)]
17#[non_exhaustive]
18pub enum PolicyError {
19    #[error("read error")]
20    Read,
21    #[error("write error")]
22    Write,
23    #[error("check error")]
24    Check,
25    #[error("panic")]
26    Panic,
27    #[error("internal error")]
28    InternalError,
29    #[error(transparent)]
30    Bug(#[from] Bug),
31}
32
33impl From<core::convert::Infallible> for PolicyError {
34    fn from(error: core::convert::Infallible) -> Self {
35        match error {}
36    }
37}
38
39#[derive(
40    Copy,
41    Clone,
42    Debug,
43    Ord,
44    PartialOrd,
45    Eq,
46    PartialEq,
47    serde::Serialize,
48    serde::Deserialize,
49    rkyv::Archive,
50    rkyv::Serialize,
51    rkyv::Deserialize,
52    rkyv::Portable,
53    rkyv::bytecheck::CheckBytes,
54)]
55#[rkyv(as = Self)]
56#[bytecheck(crate = rkyv::bytecheck)]
57#[serde(transparent)]
58#[repr(transparent)]
59pub struct PolicyId(#[serde(with = "crate::util::u64_le_serde")] u64_le);
60
61impl PolicyId {
62    pub fn new(id: u64) -> Self {
63        Self(id.into())
64    }
65}
66
67/// The [`PolicyStore`] manages storing and retrieving [`Policy`].
68pub trait PolicyStore {
69    type Policy: Policy<Effect = Self::Effect>;
70
71    type Effect;
72
73    /// Add a policy to this runtime.
74    ///
75    /// # Arguments
76    ///
77    /// * `policy` - Byte slice that holds a policy.
78    fn add_policy(&mut self, policy: &[u8]) -> Result<PolicyId, PolicyError>;
79
80    /// Get a policy from this runtime.
81    ///
82    /// # Arguments
83    ///
84    /// * `policy` - Byte slice representing a [`PolicyId`].
85    fn get_policy(&self, id: PolicyId) -> Result<&Self::Policy, PolicyError>;
86}
87
88/// The [`Sink`] transactionally consumes effects from evaluating [`Policy`].
89pub trait Sink<Eff> {
90    fn begin(&mut self);
91    fn consume(&mut self, effect: Eff);
92    fn rollback(&mut self);
93    fn commit(&mut self);
94}
95
96pub struct NullSink;
97
98impl<Eff> Sink<Eff> for NullSink {
99    fn begin(&mut self) {}
100
101    fn consume(&mut self, _effect: Eff) {}
102
103    fn rollback(&mut self) {}
104
105    fn commit(&mut self) {}
106}
107
108/// The IDs to a merge command in sorted order.
109pub struct MergeIds {
110    // left < right
111    left: Address,
112    right: Address,
113}
114
115impl MergeIds {
116    /// Create [`MergeIds`] by ordering two [`Address`]s and ensuring they are different.
117    pub fn new(a: Address, b: Address) -> Option<Self> {
118        use core::cmp::Ordering;
119        match a.id.cmp(&b.id) {
120            Ordering::Less => Some(Self { left: a, right: b }),
121            Ordering::Equal => None,
122            Ordering::Greater => Some(Self { left: b, right: a }),
123        }
124    }
125}
126
127impl From<MergeIds> for (CmdId, CmdId) {
128    /// Convert [`MergeIds`] into an ordered pair of [`CmdId`]s.
129    fn from(value: MergeIds) -> Self {
130        (value.left.id, value.right.id)
131    }
132}
133
134impl From<MergeIds> for (Address, Address) {
135    /// Convert [`MergeIds`] into an ordered pair of [`Address`]s.
136    fn from(value: MergeIds) -> Self {
137        (value.left, value.right)
138    }
139}
140
141/// [`Policy`] evaluates actions and [`Command`]s on the graph, emitting effects
142/// as a result.
143pub trait Policy {
144    type Action<'a>;
145    type Effect;
146    type Command<'a>: Command;
147
148    /// Policies have a serial number which can be used to order them.
149    /// This is used to support inband policy upgrades.
150    fn serial(&self) -> u32;
151
152    /// Evaluate a command at the given perspective. If the command is accepted, effects may
153    /// be emitted to the sink and facts may be written to the perspective. Returns an error
154    /// for a rejected command.
155    fn call_rule(
156        &self,
157        command: &impl Command,
158        facts: &mut impl FactPerspective,
159        sink: &mut impl Sink<Self::Effect>,
160        placement: CommandPlacement,
161    ) -> Result<(), PolicyError>;
162
163    /// Process an action checking each published command against the policy and emitting
164    /// effects to the sink. All published commands are handled transactionally where if any
165    /// published command is rejected no commands are added to the storage.
166    fn call_action(
167        &self,
168        action: Self::Action<'_>,
169        facts: &mut impl Perspective,
170        sink: &mut impl Sink<Self::Effect>,
171        placement: ActionPlacement,
172    ) -> Result<(), PolicyError>;
173
174    /// Produces a merge message serialized to target. The `struct` representing the
175    /// Command is returned.
176    fn merge<'a>(
177        &self,
178        target: &'a mut [u8],
179        ids: MergeIds,
180    ) -> Result<Self::Command<'a>, PolicyError>;
181}
182
183/// Describes the placement when calling an action.
184#[derive(Copy, Clone, Debug)]
185pub enum ActionPlacement {
186    /// The action is being called on-graph and will be persisted.
187    OnGraph,
188    /// The action is being called off-graph in an ephemeral session.
189    OffGraph,
190}
191
192#[derive(Copy, Clone, Debug)]
193/// Describes the placement when evaluating a command.
194pub enum CommandPlacement {
195    /// The command is being evaluated in its original location in the graph.
196    OnGraphAtOrigin,
197    /// The command is being evaluated during a braid of the graph.
198    OnGraphInBraid,
199    /// The command is being evaluated off-graph in an ephemeral session.
200    OffGraph,
201}
202
203mod impls {
204    use alloc::boxed::Box;
205
206    use super::{PolicyError, PolicyId, PolicyStore, Sink};
207
208    impl<PS: PolicyStore> PolicyStore for &mut PS {
209        type Policy = PS::Policy;
210        type Effect = PS::Effect;
211
212        fn add_policy(&mut self, policy: &[u8]) -> Result<PolicyId, PolicyError> {
213            PS::add_policy(self, policy)
214        }
215
216        fn get_policy(&self, id: PolicyId) -> Result<&Self::Policy, PolicyError> {
217            PS::get_policy(self, id)
218        }
219    }
220
221    impl<PS: PolicyStore> PolicyStore for Box<PS> {
222        type Policy = PS::Policy;
223        type Effect = PS::Effect;
224
225        fn add_policy(&mut self, policy: &[u8]) -> Result<PolicyId, PolicyError> {
226            PS::add_policy(self, policy)
227        }
228
229        fn get_policy(&self, id: PolicyId) -> Result<&Self::Policy, PolicyError> {
230            PS::get_policy(self, id)
231        }
232    }
233
234    impl<S: Sink<Eff>, Eff> Sink<Eff> for &mut S {
235        fn begin(&mut self) {
236            S::begin(self);
237        }
238
239        fn consume(&mut self, effect: Eff) {
240            S::consume(self, effect);
241        }
242
243        fn rollback(&mut self) {
244            S::rollback(self);
245        }
246
247        fn commit(&mut self) {
248            S::commit(self);
249        }
250    }
251
252    impl<S: Sink<Eff>, Eff> Sink<Eff> for Box<S> {
253        fn begin(&mut self) {
254            S::begin(self);
255        }
256
257        fn consume(&mut self, effect: Eff) {
258            S::consume(self, effect);
259        }
260
261        fn rollback(&mut self) {
262            S::rollback(self);
263        }
264
265        fn commit(&mut self) {
266            S::commit(self);
267        }
268    }
269}