1use std::sync::{Arc, Mutex, MutexGuard};
4
5use sim_kernel::{
6 CapabilityName, CapabilitySet, Cx, Datum, DatumStore, Error, Event, EventKind, EventLedger,
7 Expr, Ref, Result, Symbol,
8};
9
10use super::RequestOrigin;
11
12#[derive(Clone, Debug, PartialEq, Eq)]
14pub enum ReadEvalOutcome {
15 Admitted,
17 CapDenied,
19 TrustDenied,
21 MissingPower,
23 DecodeFailed,
25 EvalFailed,
27 ShapeDenied,
29 ShapeError,
31}
32
33impl ReadEvalOutcome {
34 fn as_symbol(&self) -> Symbol {
35 match self {
36 Self::Admitted => Symbol::new("admitted"),
37 Self::CapDenied => Symbol::new("cap-denied"),
38 Self::TrustDenied => Symbol::new("trust-denied"),
39 Self::MissingPower => Symbol::new("missing-power"),
40 Self::DecodeFailed => Symbol::new("decode-failed"),
41 Self::EvalFailed => Symbol::new("eval-failed"),
42 Self::ShapeDenied => Symbol::new("shape-denied"),
43 Self::ShapeError => Symbol::new("shape-error"),
44 }
45 }
46
47 fn from_symbol(symbol: &Symbol) -> Result<Self> {
48 match symbol.name.as_ref() {
49 "admitted" => Ok(Self::Admitted),
50 "cap-denied" => Ok(Self::CapDenied),
51 "trust-denied" => Ok(Self::TrustDenied),
52 "missing-power" => Ok(Self::MissingPower),
53 "decode-failed" => Ok(Self::DecodeFailed),
54 "eval-failed" => Ok(Self::EvalFailed),
55 "shape-denied" => Ok(Self::ShapeDenied),
56 "shape-error" => Ok(Self::ShapeError),
57 other => Err(Error::Eval(format!(
58 "unknown read-eval decision outcome {other}"
59 ))),
60 }
61 }
62}
63
64#[derive(Clone, Debug, PartialEq, Eq)]
66pub struct ReadEvalDecision {
67 pub origin: RequestOrigin,
69 pub codec: Symbol,
71 pub expected_shape: Option<Symbol>,
73 pub requires: Vec<CapabilityName>,
75 pub requested: Vec<CapabilityName>,
77 pub active: Vec<CapabilityName>,
79 pub outcome: ReadEvalOutcome,
81}
82
83pub fn read_eval_decision_run() -> Ref {
85 Ref::Symbol(Symbol::qualified("read-eval", "decisions"))
86}
87
88#[derive(Clone)]
89pub(super) struct ReadEvalLedger {
90 events: Arc<Mutex<EventLedger>>,
91 run: Ref,
92}
93
94impl Default for ReadEvalLedger {
95 fn default() -> Self {
96 Self {
97 events: Arc::new(Mutex::new(EventLedger::new())),
98 run: read_eval_decision_run(),
99 }
100 }
101}
102
103impl ReadEvalLedger {
104 pub(super) fn record(&self, cx: &mut Cx, decision: &ReadEvalDecision) -> Result<Event> {
105 let reference = decision_ref(cx, decision)?;
106 self.lock()?
107 .push(self.run.clone(), EventKind::Trace(reference))
108 }
109
110 pub(super) fn events_for_run(&self, run: &Ref) -> Result<Vec<Event>> {
111 Ok(self.lock()?.events_for_run(run).to_vec())
112 }
113
114 pub(super) fn decisions(&self, cx: &Cx) -> Result<Vec<ReadEvalDecision>> {
115 self.decisions_for_run(cx, &self.run)
116 }
117
118 pub(super) fn decisions_for_run(&self, cx: &Cx, run: &Ref) -> Result<Vec<ReadEvalDecision>> {
119 self.events_for_run(run)?
120 .iter()
121 .filter_map(|event| match &event.kind {
122 EventKind::Trace(reference) => Some(decision_from_ref(cx, reference)),
123 _ => None,
124 })
125 .collect()
126 }
127
128 fn lock(&self) -> Result<MutexGuard<'_, EventLedger>> {
129 self.events
130 .lock()
131 .map_err(|_| Error::PoisonedLock("read-eval decision ledger"))
132 }
133}
134
135fn decision_ref(cx: &mut Cx, decision: &ReadEvalDecision) -> Result<Ref> {
136 let id = cx.datum_store_mut().intern(decision_datum(decision))?;
137 Ok(Ref::Content(id))
138}
139
140fn decision_from_ref(cx: &Cx, reference: &Ref) -> Result<ReadEvalDecision> {
141 let Ref::Content(id) = reference else {
142 return Err(Error::Eval(
143 "read-eval decision trace does not reference content".to_owned(),
144 ));
145 };
146 let datum = cx
147 .datum_store()
148 .get(id)?
149 .ok_or_else(|| Error::Eval("read-eval decision content is missing".to_owned()))?;
150 decision_from_datum(datum)
151}
152
153fn decision_datum(decision: &ReadEvalDecision) -> Datum {
154 Datum::Node {
155 tag: decision_tag(),
156 fields: vec![
157 (Symbol::new("origin"), origin_datum(&decision.origin)),
158 (Symbol::new("codec"), Datum::Symbol(decision.codec.clone())),
159 (
160 Symbol::new("expected-shape"),
161 option_symbol_datum(decision.expected_shape.as_ref()),
162 ),
163 (
164 Symbol::new("requires"),
165 capabilities_datum(&decision.requires),
166 ),
167 (
168 Symbol::new("requested"),
169 capabilities_datum(&decision.requested),
170 ),
171 (Symbol::new("active"), capabilities_datum(&decision.active)),
172 (
173 Symbol::new("outcome"),
174 Datum::Symbol(decision.outcome.as_symbol()),
175 ),
176 ],
177 }
178}
179
180fn decision_from_datum(datum: &Datum) -> Result<ReadEvalDecision> {
181 let Datum::Node { tag, fields } = datum else {
182 return Err(Error::Eval(
183 "read-eval decision trace payload must be a datum node".to_owned(),
184 ));
185 };
186 if tag != &decision_tag() {
187 return Err(Error::Eval(
188 "trace payload is not a read-eval decision".to_owned(),
189 ));
190 }
191 Ok(ReadEvalDecision {
192 origin: origin_from_datum(field(fields, "origin")?)?,
193 codec: symbol_field(fields, "codec")?.clone(),
194 expected_shape: option_symbol_from_datum(field(fields, "expected-shape")?)?,
195 requires: capabilities_from_datum(field(fields, "requires")?)?,
196 requested: capabilities_from_datum(field(fields, "requested")?)?,
197 active: capabilities_from_datum(field(fields, "active")?)?,
198 outcome: ReadEvalOutcome::from_symbol(symbol_field(fields, "outcome")?)?,
199 })
200}
201
202fn origin_datum(origin: &RequestOrigin) -> Datum {
203 Datum::Node {
204 tag: Symbol::qualified("read-eval", "origin"),
205 fields: vec![
206 (Symbol::new("tag"), Datum::Symbol(origin.tag.clone())),
207 (
208 Symbol::new("detail"),
209 origin.detail.as_ref().map_or(Datum::Nil, expr_datum_lossy),
210 ),
211 ],
212 }
213}
214
215fn origin_from_datum(datum: &Datum) -> Result<RequestOrigin> {
216 let Datum::Node { tag, fields } = datum else {
217 return Err(Error::Eval(
218 "read-eval decision origin must be a datum node".to_owned(),
219 ));
220 };
221 if tag != &Symbol::qualified("read-eval", "origin") {
222 return Err(Error::Eval(
223 "read-eval decision origin has the wrong tag".to_owned(),
224 ));
225 }
226 let detail = match field(fields, "detail")? {
227 Datum::Nil => None,
228 other => Some(Expr::from(other.clone())),
229 };
230 Ok(RequestOrigin {
231 tag: symbol_field(fields, "tag")?.clone(),
232 detail,
233 })
234}
235
236fn expr_datum_lossy(expr: &Expr) -> Datum {
237 Datum::try_from(expr.clone()).unwrap_or_else(|_| Datum::String(format!("{expr:?}")))
238}
239
240fn capabilities_datum(capabilities: &[CapabilityName]) -> Datum {
241 Datum::Vector(
242 capabilities
243 .iter()
244 .map(|capability| Datum::String(capability.as_str().to_owned()))
245 .collect(),
246 )
247}
248
249fn capabilities_from_datum(datum: &Datum) -> Result<Vec<CapabilityName>> {
250 let Datum::Vector(items) = datum else {
251 return Err(Error::Eval(
252 "read-eval decision capabilities must be a vector".to_owned(),
253 ));
254 };
255 items
256 .iter()
257 .map(|item| match item {
258 Datum::String(name) => Ok(CapabilityName::new(name.clone())),
259 _ => Err(Error::Eval(
260 "read-eval decision capability must be a string".to_owned(),
261 )),
262 })
263 .collect()
264}
265
266fn option_symbol_datum(symbol: Option<&Symbol>) -> Datum {
267 symbol.cloned().map_or(Datum::Nil, Datum::Symbol)
268}
269
270fn option_symbol_from_datum(datum: &Datum) -> Result<Option<Symbol>> {
271 match datum {
272 Datum::Nil => Ok(None),
273 Datum::Symbol(symbol) => Ok(Some(symbol.clone())),
274 _ => Err(Error::Eval(
275 "read-eval decision optional symbol field is malformed".to_owned(),
276 )),
277 }
278}
279
280fn symbol_field<'a>(fields: &'a [(Symbol, Datum)], name: &str) -> Result<&'a Symbol> {
281 match field(fields, name)? {
282 Datum::Symbol(symbol) => Ok(symbol),
283 _ => Err(Error::Eval(format!(
284 "read-eval decision field {name} must be a symbol"
285 ))),
286 }
287}
288
289fn field<'a>(fields: &'a [(Symbol, Datum)], name: &str) -> Result<&'a Datum> {
290 fields
291 .iter()
292 .find_map(|(field, value)| (field.name.as_ref() == name).then_some(value))
293 .ok_or_else(|| Error::Eval(format!("read-eval decision missing {name} field")))
294}
295
296fn decision_tag() -> Symbol {
297 Symbol::qualified("read-eval", "decision")
298}
299
300pub(super) fn decision_from_request(
301 request: &super::ReadEvalRequest,
302 active: &CapabilitySet,
303 outcome: ReadEvalOutcome,
304) -> ReadEvalDecision {
305 ReadEvalDecision {
306 origin: request.origin.clone(),
307 codec: request.codec.clone(),
308 expected_shape: request.expected_shape.symbol(),
309 requires: request.requires.clone(),
310 requested: request.allow.iter().cloned().collect(),
311 active: active.iter().cloned().collect(),
312 outcome,
313 }
314}