1#![forbid(unsafe_code)]
9#![deny(missing_docs)]
10
11mod durable;
12mod lifecycle;
13mod lifecycle_engine;
14mod lifecycle_project;
15mod lifecycle_record;
16mod lifecycle_wire;
17mod operation_error;
18mod operation_service;
19mod operation_wire;
20
21pub use durable::{
22 DispatchId, DurableOperationState, OperationAttempt, OperationAttemptId, OperationDispatch,
23 OperationGrant, OperationGrantId, OperationId, OperationIntent, OperationIntentId,
24 OperationPerformer, PerformerReceipt, PerformerReceiptId, PerformerResponse, ReplayPolicy,
25};
26pub use lifecycle::{
27 EvidenceSetId, FencedDispatch, FencedDispatchId, LeaseWindow, LifecyclePerformer,
28 LifecyclePerformerResponse, LifecycleReceipt, LifecycleReceiptId, OperationLease,
29 OperationLeaseId, OperationObservation, OperationObservationId, OperationOutcome,
30 OperationOutcomeId, OperationStep, PostconditionObserver, PostconditionRequest,
31 PostconditionResponse,
32};
33pub use lifecycle_engine::OperationLifecycle;
34pub use lifecycle_record::OperationLifecycleRecord;
35pub use operation_error::OperationError;
36pub use operation_service::{OperationRecord, OperationService};
37
38use sim_kernel::{
39 CapabilityName, Cx, Error, Ref, Result,
40 effect::{Effect, resolve_effect},
41};
42
43#[derive(Clone, Copy, Debug, PartialEq, Eq)]
45pub enum ExecutionMode {
46 Observation,
48 Recorded,
50 Reviewed,
52}
53
54#[derive(Clone, Debug, PartialEq, Eq)]
56pub struct OperationDeclaration {
57 pub operation: String,
59 pub subject: Ref,
61 pub capability: CapabilityName,
63 pub mode: ExecutionMode,
65}
66
67#[derive(Clone, Debug, PartialEq, Eq)]
69pub struct Approval {
70 pub id: String,
72 pub subject: Ref,
74 pub decision: ApprovalDecision,
76}
77
78#[derive(Clone, Copy, Debug, PartialEq, Eq)]
80pub enum ApprovalDecision {
81 Approve,
83 Deny,
85}
86
87pub trait ApprovalVerifier {
89 fn verify(&self, approval: &Approval) -> Result<()>;
91}
92
93pub trait ApprovalUse {
95 fn consume(&self, approval: &Approval) -> Result<()>;
97}
98
99#[derive(Clone, Debug, PartialEq, Eq)]
101pub struct GateRecord {
102 pub operation: String,
104 pub subject: Ref,
106 pub capability: CapabilityName,
108 pub mode: ExecutionMode,
110 pub approval: Option<String>,
112 pub result: Ref,
114}
115
116pub trait GateRecordSink {
118 fn record(&self, record: GateRecord) -> Result<()>;
120}
121
122#[derive(Clone, Copy, Debug, PartialEq, Eq)]
124pub enum SinkFailurePolicy {
125 FailClosed,
127 PreserveResult,
129}
130
131pub struct GateContext<'a> {
133 pub approval: Option<&'a Approval>,
135 pub verifier: &'a dyn ApprovalVerifier,
137 pub approval_use: &'a dyn ApprovalUse,
139 pub sink: &'a dyn GateRecordSink,
141 pub sink_failure: SinkFailurePolicy,
143}
144
145pub fn guard_operation<F>(
151 cx: &mut Cx,
152 declaration: &OperationDeclaration,
153 effect: Effect,
154 gate: GateContext<'_>,
155 perform: F,
156) -> Result<Ref>
157where
158 F: FnOnce(&mut Cx, &Effect) -> Result<Ref>,
159{
160 if !effect
161 .requires
162 .iter()
163 .any(|capability| capability == &declaration.capability)
164 {
165 return Err(Error::Eval(format!(
166 "operation {} effect omits declared capability {}",
167 declaration.operation,
168 declaration.capability.as_str()
169 )));
170 }
171 resolve_effect(cx, effect, |cx, effect| {
172 let approval_id = match declaration.mode {
173 ExecutionMode::Observation | ExecutionMode::Recorded => None,
174 ExecutionMode::Reviewed => {
175 let approval = gate.approval.ok_or_else(|| {
176 Error::Eval(format!(
177 "operation {} requires approval",
178 declaration.operation
179 ))
180 })?;
181 if approval.subject != declaration.subject {
182 return Err(Error::Eval(format!(
183 "approval {} subject does not match operation {} subject",
184 approval.id, declaration.operation
185 )));
186 }
187 if approval.decision != ApprovalDecision::Approve {
188 return Err(Error::Eval(format!(
189 "approval {} does not approve",
190 approval.id
191 )));
192 }
193 gate.verifier.verify(approval)?;
194 gate.approval_use.consume(approval)?;
195 Some(approval.id.clone())
196 }
197 };
198 let result = perform(cx, effect)?;
199 let record = GateRecord {
200 operation: declaration.operation.clone(),
201 subject: declaration.subject.clone(),
202 capability: declaration.capability.clone(),
203 mode: declaration.mode,
204 approval: approval_id,
205 result: result.clone(),
206 };
207 match gate.sink.record(record) {
208 Ok(()) => Ok(result),
209 Err(_) if gate.sink_failure == SinkFailurePolicy::PreserveResult => Ok(result),
210 Err(error) => Err(error),
211 }
212 })
213}
214
215#[cfg(test)]
216mod durable_tests;
217
218#[cfg(test)]
219mod lifecycle_tests;
220
221#[cfg(test)]
222mod tests;