didbot_policy_records/engine.rs
1//! The engines this build compiles a policy document with, and the tree
2//! built from them.
3
4use std::collections::HashMap;
5use std::sync::Arc;
6
7use crate::compile::Language;
8use crate::{BuiltinPolicy, LastGoodPolicies, LoadReport, ParsedPolicy};
9use didbot_policy::{Evaluator, PolicyTree};
10use didbot_policy_cedar::{CedarEvaluator, Shapes};
11use didbot_policy_count::CountEvaluator;
12use didbot_policy_regex::RegexEvaluator;
13
14use crate::records::{CEDAR_DOCUMENT, COUNT_DOCUMENT, REGEX_DOCUMENT};
15
16/// The `didbot-policy-records::compile::Language` bridge for `"regex"`:
17/// hands back a fresh, not-yet-compiled `RegexEvaluator` per predicate
18/// document. The actual compiling — parsing `didbot-policy-regex`'s own
19/// JSON predicate schema, documented on `RegexEvaluator::compile_json` —
20/// happens inside `didbot_policy::Evaluator::compile` itself: this bridge's
21/// only job is naming which evaluator crate answers to `"regex"` for an
22/// operator's record. The built-ins in
23/// `crate::builtin` compile their own statements with the
24/// same crate and never pass through here.
25pub struct RegexLanguage;
26
27impl Language for RegexLanguage {
28 fn name(&self) -> &str {
29 "regex"
30 }
31
32 fn new_evaluator(&self) -> Arc<dyn Evaluator> {
33 Arc::new(RegexEvaluator::empty())
34 }
35}
36
37/// The `didbot-policy-records::compile::Language` bridge for `"cedar"`:
38/// hands back a fresh `CedarEvaluator` over this server's [`shapes`] per
39/// predicate document.
40pub struct CedarLanguage;
41
42impl Language for CedarLanguage {
43 fn name(&self) -> &str {
44 "cedar"
45 }
46
47 fn new_evaluator(&self) -> Arc<dyn Evaluator> {
48 Arc::new(CedarEvaluator::new(shapes()))
49 }
50}
51
52/// The `didbot-policy-records::compile::Language` bridge for `"count"`:
53/// hands back a fresh [`CountEvaluator`] per predicate document. The
54/// counts themselves are the process's, not the evaluator's, so a tree
55/// rebuilt on a poll tick keeps counting where the last one left off --
56/// see `didbot-policy-count`'s own doc comment.
57pub struct CountLanguage;
58
59impl Language for CountLanguage {
60 fn name(&self) -> &str {
61 "count"
62 }
63
64 fn new_evaluator(&self) -> Arc<dyn Evaluator> {
65 Arc::new(CountEvaluator::new())
66 }
67}
68
69/// The record shapes this server validates Cedar documents against and
70/// types `context.value` with. One set, shared by every evaluator and by
71/// the loader that splits a document into statements, so both see the
72/// same schema.
73#[must_use]
74pub fn shapes() -> Arc<Shapes> {
75 static SHAPES: std::sync::OnceLock<Arc<Shapes>> = std::sync::OnceLock::new();
76 SHAPES.get_or_init(|| Arc::new(Shapes::new())).clone()
77}
78
79/// One engine this build carries.
80pub struct Engine {
81 /// The `$type` of the `bot.did.policy` document it reads.
82 pub document: &'static str,
83 /// The bridge that compiles each of that document's predicates.
84 pub language: &'static dyn Language,
85}
86
87/// Every engine this build carries. A language no entry names is one
88/// `crate::compile` treats as
89/// [`didbot_policy::Predicate::Unusable`], the fail-closed, scoped treatment
90/// an unrecognised language gets.
91pub const ENGINES: &[Engine] = &[
92 Engine {
93 document: REGEX_DOCUMENT,
94 language: &RegexLanguage,
95 },
96 Engine {
97 document: CEDAR_DOCUMENT,
98 language: &CedarLanguage,
99 },
100 Engine {
101 document: COUNT_DOCUMENT,
102 language: &CountLanguage,
103 },
104];
105
106/// The engine that reads a document of this `$type`, if this build carries
107/// one.
108#[must_use]
109pub fn engine_for(document: &str) -> Option<&'static Engine> {
110 ENGINES.iter().find(|engine| engine.document == document)
111}
112
113/// Assembles one [`PolicyTree`] from every source a deployment carries.
114///
115/// `builtins` are the policies compiled into the binary, `startup` the
116/// declarations supplied for the length of the run, `operator` the
117/// declarations read from the operator's records — see
118/// `crate::merge::build`'s own doc comment for what each
119/// argument is. Registers the language of every [`ENGINES`] entry.
120///
121/// `last_good` must be the same [`LastGoodPolicies`] passed on every call
122/// across this deployment's lifetime — it is what lets an operator-sourced
123/// policy that compiled at an earlier revision keep being enforced when a
124/// later revision fails to compile, per `plan/policy.md`'s
125/// last-good-revision behaviour. A caller holding one per `build_tree` call
126/// defeats it entirely: there would never be a prior revision to fall back
127/// to.
128#[must_use]
129pub fn build_tree(
130 builtins: Vec<BuiltinPolicy>,
131 startup: &HashMap<String, ParsedPolicy>,
132 operator: &HashMap<String, ParsedPolicy>,
133 last_good: &LastGoodPolicies,
134) -> (PolicyTree, LoadReport) {
135 let languages: Vec<&dyn Language> = ENGINES.iter().map(|engine| engine.language).collect();
136 crate::build(builtins, startup, operator, &languages, last_good)
137}