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