sim_lib_operation_gate/
lib.rs1#![forbid(unsafe_code)]
7#![deny(missing_docs)]
8
9use sim_kernel::{
10 CapabilityName, Cx, Error, Ref, Result,
11 effect::{Effect, resolve_effect},
12};
13
14#[derive(Clone, Copy, Debug, PartialEq, Eq)]
16pub enum ExecutionMode {
17 Observation,
19 Recorded,
21 Reviewed,
23}
24
25#[derive(Clone, Debug, PartialEq, Eq)]
27pub struct OperationDeclaration {
28 pub operation: String,
30 pub subject: Ref,
32 pub capability: CapabilityName,
34 pub mode: ExecutionMode,
36}
37
38#[derive(Clone, Debug, PartialEq, Eq)]
40pub struct Approval {
41 pub id: String,
43 pub subject: Ref,
45 pub decision: ApprovalDecision,
47}
48
49#[derive(Clone, Copy, Debug, PartialEq, Eq)]
51pub enum ApprovalDecision {
52 Approve,
54 Deny,
56}
57
58pub trait ApprovalVerifier {
60 fn verify(&self, approval: &Approval) -> Result<()>;
62}
63
64pub trait ApprovalUse {
66 fn consume(&self, approval: &Approval) -> Result<()>;
68}
69
70#[derive(Clone, Debug, PartialEq, Eq)]
72pub struct GateRecord {
73 pub operation: String,
75 pub subject: Ref,
77 pub capability: CapabilityName,
79 pub mode: ExecutionMode,
81 pub approval: Option<String>,
83 pub result: Ref,
85}
86
87pub trait GateRecordSink {
89 fn record(&self, record: GateRecord) -> Result<()>;
91}
92
93#[derive(Clone, Copy, Debug, PartialEq, Eq)]
95pub enum SinkFailurePolicy {
96 FailClosed,
98 PreserveResult,
100}
101
102pub struct GateContext<'a> {
104 pub approval: Option<&'a Approval>,
106 pub verifier: &'a dyn ApprovalVerifier,
108 pub approval_use: &'a dyn ApprovalUse,
110 pub sink: &'a dyn GateRecordSink,
112 pub sink_failure: SinkFailurePolicy,
114}
115
116pub fn guard_operation<F>(
122 cx: &mut Cx,
123 declaration: &OperationDeclaration,
124 effect: Effect,
125 gate: GateContext<'_>,
126 perform: F,
127) -> Result<Ref>
128where
129 F: FnOnce(&mut Cx, &Effect) -> Result<Ref>,
130{
131 if !effect
132 .requires
133 .iter()
134 .any(|capability| capability == &declaration.capability)
135 {
136 return Err(Error::Eval(format!(
137 "operation {} effect omits declared capability {}",
138 declaration.operation,
139 declaration.capability.as_str()
140 )));
141 }
142 resolve_effect(cx, effect, |cx, effect| {
143 let approval_id = match declaration.mode {
144 ExecutionMode::Observation | ExecutionMode::Recorded => None,
145 ExecutionMode::Reviewed => {
146 let approval = gate.approval.ok_or_else(|| {
147 Error::Eval(format!(
148 "operation {} requires approval",
149 declaration.operation
150 ))
151 })?;
152 if approval.subject != declaration.subject {
153 return Err(Error::Eval(format!(
154 "approval {} subject does not match operation {} subject",
155 approval.id, declaration.operation
156 )));
157 }
158 if approval.decision != ApprovalDecision::Approve {
159 return Err(Error::Eval(format!(
160 "approval {} does not approve",
161 approval.id
162 )));
163 }
164 gate.verifier.verify(approval)?;
165 gate.approval_use.consume(approval)?;
166 Some(approval.id.clone())
167 }
168 };
169 let result = perform(cx, effect)?;
170 let record = GateRecord {
171 operation: declaration.operation.clone(),
172 subject: declaration.subject.clone(),
173 capability: declaration.capability.clone(),
174 mode: declaration.mode,
175 approval: approval_id,
176 result: result.clone(),
177 };
178 match gate.sink.record(record) {
179 Ok(()) => Ok(result),
180 Err(_) if gate.sink_failure == SinkFailurePolicy::PreserveResult => Ok(result),
181 Err(error) => Err(error),
182 }
183 })
184}
185
186#[cfg(test)]
187mod tests;