1use std::fmt;
4
5use sim_kernel::{ContentId, Datum, Symbol};
6
7use crate::{
8 OperationError, OperationId,
9 lifecycle_wire::{
10 fenced_dispatch_datum, lease_datum, lifecycle_receipt_datum, observation_base_datum,
11 observation_datum, optional_id, optional_id_datum, response_datum, response_from_datum,
12 u64_datum,
13 },
14 operation_wire::{content_id, field, id_datum, id_from_datum, node, node_fields, u64_field},
15};
16
17pub(super) const LEASE_TAG: &str = "bounded-lease-v1";
18pub(super) const DISPATCH_TAG: &str = "fenced-dispatch-v1";
19pub(super) const RECEIPT_TAG: &str = "lifecycle-receipt-v1";
20pub(super) const OBSERVATION_TAG: &str = "postcondition-observation-v1";
21pub(super) const OUTCOME_TAG: &str = "outcome-v1";
22
23macro_rules! semantic_id {
24 ($name:ident, $doc:literal) => {
25 #[doc = $doc]
26 #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
27 pub struct $name(pub(super) ContentId);
28
29 impl $name {
30 pub const fn content_id(&self) -> &ContentId {
32 &self.0
33 }
34 }
35
36 impl fmt::Display for $name {
37 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
38 crate::operation_wire::render_id(&self.0, formatter)
39 }
40 }
41 };
42}
43
44semantic_id!(
45 OperationLeaseId,
46 "Identity of one bounded, fenced operation lease."
47);
48semantic_id!(
49 FencedDispatchId,
50 "Identity of one lease-bound durable dispatch."
51);
52semantic_id!(
53 LifecycleReceiptId,
54 "Identity of one raw lifecycle performer receipt."
55);
56semantic_id!(
57 OperationObservationId,
58 "Identity of one independent postcondition observation."
59);
60semantic_id!(
61 EvidenceSetId,
62 "Identity of the evidence carried by one observation."
63);
64semantic_id!(
65 OperationOutcomeId,
66 "Identity of one durable reconciliation outcome."
67);
68
69#[derive(Clone, Debug, PartialEq, Eq)]
71pub struct LeaseWindow {
72 pub(super) holder: Datum,
73 pub(super) acquired_at: u64,
74 pub(super) expires_at: u64,
75}
76
77impl LeaseWindow {
78 pub fn new(holder: Datum, acquired_at: u64, expires_at: u64) -> Result<Self, OperationError> {
80 if expires_at <= acquired_at {
81 return Err(OperationError::InvalidLease);
82 }
83 Ok(Self {
84 holder,
85 acquired_at,
86 expires_at,
87 })
88 }
89
90 pub const fn holder(&self) -> &Datum {
92 &self.holder
93 }
94
95 pub const fn acquired_at(&self) -> u64 {
97 self.acquired_at
98 }
99
100 pub const fn expires_at(&self) -> u64 {
102 self.expires_at
103 }
104}
105
106#[derive(Clone, Debug, PartialEq, Eq)]
108pub struct OperationLease {
109 pub(super) id: OperationLeaseId,
110 pub(super) operation: OperationId,
111 pub(super) holder: Datum,
112 pub(super) fence: u64,
113 pub(super) acquired_at: u64,
114 pub(super) expires_at: u64,
115}
116
117impl OperationLease {
118 pub(super) fn new(
119 operation: OperationId,
120 holder: Datum,
121 fence: u64,
122 acquired_at: u64,
123 expires_at: u64,
124 ) -> Result<Self, OperationError> {
125 if expires_at <= acquired_at {
126 return Err(OperationError::InvalidLease);
127 }
128 let datum = lease_datum(&operation, &holder, fence, acquired_at, expires_at);
129 Ok(Self {
130 id: OperationLeaseId(content_id(&datum)?),
131 operation,
132 holder,
133 fence,
134 acquired_at,
135 expires_at,
136 })
137 }
138
139 pub const fn id(&self) -> &OperationLeaseId {
141 &self.id
142 }
143 pub const fn operation(&self) -> &OperationId {
145 &self.operation
146 }
147 pub const fn holder(&self) -> &Datum {
149 &self.holder
150 }
151 pub const fn fence(&self) -> u64 {
153 self.fence
154 }
155 pub const fn acquired_at(&self) -> u64 {
157 self.acquired_at
158 }
159 pub const fn expires_at(&self) -> u64 {
161 self.expires_at
162 }
163 pub const fn is_live_at(&self, now: u64) -> bool {
165 self.acquired_at <= now && now < self.expires_at
166 }
167 pub fn canonical_datum(&self) -> Datum {
169 lease_datum(
170 &self.operation,
171 &self.holder,
172 self.fence,
173 self.acquired_at,
174 self.expires_at,
175 )
176 }
177 pub(super) fn from_datum(datum: &Datum) -> Result<Self, OperationError> {
178 let fields = node_fields(datum, LEASE_TAG, 5)?;
179 let operation = OperationId(id_from_datum(field(fields, "operation")?)?);
180 let holder = field(fields, "holder")?.clone();
181 let value = Self::new(
182 operation,
183 holder,
184 u64_field(fields, "fence")?,
185 u64_field(fields, "acquired-at")?,
186 u64_field(fields, "expires-at")?,
187 )?;
188 if value.canonical_datum() != *datum {
189 return Err(OperationError::NonCanonical("operation lease"));
190 }
191 Ok(value)
192 }
193}
194
195#[derive(Clone, Debug, PartialEq, Eq)]
197pub struct FencedDispatch {
198 pub(super) id: FencedDispatchId,
199 pub(super) operation: OperationId,
200 pub(super) grant: crate::OperationGrantId,
201 pub(super) attempt: crate::OperationAttemptId,
202 pub(super) lease: OperationLeaseId,
203 pub(super) performer: Datum,
204}
205
206impl FencedDispatch {
207 pub(super) fn new(
208 operation: OperationId,
209 grant: crate::OperationGrantId,
210 attempt: crate::OperationAttemptId,
211 lease: OperationLeaseId,
212 performer: Datum,
213 ) -> Result<Self, OperationError> {
214 let datum = fenced_dispatch_datum(&operation, &grant, &attempt, &lease, &performer);
215 Ok(Self {
216 id: FencedDispatchId(content_id(&datum)?),
217 operation,
218 grant,
219 attempt,
220 lease,
221 performer,
222 })
223 }
224 pub const fn id(&self) -> &FencedDispatchId {
226 &self.id
227 }
228 pub const fn operation(&self) -> &OperationId {
230 &self.operation
231 }
232 pub const fn grant(&self) -> &crate::OperationGrantId {
234 &self.grant
235 }
236 pub const fn attempt(&self) -> &crate::OperationAttemptId {
238 &self.attempt
239 }
240 pub const fn lease(&self) -> &OperationLeaseId {
242 &self.lease
243 }
244 pub const fn performer(&self) -> &Datum {
246 &self.performer
247 }
248 pub fn canonical_datum(&self) -> Datum {
250 fenced_dispatch_datum(
251 &self.operation,
252 &self.grant,
253 &self.attempt,
254 &self.lease,
255 &self.performer,
256 )
257 }
258 pub(super) fn from_datum(datum: &Datum) -> Result<Self, OperationError> {
259 let fields = node_fields(datum, DISPATCH_TAG, 5)?;
260 let value = Self::new(
261 OperationId(id_from_datum(field(fields, "operation")?)?),
262 crate::OperationGrantId(id_from_datum(field(fields, "grant")?)?),
263 crate::OperationAttemptId(id_from_datum(field(fields, "attempt")?)?),
264 OperationLeaseId(id_from_datum(field(fields, "lease")?)?),
265 field(fields, "performer")?.clone(),
266 )?;
267 if value.canonical_datum() != *datum {
268 return Err(OperationError::NonCanonical("fenced dispatch"));
269 }
270 Ok(value)
271 }
272}
273
274#[derive(Clone, Debug, PartialEq, Eq)]
276pub struct LifecycleReceipt {
277 pub(super) id: LifecycleReceiptId,
278 pub(super) dispatch: FencedDispatchId,
279 pub(super) raw: Datum,
280}
281
282impl LifecycleReceipt {
283 pub(super) fn new(dispatch: FencedDispatchId, raw: Datum) -> Result<Self, OperationError> {
284 let datum = lifecycle_receipt_datum(&dispatch, &raw);
285 Ok(Self {
286 id: LifecycleReceiptId(content_id(&datum)?),
287 dispatch,
288 raw,
289 })
290 }
291 pub const fn id(&self) -> &LifecycleReceiptId {
293 &self.id
294 }
295 pub const fn dispatch(&self) -> &FencedDispatchId {
297 &self.dispatch
298 }
299 pub const fn raw(&self) -> &Datum {
301 &self.raw
302 }
303 pub fn canonical_datum(&self) -> Datum {
305 lifecycle_receipt_datum(&self.dispatch, &self.raw)
306 }
307 pub(super) fn from_datum(datum: &Datum) -> Result<Self, OperationError> {
308 let fields = node_fields(datum, RECEIPT_TAG, 2)?;
309 let value = Self::new(
310 FencedDispatchId(id_from_datum(field(fields, "dispatch")?)?),
311 field(fields, "raw")?.clone(),
312 )?;
313 if value.canonical_datum() != *datum {
314 return Err(OperationError::NonCanonical("lifecycle receipt"));
315 }
316 Ok(value)
317 }
318}
319
320#[derive(Clone, Copy, Debug, PartialEq, Eq)]
322pub enum OperationStep {
323 IntentPersisted,
325 LeaseAcquired,
327 DispatchPersisted,
329 ReceiptPersisted,
331 ObservationPersisted,
333 OutcomePersisted,
335}
336
337impl OperationStep {
338 pub(super) fn datum(self) -> Datum {
339 Datum::Symbol(Symbol::qualified(
340 "operation-step",
341 match self {
342 Self::IntentPersisted => "intent-persisted",
343 Self::LeaseAcquired => "lease-acquired",
344 Self::DispatchPersisted => "dispatch-persisted",
345 Self::ReceiptPersisted => "receipt-persisted",
346 Self::ObservationPersisted => "observation-persisted",
347 Self::OutcomePersisted => "outcome-persisted",
348 },
349 ))
350 }
351 pub(super) fn from_datum(datum: &Datum) -> Result<Self, OperationError> {
352 let Datum::Symbol(value) = datum else {
353 return Err(OperationError::NonCanonical("operation step"));
354 };
355 for step in [
356 Self::IntentPersisted,
357 Self::LeaseAcquired,
358 Self::DispatchPersisted,
359 Self::ReceiptPersisted,
360 Self::ObservationPersisted,
361 Self::OutcomePersisted,
362 ] {
363 if step.datum() == Datum::Symbol(value.clone()) {
364 return Ok(step);
365 }
366 }
367 Err(OperationError::NonCanonical("operation step"))
368 }
369}
370
371#[derive(Clone, Debug, PartialEq, Eq)]
373pub struct PostconditionRequest {
374 pub(super) operation: OperationId,
375 pub(super) target: Datum,
376 pub(super) expected: Datum,
377 pub(super) dispatch: Option<FencedDispatchId>,
378 pub(super) receipt: Option<LifecycleReceiptId>,
379 pub(super) last_durable_step: OperationStep,
380 pub(super) observed_at: u64,
381}
382
383impl PostconditionRequest {
384 pub const fn operation(&self) -> &OperationId {
386 &self.operation
387 }
388 pub const fn target(&self) -> &Datum {
390 &self.target
391 }
392 pub const fn expected(&self) -> &Datum {
394 &self.expected
395 }
396 pub const fn dispatch(&self) -> Option<&FencedDispatchId> {
398 self.dispatch.as_ref()
399 }
400 pub const fn receipt(&self) -> Option<&LifecycleReceiptId> {
402 self.receipt.as_ref()
403 }
404 pub const fn last_durable_step(&self) -> OperationStep {
406 self.last_durable_step
407 }
408 pub const fn observed_at(&self) -> u64 {
410 self.observed_at
411 }
412}
413
414#[derive(Clone, Debug, PartialEq, Eq)]
416pub enum PostconditionResponse {
417 Satisfied {
419 observed: Datum,
421 evidence: Datum,
423 },
424 NotSatisfied {
426 observed: Datum,
428 evidence: Datum,
430 },
431 Unavailable {
433 reason: Datum,
435 },
436 Disputed {
438 first: Datum,
440 second: Datum,
442 },
443}
444
445pub trait PostconditionObserver {
447 fn identity(&self) -> Datum;
449 fn observe(&mut self, request: &PostconditionRequest) -> PostconditionResponse;
451}
452
453#[derive(Clone, Debug, PartialEq, Eq)]
455pub enum LifecyclePerformerResponse {
456 Receipt(Datum),
458 AcknowledgementMissing,
460}
461
462pub trait LifecyclePerformer {
464 fn identity(&self) -> Datum;
466 fn perform(&mut self, dispatch: &FencedDispatch) -> LifecyclePerformerResponse;
468}
469
470#[derive(Clone, Debug, PartialEq, Eq)]
472pub struct OperationObservation {
473 pub(super) id: OperationObservationId,
474 pub(super) operation: OperationId,
475 pub(super) observer: Datum,
476 pub(super) response: PostconditionResponse,
477 pub(super) dispatch: Option<FencedDispatchId>,
478 pub(super) receipt: Option<LifecycleReceiptId>,
479 pub(super) last_durable_step: OperationStep,
480 pub(super) observed_at: u64,
481 pub(super) evidence: EvidenceSetId,
482}
483
484impl OperationObservation {
485 pub(super) fn new(
486 request: &PostconditionRequest,
487 observer: Datum,
488 response: PostconditionResponse,
489 ) -> Result<Self, OperationError> {
490 let base = observation_base_datum(request, &observer, &response);
491 let evidence = EvidenceSetId(content_id(&node(
492 "evidence-set-v1",
493 vec![("observation", base.clone())],
494 ))?);
495 let datum = observation_datum(request, &observer, &response, &evidence);
496 Ok(Self {
497 id: OperationObservationId(content_id(&datum)?),
498 operation: request.operation.clone(),
499 observer,
500 response,
501 dispatch: request.dispatch.clone(),
502 receipt: request.receipt.clone(),
503 last_durable_step: request.last_durable_step,
504 observed_at: request.observed_at,
505 evidence,
506 })
507 }
508 pub const fn id(&self) -> &OperationObservationId {
510 &self.id
511 }
512 pub const fn operation(&self) -> &OperationId {
514 &self.operation
515 }
516 pub const fn observer(&self) -> &Datum {
518 &self.observer
519 }
520 pub const fn response(&self) -> &PostconditionResponse {
522 &self.response
523 }
524 pub const fn dispatch(&self) -> Option<&FencedDispatchId> {
526 self.dispatch.as_ref()
527 }
528 pub const fn receipt(&self) -> Option<&LifecycleReceiptId> {
530 self.receipt.as_ref()
531 }
532 pub const fn last_durable_step(&self) -> OperationStep {
534 self.last_durable_step
535 }
536 pub const fn observed_at(&self) -> u64 {
538 self.observed_at
539 }
540 pub const fn evidence(&self) -> &EvidenceSetId {
542 &self.evidence
543 }
544 pub fn canonical_datum(&self) -> Datum {
546 self.stored_datum()
547 }
548 pub(super) fn from_datum(datum: &Datum) -> Result<Self, OperationError> {
549 let fields = node_fields(datum, OBSERVATION_TAG, 8)?;
550 let operation = OperationId(id_from_datum(field(fields, "operation")?)?);
551 let observer = field(fields, "observer")?.clone();
552 let response = response_from_datum(field(fields, "response")?)?;
553 let dispatch = optional_id(field(fields, "dispatch")?)?.map(FencedDispatchId);
554 let last_durable_step = OperationStep::from_datum(field(fields, "last-durable-step")?)?;
555 let observed_at = u64_field(fields, "observed-at")?;
556 let receipt = optional_id(field(fields, "receipt")?)?.map(LifecycleReceiptId);
557 let evidence = EvidenceSetId(id_from_datum(field(fields, "evidence")?)?);
558 let evidence_input = node(
559 "observation-evidence-v1",
560 vec![
561 ("operation", id_datum(operation.content_id())),
562 ("observer", observer.clone()),
563 ("response", response_datum(&response)),
564 (
565 "dispatch",
566 optional_id_datum(dispatch.as_ref().map(FencedDispatchId::content_id)),
567 ),
568 (
569 "receipt",
570 optional_id_datum(receipt.as_ref().map(LifecycleReceiptId::content_id)),
571 ),
572 ("last-durable-step", last_durable_step.datum()),
573 ("observed-at", u64_datum(observed_at)),
574 ],
575 );
576 let expected_evidence = EvidenceSetId(content_id(&node(
577 "evidence-set-v1",
578 vec![("observation", evidence_input)],
579 ))?);
580 if evidence != expected_evidence {
581 return Err(OperationError::NonCanonical("observation evidence set"));
582 }
583 let id = OperationObservationId(content_id(datum)?);
584 let value = Self {
585 id,
586 operation,
587 observer,
588 response,
589 dispatch,
590 receipt,
591 last_durable_step,
592 observed_at,
593 evidence,
594 };
595 if value.stored_datum() != *datum {
596 return Err(OperationError::NonCanonical("operation observation"));
597 }
598 Ok(value)
599 }
600 pub(super) fn stored_datum(&self) -> Datum {
601 node(
602 OBSERVATION_TAG,
603 vec![
604 ("operation", id_datum(self.operation.content_id())),
605 ("observer", self.observer.clone()),
606 ("response", response_datum(&self.response)),
607 (
608 "dispatch",
609 optional_id_datum(self.dispatch.as_ref().map(FencedDispatchId::content_id)),
610 ),
611 (
612 "receipt",
613 optional_id_datum(self.receipt.as_ref().map(LifecycleReceiptId::content_id)),
614 ),
615 ("last-durable-step", self.last_durable_step.datum()),
616 ("observed-at", u64_datum(self.observed_at)),
617 ("evidence", id_datum(self.evidence.content_id())),
618 ],
619 )
620 }
621}
622
623#[derive(Clone, Debug, PartialEq, Eq)]
625pub enum OperationOutcome {
626 AlreadyTrue {
628 evidence: EvidenceSetId,
630 },
631 Verified {
633 evidence: EvidenceSetId,
635 },
636 Diverged {
638 observed: Datum,
640 expected: Datum,
642 },
643 Uncertain {
645 last_durable_step: OperationStep,
647 },
648}