liminal_protocol/lifecycle/operations/
marker_proof.rs1use 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#[derive(Clone, Debug, PartialEq, Eq)]
19pub enum MarkerProofInput {
20 CredentialAttach(AttachMarkerProof),
22 MarkerAck(MarkerAckProof),
24}
25
26impl MarkerProofInput {
27 #[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 #[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 #[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 #[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#[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 #[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 #[must_use]
148 pub const fn current_cursor(self) -> DeliverySeq {
149 self.current_cursor
150 }
151
152 #[must_use]
154 pub const fn accepted_marker_at_cursor(self) -> bool {
155 self.accepted_marker_at_cursor
156 }
157
158 #[must_use]
160 pub const fn expected_marker_delivery_seq(self) -> Option<DeliverySeq> {
161 self.expected_marker_delivery_seq
162 }
163
164 #[must_use]
166 pub const fn proof_binding_epoch(self) -> BindingEpoch {
167 self.proof_binding_epoch
168 }
169
170 #[must_use]
172 pub const fn delivered_to_proof_epoch(self) -> Option<ParticipantCursorProgress> {
173 self.delivered_to_proof_epoch
174 }
175}
176
177#[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 #[must_use]
189 pub const fn operation(&self) -> &MarkerProofInput {
190 &self.operation
191 }
192
193 #[must_use]
195 pub const fn expected_marker_delivery_seq(&self) -> DeliverySeq {
196 self.expected_marker_delivery_seq
197 }
198
199 #[must_use]
201 pub const fn proof_binding_epoch(&self) -> BindingEpoch {
202 self.proof_binding_epoch
203 }
204
205 #[must_use]
207 pub const fn progress(&self) -> ParticipantCursorProgress {
208 self.progress
209 }
210}
211
212#[derive(Clone, Debug, PartialEq, Eq)]
214pub enum MarkerProofDecision {
215 AckNoOp(AckNoOp),
217 MarkerMismatch(MarkerMismatch),
219 MarkerNotDelivered(MarkerNotDelivered),
221 Permit(MarkerProofPermit),
223}
224
225#[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}