use std::fmt;
use sim_kernel::{ContentId, Datum, Symbol};
use crate::{
OperationError, OperationId,
lifecycle_wire::{
fenced_dispatch_datum, lease_datum, lifecycle_receipt_datum, observation_base_datum,
observation_datum, optional_id, optional_id_datum, response_datum, response_from_datum,
u64_datum,
},
operation_wire::{content_id, field, id_datum, id_from_datum, node, node_fields, u64_field},
};
pub(super) const LEASE_TAG: &str = "bounded-lease-v1";
pub(super) const DISPATCH_TAG: &str = "fenced-dispatch-v1";
pub(super) const RECEIPT_TAG: &str = "lifecycle-receipt-v1";
pub(super) const OBSERVATION_TAG: &str = "postcondition-observation-v1";
pub(super) const OUTCOME_TAG: &str = "outcome-v1";
macro_rules! semantic_id {
($name:ident, $doc:literal) => {
#[doc = $doc]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct $name(pub(super) ContentId);
impl $name {
pub const fn content_id(&self) -> &ContentId {
&self.0
}
}
impl fmt::Display for $name {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
crate::operation_wire::render_id(&self.0, formatter)
}
}
};
}
semantic_id!(
OperationLeaseId,
"Identity of one bounded, fenced operation lease."
);
semantic_id!(
FencedDispatchId,
"Identity of one lease-bound durable dispatch."
);
semantic_id!(
LifecycleReceiptId,
"Identity of one raw lifecycle performer receipt."
);
semantic_id!(
OperationObservationId,
"Identity of one independent postcondition observation."
);
semantic_id!(
EvidenceSetId,
"Identity of the evidence carried by one observation."
);
semantic_id!(
OperationOutcomeId,
"Identity of one durable reconciliation outcome."
);
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct LeaseWindow {
pub(super) holder: Datum,
pub(super) acquired_at: u64,
pub(super) expires_at: u64,
}
impl LeaseWindow {
pub fn new(holder: Datum, acquired_at: u64, expires_at: u64) -> Result<Self, OperationError> {
if expires_at <= acquired_at {
return Err(OperationError::InvalidLease);
}
Ok(Self {
holder,
acquired_at,
expires_at,
})
}
pub const fn holder(&self) -> &Datum {
&self.holder
}
pub const fn acquired_at(&self) -> u64 {
self.acquired_at
}
pub const fn expires_at(&self) -> u64 {
self.expires_at
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct OperationLease {
pub(super) id: OperationLeaseId,
pub(super) operation: OperationId,
pub(super) holder: Datum,
pub(super) fence: u64,
pub(super) acquired_at: u64,
pub(super) expires_at: u64,
}
impl OperationLease {
pub(super) fn new(
operation: OperationId,
holder: Datum,
fence: u64,
acquired_at: u64,
expires_at: u64,
) -> Result<Self, OperationError> {
if expires_at <= acquired_at {
return Err(OperationError::InvalidLease);
}
let datum = lease_datum(&operation, &holder, fence, acquired_at, expires_at);
Ok(Self {
id: OperationLeaseId(content_id(&datum)?),
operation,
holder,
fence,
acquired_at,
expires_at,
})
}
pub const fn id(&self) -> &OperationLeaseId {
&self.id
}
pub const fn operation(&self) -> &OperationId {
&self.operation
}
pub const fn holder(&self) -> &Datum {
&self.holder
}
pub const fn fence(&self) -> u64 {
self.fence
}
pub const fn acquired_at(&self) -> u64 {
self.acquired_at
}
pub const fn expires_at(&self) -> u64 {
self.expires_at
}
pub const fn is_live_at(&self, now: u64) -> bool {
self.acquired_at <= now && now < self.expires_at
}
pub fn canonical_datum(&self) -> Datum {
lease_datum(
&self.operation,
&self.holder,
self.fence,
self.acquired_at,
self.expires_at,
)
}
pub(super) fn from_datum(datum: &Datum) -> Result<Self, OperationError> {
let fields = node_fields(datum, LEASE_TAG, 5)?;
let operation = OperationId(id_from_datum(field(fields, "operation")?)?);
let holder = field(fields, "holder")?.clone();
let value = Self::new(
operation,
holder,
u64_field(fields, "fence")?,
u64_field(fields, "acquired-at")?,
u64_field(fields, "expires-at")?,
)?;
if value.canonical_datum() != *datum {
return Err(OperationError::NonCanonical("operation lease"));
}
Ok(value)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct FencedDispatch {
pub(super) id: FencedDispatchId,
pub(super) operation: OperationId,
pub(super) grant: crate::OperationGrantId,
pub(super) attempt: crate::OperationAttemptId,
pub(super) lease: OperationLeaseId,
pub(super) performer: Datum,
}
impl FencedDispatch {
pub(super) fn new(
operation: OperationId,
grant: crate::OperationGrantId,
attempt: crate::OperationAttemptId,
lease: OperationLeaseId,
performer: Datum,
) -> Result<Self, OperationError> {
let datum = fenced_dispatch_datum(&operation, &grant, &attempt, &lease, &performer);
Ok(Self {
id: FencedDispatchId(content_id(&datum)?),
operation,
grant,
attempt,
lease,
performer,
})
}
pub const fn id(&self) -> &FencedDispatchId {
&self.id
}
pub const fn operation(&self) -> &OperationId {
&self.operation
}
pub const fn grant(&self) -> &crate::OperationGrantId {
&self.grant
}
pub const fn attempt(&self) -> &crate::OperationAttemptId {
&self.attempt
}
pub const fn lease(&self) -> &OperationLeaseId {
&self.lease
}
pub const fn performer(&self) -> &Datum {
&self.performer
}
pub fn canonical_datum(&self) -> Datum {
fenced_dispatch_datum(
&self.operation,
&self.grant,
&self.attempt,
&self.lease,
&self.performer,
)
}
pub(super) fn from_datum(datum: &Datum) -> Result<Self, OperationError> {
let fields = node_fields(datum, DISPATCH_TAG, 5)?;
let value = Self::new(
OperationId(id_from_datum(field(fields, "operation")?)?),
crate::OperationGrantId(id_from_datum(field(fields, "grant")?)?),
crate::OperationAttemptId(id_from_datum(field(fields, "attempt")?)?),
OperationLeaseId(id_from_datum(field(fields, "lease")?)?),
field(fields, "performer")?.clone(),
)?;
if value.canonical_datum() != *datum {
return Err(OperationError::NonCanonical("fenced dispatch"));
}
Ok(value)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct LifecycleReceipt {
pub(super) id: LifecycleReceiptId,
pub(super) dispatch: FencedDispatchId,
pub(super) raw: Datum,
}
impl LifecycleReceipt {
pub(super) fn new(dispatch: FencedDispatchId, raw: Datum) -> Result<Self, OperationError> {
let datum = lifecycle_receipt_datum(&dispatch, &raw);
Ok(Self {
id: LifecycleReceiptId(content_id(&datum)?),
dispatch,
raw,
})
}
pub const fn id(&self) -> &LifecycleReceiptId {
&self.id
}
pub const fn dispatch(&self) -> &FencedDispatchId {
&self.dispatch
}
pub const fn raw(&self) -> &Datum {
&self.raw
}
pub fn canonical_datum(&self) -> Datum {
lifecycle_receipt_datum(&self.dispatch, &self.raw)
}
pub(super) fn from_datum(datum: &Datum) -> Result<Self, OperationError> {
let fields = node_fields(datum, RECEIPT_TAG, 2)?;
let value = Self::new(
FencedDispatchId(id_from_datum(field(fields, "dispatch")?)?),
field(fields, "raw")?.clone(),
)?;
if value.canonical_datum() != *datum {
return Err(OperationError::NonCanonical("lifecycle receipt"));
}
Ok(value)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum OperationStep {
IntentPersisted,
LeaseAcquired,
DispatchPersisted,
ReceiptPersisted,
ObservationPersisted,
OutcomePersisted,
}
impl OperationStep {
pub(super) fn datum(self) -> Datum {
Datum::Symbol(Symbol::qualified(
"operation-step",
match self {
Self::IntentPersisted => "intent-persisted",
Self::LeaseAcquired => "lease-acquired",
Self::DispatchPersisted => "dispatch-persisted",
Self::ReceiptPersisted => "receipt-persisted",
Self::ObservationPersisted => "observation-persisted",
Self::OutcomePersisted => "outcome-persisted",
},
))
}
pub(super) fn from_datum(datum: &Datum) -> Result<Self, OperationError> {
let Datum::Symbol(value) = datum else {
return Err(OperationError::NonCanonical("operation step"));
};
for step in [
Self::IntentPersisted,
Self::LeaseAcquired,
Self::DispatchPersisted,
Self::ReceiptPersisted,
Self::ObservationPersisted,
Self::OutcomePersisted,
] {
if step.datum() == Datum::Symbol(value.clone()) {
return Ok(step);
}
}
Err(OperationError::NonCanonical("operation step"))
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PostconditionRequest {
pub(super) operation: OperationId,
pub(super) target: Datum,
pub(super) expected: Datum,
pub(super) dispatch: Option<FencedDispatchId>,
pub(super) receipt: Option<LifecycleReceiptId>,
pub(super) last_durable_step: OperationStep,
pub(super) observed_at: u64,
}
impl PostconditionRequest {
pub const fn operation(&self) -> &OperationId {
&self.operation
}
pub const fn target(&self) -> &Datum {
&self.target
}
pub const fn expected(&self) -> &Datum {
&self.expected
}
pub const fn dispatch(&self) -> Option<&FencedDispatchId> {
self.dispatch.as_ref()
}
pub const fn receipt(&self) -> Option<&LifecycleReceiptId> {
self.receipt.as_ref()
}
pub const fn last_durable_step(&self) -> OperationStep {
self.last_durable_step
}
pub const fn observed_at(&self) -> u64 {
self.observed_at
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum PostconditionResponse {
Satisfied {
observed: Datum,
evidence: Datum,
},
NotSatisfied {
observed: Datum,
evidence: Datum,
},
Unavailable {
reason: Datum,
},
Disputed {
first: Datum,
second: Datum,
},
}
pub trait PostconditionObserver {
fn identity(&self) -> Datum;
fn observe(&mut self, request: &PostconditionRequest) -> PostconditionResponse;
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum LifecyclePerformerResponse {
Receipt(Datum),
AcknowledgementMissing,
}
pub trait LifecyclePerformer {
fn identity(&self) -> Datum;
fn perform(&mut self, dispatch: &FencedDispatch) -> LifecyclePerformerResponse;
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct OperationObservation {
pub(super) id: OperationObservationId,
pub(super) operation: OperationId,
pub(super) observer: Datum,
pub(super) response: PostconditionResponse,
pub(super) dispatch: Option<FencedDispatchId>,
pub(super) receipt: Option<LifecycleReceiptId>,
pub(super) last_durable_step: OperationStep,
pub(super) observed_at: u64,
pub(super) evidence: EvidenceSetId,
}
impl OperationObservation {
pub(super) fn new(
request: &PostconditionRequest,
observer: Datum,
response: PostconditionResponse,
) -> Result<Self, OperationError> {
let base = observation_base_datum(request, &observer, &response);
let evidence = EvidenceSetId(content_id(&node(
"evidence-set-v1",
vec![("observation", base.clone())],
))?);
let datum = observation_datum(request, &observer, &response, &evidence);
Ok(Self {
id: OperationObservationId(content_id(&datum)?),
operation: request.operation.clone(),
observer,
response,
dispatch: request.dispatch.clone(),
receipt: request.receipt.clone(),
last_durable_step: request.last_durable_step,
observed_at: request.observed_at,
evidence,
})
}
pub const fn id(&self) -> &OperationObservationId {
&self.id
}
pub const fn operation(&self) -> &OperationId {
&self.operation
}
pub const fn observer(&self) -> &Datum {
&self.observer
}
pub const fn response(&self) -> &PostconditionResponse {
&self.response
}
pub const fn dispatch(&self) -> Option<&FencedDispatchId> {
self.dispatch.as_ref()
}
pub const fn receipt(&self) -> Option<&LifecycleReceiptId> {
self.receipt.as_ref()
}
pub const fn last_durable_step(&self) -> OperationStep {
self.last_durable_step
}
pub const fn observed_at(&self) -> u64 {
self.observed_at
}
pub const fn evidence(&self) -> &EvidenceSetId {
&self.evidence
}
pub fn canonical_datum(&self) -> Datum {
self.stored_datum()
}
pub(super) fn from_datum(datum: &Datum) -> Result<Self, OperationError> {
let fields = node_fields(datum, OBSERVATION_TAG, 8)?;
let operation = OperationId(id_from_datum(field(fields, "operation")?)?);
let observer = field(fields, "observer")?.clone();
let response = response_from_datum(field(fields, "response")?)?;
let dispatch = optional_id(field(fields, "dispatch")?)?.map(FencedDispatchId);
let last_durable_step = OperationStep::from_datum(field(fields, "last-durable-step")?)?;
let observed_at = u64_field(fields, "observed-at")?;
let receipt = optional_id(field(fields, "receipt")?)?.map(LifecycleReceiptId);
let evidence = EvidenceSetId(id_from_datum(field(fields, "evidence")?)?);
let evidence_input = node(
"observation-evidence-v1",
vec![
("operation", id_datum(operation.content_id())),
("observer", observer.clone()),
("response", response_datum(&response)),
(
"dispatch",
optional_id_datum(dispatch.as_ref().map(FencedDispatchId::content_id)),
),
(
"receipt",
optional_id_datum(receipt.as_ref().map(LifecycleReceiptId::content_id)),
),
("last-durable-step", last_durable_step.datum()),
("observed-at", u64_datum(observed_at)),
],
);
let expected_evidence = EvidenceSetId(content_id(&node(
"evidence-set-v1",
vec![("observation", evidence_input)],
))?);
if evidence != expected_evidence {
return Err(OperationError::NonCanonical("observation evidence set"));
}
let id = OperationObservationId(content_id(datum)?);
let value = Self {
id,
operation,
observer,
response,
dispatch,
receipt,
last_durable_step,
observed_at,
evidence,
};
if value.stored_datum() != *datum {
return Err(OperationError::NonCanonical("operation observation"));
}
Ok(value)
}
pub(super) fn stored_datum(&self) -> Datum {
node(
OBSERVATION_TAG,
vec![
("operation", id_datum(self.operation.content_id())),
("observer", self.observer.clone()),
("response", response_datum(&self.response)),
(
"dispatch",
optional_id_datum(self.dispatch.as_ref().map(FencedDispatchId::content_id)),
),
(
"receipt",
optional_id_datum(self.receipt.as_ref().map(LifecycleReceiptId::content_id)),
),
("last-durable-step", self.last_durable_step.datum()),
("observed-at", u64_datum(self.observed_at)),
("evidence", id_datum(self.evidence.content_id())),
],
)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum OperationOutcome {
AlreadyTrue {
evidence: EvidenceSetId,
},
Verified {
evidence: EvidenceSetId,
},
Diverged {
observed: Datum,
expected: Datum,
},
Uncertain {
last_durable_step: OperationStep,
},
}