Skip to main content

sim_lib_operation_gate/
durable.rs

1//! Durable operation intent, dispatch, and raw performer receipts.
2
3use std::fmt;
4
5use crate::{operation_error::OperationError, operation_wire::*};
6use sim_kernel::{CapabilityName, ContentId, Datum, Symbol};
7
8pub(crate) const INTENT_TAG: &str = "intent-v1";
9pub(crate) const GRANT_TAG: &str = "grant-v1";
10pub(crate) const ATTEMPT_TAG: &str = "attempt-v1";
11pub(crate) const DISPATCH_TAG: &str = "dispatch-v1";
12pub(crate) const RECEIPT_TAG: &str = "performer-receipt-v1";
13
14/// Replay rule bound into immutable operation intent.
15#[derive(Clone, Copy, Debug, PartialEq, Eq)]
16pub enum ReplayPolicy {
17    /// A later reconciliation phase may prove that repeat performance is safe.
18    Idempotent,
19    /// A durable dispatch is an at-most-once barrier, even without an acknowledgement.
20    ExactlyOnce,
21}
22
23impl ReplayPolicy {
24    pub(crate) fn datum(self) -> Datum {
25        Datum::Symbol(Symbol::qualified(
26            "operation",
27            match self {
28                Self::Idempotent => "idempotent",
29                Self::ExactlyOnce => "exactly-once",
30            },
31        ))
32    }
33
34    pub(crate) fn from_datum(datum: &Datum) -> Result<Self, OperationError> {
35        match datum {
36            Datum::Symbol(value) if *value == Symbol::qualified("operation", "idempotent") => {
37                Ok(Self::Idempotent)
38            }
39            Datum::Symbol(value) if *value == Symbol::qualified("operation", "exactly-once") => {
40                Ok(Self::ExactlyOnce)
41            }
42            _ => Err(OperationError::NonCanonical("replay policy")),
43        }
44    }
45}
46
47/// Stable identity derived only from canonical immutable operation intent.
48#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
49pub struct OperationId(pub(crate) ContentId);
50
51/// Identity of canonical operation intent.
52pub type OperationIntentId = OperationId;
53
54impl OperationId {
55    /// Borrows the kernel semantic content identity.
56    pub const fn content_id(&self) -> &ContentId {
57        &self.0
58    }
59}
60
61impl fmt::Display for OperationId {
62    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
63        render_id(&self.0, formatter)
64    }
65}
66
67/// Canonical semantic intent whose identity survives grants, attempts, and leases.
68#[derive(Clone, Debug, PartialEq, Eq)]
69pub struct OperationIntent {
70    pub(crate) id: OperationId,
71    pub(crate) operation: String,
72    pub(crate) target: Datum,
73    pub(crate) intended_result: Datum,
74    pub(crate) replay_policy: ReplayPolicy,
75}
76
77impl OperationIntent {
78    /// Constructs and identifies one immutable intent.
79    pub fn new(
80        operation: impl Into<String>,
81        target: Datum,
82        intended_result: Datum,
83        replay_policy: ReplayPolicy,
84    ) -> Result<Self, OperationError> {
85        let operation = operation.into();
86        if operation.is_empty() {
87            return Err(OperationError::EmptyOperation);
88        }
89        let datum = intent_datum(&operation, &target, &intended_result, replay_policy);
90        Ok(Self {
91            id: OperationId(content_id(&datum)?),
92            operation,
93            target,
94            intended_result,
95            replay_policy,
96        })
97    }
98
99    /// Returns the stable operation identity.
100    pub const fn id(&self) -> &OperationId {
101        &self.id
102    }
103
104    /// Returns the open domain operation name.
105    pub fn operation(&self) -> &str {
106        &self.operation
107    }
108
109    /// Returns the exact semantic target.
110    pub const fn target(&self) -> &Datum {
111        &self.target
112    }
113
114    /// Returns the intended semantic postcondition.
115    pub const fn intended_result(&self) -> &Datum {
116        &self.intended_result
117    }
118
119    /// Returns the replay rule bound into this intent.
120    pub const fn replay_policy(&self) -> ReplayPolicy {
121        self.replay_policy
122    }
123
124    /// Returns the exact canonical value whose id is [`Self::id`].
125    pub fn canonical_datum(&self) -> Datum {
126        intent_datum(
127            &self.operation,
128            &self.target,
129            &self.intended_result,
130            self.replay_policy,
131        )
132    }
133
134    pub(crate) fn verify(&self) -> Result<(), OperationError> {
135        if content_id(&self.canonical_datum())? != self.id.0 {
136            return Err(OperationError::ContradictoryIntent);
137        }
138        Ok(())
139    }
140
141    pub(crate) fn from_datum(datum: &Datum) -> Result<Self, OperationError> {
142        let fields = node_fields(datum, INTENT_TAG, 4)?;
143        let operation = string_field(fields, "operation")?.to_owned();
144        let target = field(fields, "target")?.clone();
145        let intended_result = field(fields, "intended-result")?.clone();
146        let replay_policy = ReplayPolicy::from_datum(field(fields, "replay-policy")?)?;
147        let value = Self::new(operation, target, intended_result, replay_policy)?;
148        if value.canonical_datum() != *datum {
149            return Err(OperationError::NonCanonical("operation intent"));
150        }
151        Ok(value)
152    }
153}
154
155/// Identity of one separately recorded least-authority grant.
156#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
157pub struct OperationGrantId(pub(crate) ContentId);
158
159impl OperationGrantId {
160    /// Borrows the grant's semantic content identity.
161    pub const fn content_id(&self) -> &ContentId {
162        &self.0
163    }
164}
165
166/// Exact authority presented for one operation.
167#[derive(Clone, Debug, PartialEq, Eq)]
168pub struct OperationGrant {
169    pub(crate) id: OperationGrantId,
170    pub(crate) operation: OperationId,
171    pub(crate) capability: CapabilityName,
172    pub(crate) authority: Datum,
173}
174
175impl OperationGrant {
176    /// Constructs a grant independently of any writer lease or attempt.
177    pub fn new(
178        operation: OperationId,
179        capability: CapabilityName,
180        authority: Datum,
181    ) -> Result<Self, OperationError> {
182        let datum = grant_datum(&operation, &capability, &authority);
183        Ok(Self {
184            id: OperationGrantId(content_id(&datum)?),
185            operation,
186            capability,
187            authority,
188        })
189    }
190
191    /// Returns the grant identity.
192    pub const fn id(&self) -> &OperationGrantId {
193        &self.id
194    }
195
196    /// Returns the operation this grant can authorize.
197    pub const fn operation(&self) -> &OperationId {
198        &self.operation
199    }
200
201    /// Returns the exact capability recorded by the grant.
202    pub const fn capability(&self) -> &CapabilityName {
203        &self.capability
204    }
205
206    /// Returns the canonical authority evidence supplied by the caller.
207    pub const fn authority(&self) -> &Datum {
208        &self.authority
209    }
210
211    /// Returns the grant's canonical semantic value.
212    pub fn canonical_datum(&self) -> Datum {
213        grant_datum(&self.operation, &self.capability, &self.authority)
214    }
215
216    pub(crate) fn verify(&self) -> Result<(), OperationError> {
217        if content_id(&self.canonical_datum())? != self.id.0 {
218            return Err(OperationError::NonCanonical("operation grant"));
219        }
220        Ok(())
221    }
222
223    pub(crate) fn from_datum(datum: &Datum) -> Result<Self, OperationError> {
224        let fields = node_fields(datum, GRANT_TAG, 3)?;
225        let operation = OperationId(id_from_datum(field(fields, "operation")?)?);
226        let capability = CapabilityName::new(string_field(fields, "capability")?);
227        let authority = field(fields, "authority")?.clone();
228        let value = Self::new(operation, capability, authority)?;
229        if value.canonical_datum() != *datum {
230            return Err(OperationError::NonCanonical("operation grant"));
231        }
232        Ok(value)
233    }
234}
235
236/// Identity of one attempt record, kept separate from the operation id.
237#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
238pub struct OperationAttemptId(pub(crate) ContentId);
239
240/// One caller-selected attempt ordinal for a stable operation.
241#[derive(Clone, Debug, PartialEq, Eq)]
242pub struct OperationAttempt {
243    pub(crate) id: OperationAttemptId,
244    pub(crate) operation: OperationId,
245    pub(crate) ordinal: u64,
246}
247
248impl OperationAttempt {
249    /// Constructs an attempt whose identity contains no writer lease.
250    pub fn new(operation: OperationId, ordinal: u64) -> Result<Self, OperationError> {
251        let datum = attempt_datum(&operation, ordinal);
252        Ok(Self {
253            id: OperationAttemptId(content_id(&datum)?),
254            operation,
255            ordinal,
256        })
257    }
258
259    /// Returns the attempt identity.
260    pub const fn id(&self) -> &OperationAttemptId {
261        &self.id
262    }
263
264    /// Returns the stable operation attempted.
265    pub const fn operation(&self) -> &OperationId {
266        &self.operation
267    }
268
269    /// Returns the caller-selected attempt ordinal.
270    pub const fn ordinal(&self) -> u64 {
271        self.ordinal
272    }
273
274    /// Returns the attempt's canonical semantic value.
275    pub fn canonical_datum(&self) -> Datum {
276        attempt_datum(&self.operation, self.ordinal)
277    }
278
279    pub(crate) fn verify(&self) -> Result<(), OperationError> {
280        if content_id(&self.canonical_datum())? != self.id.0 {
281            return Err(OperationError::NonCanonical("operation attempt"));
282        }
283        Ok(())
284    }
285
286    pub(crate) fn from_datum(datum: &Datum) -> Result<Self, OperationError> {
287        let fields = node_fields(datum, ATTEMPT_TAG, 2)?;
288        let operation = OperationId(id_from_datum(field(fields, "operation")?)?);
289        let ordinal = u64_field(fields, "ordinal")?;
290        let value = Self::new(operation, ordinal)?;
291        if value.canonical_datum() != *datum {
292            return Err(OperationError::NonCanonical("operation attempt"));
293        }
294        Ok(value)
295    }
296}
297
298impl OperationAttemptId {
299    /// Borrows the attempt's semantic content identity.
300    pub const fn content_id(&self) -> &ContentId {
301        &self.0
302    }
303}
304
305/// Identity of a dispatch durably recorded before a performer is called.
306#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
307pub struct DispatchId(pub(crate) ContentId);
308
309impl DispatchId {
310    /// Borrows the dispatch's semantic content identity.
311    pub const fn content_id(&self) -> &ContentId {
312        &self.0
313    }
314}
315
316/// Exact durable handoff to an injected performer.
317#[derive(Clone, Debug, PartialEq, Eq)]
318pub struct OperationDispatch {
319    pub(crate) id: DispatchId,
320    pub(crate) operation: OperationId,
321    pub(crate) grant: OperationGrantId,
322    pub(crate) attempt: OperationAttemptId,
323}
324
325impl OperationDispatch {
326    pub(crate) fn new(
327        operation: OperationId,
328        grant: OperationGrantId,
329        attempt: OperationAttemptId,
330    ) -> Result<Self, OperationError> {
331        let datum = dispatch_datum(&operation, &grant, &attempt);
332        Ok(Self {
333            id: DispatchId(content_id(&datum)?),
334            operation,
335            grant,
336            attempt,
337        })
338    }
339
340    /// Returns the durable dispatch identity.
341    pub const fn id(&self) -> &DispatchId {
342        &self.id
343    }
344
345    /// Returns the stable operation identity.
346    pub const fn operation(&self) -> &OperationId {
347        &self.operation
348    }
349
350    /// Returns the exact separately persisted grant identity.
351    pub const fn grant(&self) -> &OperationGrantId {
352        &self.grant
353    }
354
355    /// Returns the exact separately persisted attempt identity.
356    pub const fn attempt(&self) -> &OperationAttemptId {
357        &self.attempt
358    }
359
360    /// Returns the dispatch's canonical semantic value.
361    pub fn canonical_datum(&self) -> Datum {
362        dispatch_datum(&self.operation, &self.grant, &self.attempt)
363    }
364
365    pub(crate) fn from_datum(datum: &Datum) -> Result<Self, OperationError> {
366        let fields = node_fields(datum, DISPATCH_TAG, 3)?;
367        let operation = OperationId(id_from_datum(field(fields, "operation")?)?);
368        let grant = OperationGrantId(id_from_datum(field(fields, "grant")?)?);
369        let attempt = OperationAttemptId(id_from_datum(field(fields, "attempt")?)?);
370        let value = Self::new(operation, grant, attempt)?;
371        if value.canonical_datum() != *datum {
372            return Err(OperationError::NonCanonical("operation dispatch"));
373        }
374        Ok(value)
375    }
376}
377
378/// Identity of a raw performer acknowledgement.
379#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
380pub struct PerformerReceiptId(pub(crate) ContentId);
381
382impl PerformerReceiptId {
383    /// Borrows the receipt's semantic content identity.
384    pub const fn content_id(&self) -> &ContentId {
385        &self.0
386    }
387}
388
389/// Raw performer acknowledgement bound to one durable dispatch.
390#[derive(Clone, Debug, PartialEq, Eq)]
391pub struct PerformerReceipt {
392    pub(crate) id: PerformerReceiptId,
393    pub(crate) dispatch: DispatchId,
394    pub(crate) raw: Datum,
395}
396
397impl PerformerReceipt {
398    pub(crate) fn new(dispatch: DispatchId, raw: Datum) -> Result<Self, OperationError> {
399        let datum = receipt_datum(&dispatch, &raw);
400        Ok(Self {
401            id: PerformerReceiptId(content_id(&datum)?),
402            dispatch,
403            raw,
404        })
405    }
406
407    /// Returns the raw receipt identity.
408    pub const fn id(&self) -> &PerformerReceiptId {
409        &self.id
410    }
411
412    /// Returns the dispatch acknowledged by the performer.
413    pub const fn dispatch(&self) -> &DispatchId {
414        &self.dispatch
415    }
416
417    /// Returns the uninterpreted canonical performer response.
418    pub const fn raw(&self) -> &Datum {
419        &self.raw
420    }
421
422    /// Returns the receipt's canonical semantic value.
423    pub fn canonical_datum(&self) -> Datum {
424        receipt_datum(&self.dispatch, &self.raw)
425    }
426
427    pub(crate) fn from_datum(datum: &Datum) -> Result<Self, OperationError> {
428        let fields = node_fields(datum, RECEIPT_TAG, 2)?;
429        let dispatch = DispatchId(id_from_datum(field(fields, "dispatch")?)?);
430        let raw = field(fields, "raw")?.clone();
431        let value = Self::new(dispatch, raw)?;
432        if value.canonical_datum() != *datum {
433            return Err(OperationError::NonCanonical("performer receipt"));
434        }
435        Ok(value)
436    }
437}
438
439/// The three durable states delivered by the operation-log phase.
440#[derive(Clone, Debug, PartialEq, Eq)]
441pub enum DurableOperationState {
442    /// Canonical intent and its separate grant are durable.
443    IntentPersisted {
444        /// Exact semantic intent identity.
445        intent: OperationIntentId,
446    },
447    /// Dispatch is durable; recovery must not call the performer again.
448    Dispatched {
449        /// Exact semantic intent identity.
450        intent: OperationIntentId,
451        /// Exact dispatch identity.
452        dispatch: DispatchId,
453    },
454    /// The raw performer acknowledgement is durable.
455    ReceiptPersisted {
456        /// Exact dispatch identity.
457        dispatch: DispatchId,
458        /// Exact raw receipt identity.
459        receipt: PerformerReceiptId,
460    },
461}
462
463/// Result of one injected performer call.
464#[derive(Clone, Debug, PartialEq, Eq)]
465pub enum PerformerResponse {
466    /// The performer returned a canonical raw acknowledgement.
467    Receipt(Datum),
468    /// Performance may have occurred, but no acknowledgement arrived.
469    AcknowledgementMissing,
470}
471
472/// Effect boundary used only after a matching dispatch is durable.
473pub trait OperationPerformer {
474    /// Performs one dispatch and returns an uninterpreted response.
475    fn perform(&mut self, dispatch: &OperationDispatch) -> PerformerResponse;
476}