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