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