Skip to main content

liminal_protocol/lifecycle/operations/
marker_proof.rs

1//! Total marker-proof selection after participant and binding authority.
2//!
3//! The selector is pure: it consumes an operation-specific request view and
4//! borrows durable marker facts. Only the success arm creates an opaque permit
5//! retaining the exact operation, expected marker, proof epoch, and typed
6//! marker-backed cursor provenance needed by a later `MarkerAck` or fenced attach
7//! commit.
8
9use crate::wire::{
10    AckNoOp, AttachMarkerProof, BindingEpoch, CredentialAttachRequest, DeliverySeq, MarkerAck,
11    MarkerAckEnvelope, MarkerAckProof, MarkerMismatch, MarkerMismatchBody, MarkerNotDelivered,
12    MarkerNotDeliveredReason, MarkerProofRequest, ParticipantId,
13};
14
15use super::super::edge::ParticipantCursorProgress;
16
17/// Operation-specific marker-proof input after common authority validation.
18#[derive(Clone, Debug, PartialEq, Eq)]
19pub enum MarkerProofInput {
20    /// Credential attach presenting an explicit marker.
21    CredentialAttach(AttachMarkerProof),
22    /// Explicit marker acknowledgement.
23    MarkerAck(MarkerAckProof),
24}
25
26impl MarkerProofInput {
27    /// Derives attach proof input only when the request presents `Some(marker)`.
28    #[must_use]
29    pub const fn credential_attach(request: &CredentialAttachRequest) -> Option<Self> {
30        let Some(requested_marker_delivery_seq) = request.accept_marker_delivery_seq else {
31            return None;
32        };
33        Some(Self::CredentialAttach(AttachMarkerProof {
34            conversation_id: request.conversation_id,
35            token: request.attach_attempt_token,
36            participant_id: request.participant_id,
37            capability_generation: request.capability_generation,
38            requested_marker_delivery_seq,
39        }))
40    }
41
42    /// Derives marker-ack proof input without changing its operation envelope.
43    #[must_use]
44    pub const fn marker_ack(request: &MarkerAck) -> Self {
45        Self::MarkerAck(MarkerAckProof {
46            conversation_id: request.conversation_id,
47            participant_id: request.participant_id,
48            capability_generation: request.capability_generation,
49            requested_marker_delivery_seq: request.marker_delivery_seq,
50        })
51    }
52
53    /// Returns the exact requested marker sequence.
54    #[must_use]
55    pub const fn requested_marker_delivery_seq(&self) -> DeliverySeq {
56        match self {
57            Self::CredentialAttach(request) => request.requested_marker_delivery_seq,
58            Self::MarkerAck(request) => request.requested_marker_delivery_seq,
59        }
60    }
61
62    /// Returns the permanent participant named by the operation.
63    #[must_use]
64    pub const fn participant_id(&self) -> ParticipantId {
65        match self {
66            Self::CredentialAttach(request) => request.participant_id,
67            Self::MarkerAck(request) => request.participant_id,
68        }
69    }
70
71    const fn capability_generation(&self) -> crate::wire::Generation {
72        match self {
73            Self::CredentialAttach(request) => request.capability_generation,
74            Self::MarkerAck(request) => request.capability_generation,
75        }
76    }
77
78    const fn is_marker_ack(&self) -> bool {
79        matches!(self, Self::MarkerAck(_))
80    }
81
82    const fn into_wire_request(self) -> MarkerProofRequest {
83        match self {
84            Self::CredentialAttach(request) => MarkerProofRequest::CredentialAttach(request),
85            Self::MarkerAck(request) => MarkerProofRequest::MarkerAck(request),
86        }
87    }
88
89    const fn marker_ack_envelope(&self) -> Option<MarkerAckEnvelope> {
90        let Self::MarkerAck(request) = self else {
91            return None;
92        };
93        Some(MarkerAckEnvelope {
94            conversation_id: request.conversation_id,
95            participant_id: request.participant_id,
96            capability_generation: request.capability_generation,
97            marker_delivery_seq: request.requested_marker_delivery_seq,
98        })
99    }
100}
101
102/// Durable participant facts read by the total marker-proof selector.
103#[derive(Clone, Copy, Debug, PartialEq, Eq)]
104pub struct MarkerProofState {
105    current_cursor: DeliverySeq,
106    accepted_marker_at_cursor: bool,
107    expected_marker_delivery_seq: Option<DeliverySeq>,
108    proof_binding_epoch: BindingEpoch,
109    delivered_to_proof_epoch: Option<ParticipantCursorProgress>,
110}
111
112impl MarkerProofState {
113    /// Creates one immutable snapshot of the durable marker proof facts.
114    ///
115    /// The final field requires a cursor-progress witness. A planned, still
116    /// undelivered [`crate::lifecycle::MarkerDelivery`] cannot be supplied as
117    /// proof by construction.
118    ///
119    /// ```compile_fail
120    /// use liminal_protocol::{
121    ///     lifecycle::{MarkerDelivery, MarkerProofState},
122    ///     wire::BindingEpoch,
123    /// };
124    ///
125    /// fn undelivered_is_not_proof(epoch: BindingEpoch, delivery: MarkerDelivery) {
126    ///     let _ = MarkerProofState::new(0, false, Some(1), epoch, Some(delivery));
127    /// }
128    /// ```
129    #[must_use]
130    pub const fn new(
131        current_cursor: DeliverySeq,
132        accepted_marker_at_cursor: bool,
133        expected_marker_delivery_seq: Option<DeliverySeq>,
134        proof_binding_epoch: BindingEpoch,
135        delivered_to_proof_epoch: Option<ParticipantCursorProgress>,
136    ) -> Self {
137        Self {
138            current_cursor,
139            accepted_marker_at_cursor,
140            expected_marker_delivery_seq,
141            proof_binding_epoch,
142            delivered_to_proof_epoch,
143        }
144    }
145
146    /// Returns the participant's durable cumulative cursor.
147    #[must_use]
148    pub const fn current_cursor(self) -> DeliverySeq {
149        self.current_cursor
150    }
151
152    /// Returns whether the record at the cursor is this participant's accepted marker.
153    #[must_use]
154    pub const fn accepted_marker_at_cursor(self) -> bool {
155        self.accepted_marker_at_cursor
156    }
157
158    /// Returns the currently expected marker anchor, if any.
159    #[must_use]
160    pub const fn expected_marker_delivery_seq(self) -> Option<DeliverySeq> {
161        self.expected_marker_delivery_seq
162    }
163
164    /// Returns the exact binding epoch against which delivery must be proven.
165    #[must_use]
166    pub const fn proof_binding_epoch(self) -> BindingEpoch {
167        self.proof_binding_epoch
168    }
169
170    /// Returns the marker-backed cursor witness proving exact delivery, if present.
171    #[must_use]
172    pub const fn delivered_to_proof_epoch(self) -> Option<ParticipantCursorProgress> {
173        self.delivered_to_proof_epoch
174    }
175}
176
177/// Opaque authority for an exact delivered marker and originating operation.
178#[derive(Clone, Debug, PartialEq, Eq)]
179pub struct MarkerProofPermit {
180    operation: MarkerProofInput,
181    expected_marker_delivery_seq: DeliverySeq,
182    proof_binding_epoch: BindingEpoch,
183    progress: ParticipantCursorProgress,
184}
185
186impl MarkerProofPermit {
187    /// Returns the exact operation authorized by this proof.
188    #[must_use]
189    pub const fn operation(&self) -> &MarkerProofInput {
190        &self.operation
191    }
192
193    /// Returns the exact expected and delivered marker sequence.
194    #[must_use]
195    pub const fn expected_marker_delivery_seq(&self) -> DeliverySeq {
196        self.expected_marker_delivery_seq
197    }
198
199    /// Returns the binding epoch against which delivery was proven.
200    #[must_use]
201    pub const fn proof_binding_epoch(&self) -> BindingEpoch {
202        self.proof_binding_epoch
203    }
204
205    /// Returns the retained marker-backed cursor provenance.
206    #[must_use]
207    pub const fn progress(&self) -> ParticipantCursorProgress {
208        self.progress
209    }
210}
211
212/// Exhaustive result of marker-proof selection after common authority.
213#[derive(Clone, Debug, PartialEq, Eq)]
214pub enum MarkerProofDecision {
215    /// Exact replay of an already-accepted marker acknowledgement.
216    AckNoOp(AckNoOp),
217    /// Requested marker conflicts with cursor or anchor state.
218    MarkerMismatch(MarkerMismatch),
219    /// Exact expected marker lacks delivery to the proof epoch.
220    MarkerNotDelivered(MarkerNotDelivered),
221    /// Exact expected marker was delivered to the exact proof epoch.
222    Permit(MarkerProofPermit),
223}
224
225/// Applies the frozen total marker-proof selector in its exact precedence order.
226#[must_use]
227pub fn select_marker_proof(
228    state: &MarkerProofState,
229    input: MarkerProofInput,
230) -> MarkerProofDecision {
231    let requested = input.requested_marker_delivery_seq();
232    if requested < state.current_cursor {
233        return MarkerProofDecision::MarkerMismatch(MarkerMismatch {
234            request: input.into_wire_request(),
235            mismatch: MarkerMismatchBody::BelowCursor {
236                current_cursor: state.current_cursor,
237            },
238        });
239    }
240
241    if requested == state.current_cursor && state.accepted_marker_at_cursor && input.is_marker_ack()
242    {
243        if let Some(envelope) = input.marker_ack_envelope() {
244            return MarkerProofDecision::AckNoOp(AckNoOp::marker_ack(envelope));
245        }
246    }
247
248    let Some(expected) = state.expected_marker_delivery_seq else {
249        return MarkerProofDecision::MarkerMismatch(MarkerMismatch {
250            request: input.into_wire_request(),
251            mismatch: MarkerMismatchBody::NoMarkerExpected,
252        });
253    };
254    if requested != expected {
255        return MarkerProofDecision::MarkerMismatch(MarkerMismatch {
256            request: input.into_wire_request(),
257            mismatch: MarkerMismatchBody::ExpectedDifferentMarker {
258                expected_marker_delivery_seq: expected,
259            },
260        });
261    }
262
263    let Some(progress) = state.delivered_to_proof_epoch else {
264        return MarkerProofDecision::MarkerNotDelivered(MarkerNotDelivered {
265            request: input.into_wire_request(),
266            reason: MarkerNotDeliveredReason::NotDeliveredToProofEpoch,
267            expected_marker_delivery_seq: expected,
268        });
269    };
270    if progress.participant_id() != input.participant_id()
271        || progress.binding_epoch() != state.proof_binding_epoch
272        || progress.through_seq() != expected
273        || progress.marker_delivery_seq() != Some(expected)
274        || state.proof_binding_epoch.capability_generation != input.capability_generation()
275    {
276        return MarkerProofDecision::MarkerNotDelivered(MarkerNotDelivered {
277            request: input.into_wire_request(),
278            reason: MarkerNotDeliveredReason::NotDeliveredToProofEpoch,
279            expected_marker_delivery_seq: expected,
280        });
281    }
282    MarkerProofDecision::Permit(MarkerProofPermit {
283        operation: input,
284        expected_marker_delivery_seq: expected,
285        proof_binding_epoch: state.proof_binding_epoch,
286        progress,
287    })
288}