1#![forbid(unsafe_code)]
7#![deny(missing_docs)]
8
9mod durable;
10mod operation_error;
11mod operation_service;
12mod operation_wire;
13
14pub use durable::{
15 DispatchId, DurableOperationState, OperationAttempt, OperationAttemptId, OperationDispatch,
16 OperationGrant, OperationGrantId, OperationId, OperationIntent, OperationIntentId,
17 OperationPerformer, PerformerReceipt, PerformerReceiptId, PerformerResponse, ReplayPolicy,
18};
19pub use operation_error::OperationError;
20pub use operation_service::{OperationRecord, OperationService};
21
22use sim_kernel::{
23 CapabilityName, Cx, Error, Ref, Result,
24 effect::{Effect, resolve_effect},
25};
26
27#[derive(Clone, Copy, Debug, PartialEq, Eq)]
29pub enum ExecutionMode {
30 Observation,
32 Recorded,
34 Reviewed,
36}
37
38#[derive(Clone, Debug, PartialEq, Eq)]
40pub struct OperationDeclaration {
41 pub operation: String,
43 pub subject: Ref,
45 pub capability: CapabilityName,
47 pub mode: ExecutionMode,
49}
50
51#[derive(Clone, Debug, PartialEq, Eq)]
53pub struct Approval {
54 pub id: String,
56 pub subject: Ref,
58 pub decision: ApprovalDecision,
60}
61
62#[derive(Clone, Copy, Debug, PartialEq, Eq)]
64pub enum ApprovalDecision {
65 Approve,
67 Deny,
69}
70
71pub trait ApprovalVerifier {
73 fn verify(&self, approval: &Approval) -> Result<()>;
75}
76
77pub trait ApprovalUse {
79 fn consume(&self, approval: &Approval) -> Result<()>;
81}
82
83#[derive(Clone, Debug, PartialEq, Eq)]
85pub struct GateRecord {
86 pub operation: String,
88 pub subject: Ref,
90 pub capability: CapabilityName,
92 pub mode: ExecutionMode,
94 pub approval: Option<String>,
96 pub result: Ref,
98}
99
100pub trait GateRecordSink {
102 fn record(&self, record: GateRecord) -> Result<()>;
104}
105
106#[derive(Clone, Copy, Debug, PartialEq, Eq)]
108pub enum SinkFailurePolicy {
109 FailClosed,
111 PreserveResult,
113}
114
115pub struct GateContext<'a> {
117 pub approval: Option<&'a Approval>,
119 pub verifier: &'a dyn ApprovalVerifier,
121 pub approval_use: &'a dyn ApprovalUse,
123 pub sink: &'a dyn GateRecordSink,
125 pub sink_failure: SinkFailurePolicy,
127}
128
129pub fn guard_operation<F>(
135 cx: &mut Cx,
136 declaration: &OperationDeclaration,
137 effect: Effect,
138 gate: GateContext<'_>,
139 perform: F,
140) -> Result<Ref>
141where
142 F: FnOnce(&mut Cx, &Effect) -> Result<Ref>,
143{
144 if !effect
145 .requires
146 .iter()
147 .any(|capability| capability == &declaration.capability)
148 {
149 return Err(Error::Eval(format!(
150 "operation {} effect omits declared capability {}",
151 declaration.operation,
152 declaration.capability.as_str()
153 )));
154 }
155 resolve_effect(cx, effect, |cx, effect| {
156 let approval_id = match declaration.mode {
157 ExecutionMode::Observation | ExecutionMode::Recorded => None,
158 ExecutionMode::Reviewed => {
159 let approval = gate.approval.ok_or_else(|| {
160 Error::Eval(format!(
161 "operation {} requires approval",
162 declaration.operation
163 ))
164 })?;
165 if approval.subject != declaration.subject {
166 return Err(Error::Eval(format!(
167 "approval {} subject does not match operation {} subject",
168 approval.id, declaration.operation
169 )));
170 }
171 if approval.decision != ApprovalDecision::Approve {
172 return Err(Error::Eval(format!(
173 "approval {} does not approve",
174 approval.id
175 )));
176 }
177 gate.verifier.verify(approval)?;
178 gate.approval_use.consume(approval)?;
179 Some(approval.id.clone())
180 }
181 };
182 let result = perform(cx, effect)?;
183 let record = GateRecord {
184 operation: declaration.operation.clone(),
185 subject: declaration.subject.clone(),
186 capability: declaration.capability.clone(),
187 mode: declaration.mode,
188 approval: approval_id,
189 result: result.clone(),
190 };
191 match gate.sink.record(record) {
192 Ok(()) => Ok(result),
193 Err(_) if gate.sink_failure == SinkFailurePolicy::PreserveResult => Ok(result),
194 Err(error) => Err(error),
195 }
196 })
197}
198
199#[cfg(test)]
200mod durable_tests;
201
202#[cfg(test)]
203mod tests;