Skip to main content

liminal_protocol/lifecycle/
binding_fate.rs

1use alloc::boxed::Box;
2
3use crate::wire::{BindingEpoch, ConversationId, DeliverySeq, ParticipantId};
4
5#[cfg(test)]
6use super::FencedAttachCommit;
7use super::{
8    CommittedDiedTerminal, Event, OrdinaryBindingFate, RecoveredBindingFate, SealedBindingFateToken,
9};
10
11/// Closed persistence shape carried by one sealed binding-fate token.
12///
13/// This projection exposes only the durable intent fields. It neither exposes
14/// nor duplicates the move-only authority consumed by protocol measurement.
15#[derive(Clone, Copy, Debug, PartialEq, Eq)]
16pub enum SealedBindingFateIntent {
17    /// A no-marker attachment must complete through an exact Died terminal.
18    Ordinary,
19    /// A fenced attachment retains the exact prior epoch and accepted marker.
20    Recovered {
21        /// Binding epoch whose marker authorized the fenced replacement.
22        prior_binding_epoch: BindingEpoch,
23        /// Exact accepted marker delivery sequence.
24        marker_delivery_seq: DeliverySeq,
25    },
26}
27
28/// Protocol-private measurement inputs carried by one sealed fate token.
29#[derive(Clone, Copy, Debug, PartialEq, Eq)]
30pub(in crate::lifecycle) struct BindingFateMeasurementContext {
31    pub(in crate::lifecycle) conversation_id: ConversationId,
32    pub(in crate::lifecycle) participant_id: ParticipantId,
33    pub(in crate::lifecycle) binding_epoch: BindingEpoch,
34    pub(in crate::lifecycle) cursor: DeliverySeq,
35}
36
37impl SealedBindingFateToken {
38    /// Reports whether this token carries recovered occurrence authority.
39    #[must_use]
40    pub const fn is_recovered(&self) -> bool {
41        self.recovered.is_some()
42    }
43
44    /// Returns the closed durable intent shape without surrendering authority.
45    #[must_use]
46    pub const fn intent(&self) -> Option<SealedBindingFateIntent> {
47        match (&self.ordinary, &self.recovered) {
48            (Some(_), None) => Some(SealedBindingFateIntent::Ordinary),
49            (None, Some(proof)) => Some(SealedBindingFateIntent::Recovered {
50                prior_binding_epoch: proof.prior_binding_epoch(),
51                marker_delivery_seq: proof.marker_delivery_seq(),
52            }),
53            (None, None) | (Some(_), Some(_)) => None,
54        }
55    }
56
57    #[cfg(test)]
58    pub(in crate::lifecycle) const fn from_recovered_for_test(
59        recovered: FencedAttachCommit,
60    ) -> Self {
61        let cursor = recovered.marker_delivery_seq();
62        Self {
63            ordinary: None,
64            recovered: Some(recovered),
65            cursor,
66        }
67    }
68
69    /// Replays one protocol-selected normal acknowledgement into this token.
70    pub(in crate::lifecycle) fn participant_ack_progressed(
71        mut self,
72        conversation_id: ConversationId,
73        participant_id: ParticipantId,
74        binding_epoch: BindingEpoch,
75        previous_cursor: DeliverySeq,
76        through_seq: DeliverySeq,
77    ) -> Result<Self, Box<Self>> {
78        if previous_cursor != self.cursor || through_seq <= previous_cursor {
79            return Err(Box::new(self));
80        }
81        match (self.ordinary.take(), self.recovered.as_ref()) {
82            (Some(authority), None) => match authority.participant_ack_progressed(
83                conversation_id,
84                participant_id,
85                binding_epoch,
86                previous_cursor,
87                through_seq,
88            ) {
89                Ok(authority) => self.ordinary = Some(authority),
90                Err(authority) => {
91                    self.ordinary = Some(authority);
92                    return Err(Box::new(self));
93                }
94            },
95            (None, Some(proof))
96                if proof.conversation_id() == conversation_id
97                    && proof.participant_id() == participant_id
98                    && proof.new_binding_epoch() == binding_epoch => {}
99            (ordinary, _) => {
100                self.ordinary = ordinary;
101                return Err(Box::new(self));
102            }
103        }
104        self.cursor = through_seq;
105        Ok(self)
106    }
107
108    /// Returns the exact protocol-owned identity whose floor must be measured.
109    pub(in crate::lifecycle) const fn measurement_context(
110        &self,
111    ) -> Option<BindingFateMeasurementContext> {
112        match (&self.ordinary, &self.recovered) {
113            (Some(authority), None) if authority.through_seq() == self.cursor => {
114                let binding = authority.binding();
115                Some(BindingFateMeasurementContext {
116                    conversation_id: binding.conversation_id,
117                    participant_id: binding.participant_id,
118                    binding_epoch: binding.binding_epoch,
119                    cursor: self.cursor,
120                })
121            }
122            (None, Some(proof)) => Some(BindingFateMeasurementContext {
123                conversation_id: proof.conversation_id(),
124                participant_id: proof.participant_id(),
125                binding_epoch: proof.new_binding_epoch(),
126                cursor: self.cursor,
127            }),
128            (None | Some(_), None) | (Some(_), Some(_)) => None,
129        }
130    }
131
132    /// Consumes ordinary authority and the exact committed Died terminal.
133    pub(in crate::lifecycle) fn ordinary_binding_fate(
134        mut self,
135        terminal: CommittedDiedTerminal,
136        resulting_floor: DeliverySeq,
137    ) -> Result<OrdinaryBindingFate, Box<Self>> {
138        if self.recovered.is_some() {
139            return Err(Box::new(self));
140        }
141        let Some(authority) = self.ordinary.take() else {
142            return Err(Box::new(self));
143        };
144        match authority.binding_fate(terminal, resulting_floor) {
145            Ok(fate) => Ok(fate),
146            Err(authority) => {
147                self.ordinary = Some(authority);
148                Err(Box::new(self))
149            }
150        }
151    }
152
153    /// Consumes recovered authority using a protocol-measured floor.
154    pub(in crate::lifecycle) fn recovered_binding_fate_measured(
155        self,
156        resulting_floor: DeliverySeq,
157    ) -> Result<RecoveredBindingFate, Box<Self>> {
158        let Some(context) = self.measurement_context() else {
159            return Err(Box::new(self));
160        };
161        self.recovered_binding_fate(Event::binding_fate_observed(
162            context.participant_id,
163            context.binding_epoch,
164            resulting_floor,
165        ))
166    }
167
168    pub(in crate::lifecycle) fn recovered_binding_fate(
169        mut self,
170        event: Event,
171    ) -> Result<RecoveredBindingFate, Box<Self>> {
172        if self.ordinary.is_some() {
173            return Err(Box::new(self));
174        }
175        let Some(proof) = self.recovered.take() else {
176            return Err(Box::new(self));
177        };
178        match proof.recovered_binding_fate(event) {
179            Ok(fate) => Ok(fate),
180            Err(proof) => {
181                self.recovered = Some(*proof);
182                Err(Box::new(self))
183            }
184        }
185    }
186}