Skip to main content

sim_lib_operation_gate/
lib.rs

1//! Declaration-driven gate for capability-scoped operations.
2//!
3//! The gate contains no domain policy: callers provide a manifest declaration,
4//! exact approval verifier/use adapters, a record sink, and the performer.
5
6#![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/// Policy label for an operation. None implies reversibility.
28#[derive(Clone, Copy, Debug, PartialEq, Eq)]
29pub enum ExecutionMode {
30    /// Read or observation whose execution is still an effect.
31    Observation,
32    /// Effect recorded for audit and replay without review.
33    Recorded,
34    /// Effect requiring an exact approval before its first performance.
35    Reviewed,
36}
37
38/// Canonical operation declaration supplied by a domain manifest.
39#[derive(Clone, Debug, PartialEq, Eq)]
40pub struct OperationDeclaration {
41    /// Exact operation identity.
42    pub operation: String,
43    /// Exact approval subject.
44    pub subject: Ref,
45    /// Required capability.
46    pub capability: CapabilityName,
47    /// Execution policy label.
48    pub mode: ExecutionMode,
49}
50
51/// Approval presented for a reviewed operation.
52#[derive(Clone, Debug, PartialEq, Eq)]
53pub struct Approval {
54    /// Stable approval identity.
55    pub id: String,
56    /// Exact subject this approval authorizes.
57    pub subject: Ref,
58    /// Decision asserted by the approver.
59    pub decision: ApprovalDecision,
60}
61
62/// Explicit approval decision.
63#[derive(Clone, Copy, Debug, PartialEq, Eq)]
64pub enum ApprovalDecision {
65    /// Permit the exact subject.
66    Approve,
67    /// Refuse the exact subject.
68    Deny,
69}
70
71/// Validates approval authenticity and validity without consuming it.
72pub trait ApprovalVerifier {
73    /// Reject invalid, expired, or otherwise unusable approval evidence.
74    fn verify(&self, approval: &Approval) -> Result<()>;
75}
76
77/// Atomically consumes a verified approval once.
78pub trait ApprovalUse {
79    /// Consume `approval`, rejecting reuse or policy denial.
80    fn consume(&self, approval: &Approval) -> Result<()>;
81}
82
83/// Audit record emitted after successful first performance.
84#[derive(Clone, Debug, PartialEq, Eq)]
85pub struct GateRecord {
86    /// Operation identity.
87    pub operation: String,
88    /// Exact subject.
89    pub subject: Ref,
90    /// Required capability.
91    pub capability: CapabilityName,
92    /// Applied mode.
93    pub mode: ExecutionMode,
94    /// Consumed approval id, if reviewed.
95    pub approval: Option<String>,
96    /// Result returned by the performer.
97    pub result: Ref,
98}
99
100/// Receives gate records.
101pub trait GateRecordSink {
102    /// Persist one successful first-performance record.
103    fn record(&self, record: GateRecord) -> Result<()>;
104}
105
106/// Policy for a record-sink failure after the operation performed.
107#[derive(Clone, Copy, Debug, PartialEq, Eq)]
108pub enum SinkFailurePolicy {
109    /// Fail closed and surface the sink error.
110    FailClosed,
111    /// Keep the successful operation result despite an unavailable sink.
112    PreserveResult,
113}
114
115/// Dependencies used to guard one operation.
116pub struct GateContext<'a> {
117    /// Optional approval for a reviewed operation.
118    pub approval: Option<&'a Approval>,
119    /// Approval verifier.
120    pub verifier: &'a dyn ApprovalVerifier,
121    /// Atomic approval-use adapter.
122    pub approval_use: &'a dyn ApprovalUse,
123    /// Audit sink.
124    pub sink: &'a dyn GateRecordSink,
125    /// Explicit sink failure policy.
126    pub sink_failure: SinkFailurePolicy,
127}
128
129/// Guard and resolve an effect, returning the kernel's result reference directly.
130///
131/// Approval verification, atomic use, performance, and record emission occur
132/// only in the `resolve_effect` performer. Cassette replay therefore repeats
133/// none of them.
134pub 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;