1use crate::outcome::CandidatePhase;
2use crate::wire::{
3 AttachSecret, BindingEpoch, ConversationId, DeliverySeq, DetachedCause, Generation,
4 LeaveAttemptToken, LeaveCommitted, LeaveRequest, ParticipantId, TransactionOrder,
5};
6
7use super::{
8 AdmissionOrder, BindingState, ClaimFrontiers, CommittedBindingTerminal, DetachCell,
9 PendingFinalization, detach::validate_pending_pair, lookup::AttachSecretProof,
10};
11
12#[derive(Clone, Debug, PartialEq, Eq)]
14pub struct EnrollmentFingerprint<F>(F);
15
16impl<F> EnrollmentFingerprint<F> {
17 #[must_use]
19 pub const fn new(value: F) -> Self {
20 Self(value)
21 }
22
23 #[must_use]
25 pub const fn value(&self) -> &F {
26 &self.0
27 }
28
29 #[must_use]
31 pub fn into_inner(self) -> F {
32 self.0
33 }
34}
35
36#[derive(Clone, Debug, PartialEq, Eq)]
38pub struct LeaveFingerprint<F>(F);
39
40impl<F> LeaveFingerprint<F> {
41 #[must_use]
43 pub const fn new(value: F) -> Self {
44 Self(value)
45 }
46
47 #[must_use]
49 pub const fn value(&self) -> &F {
50 &self.0
51 }
52
53 #[must_use]
55 pub fn into_inner(self) -> F {
56 self.0
57 }
58}
59
60#[derive(Clone, Debug, PartialEq, Eq)]
62pub struct LiveMemberRestore<F> {
63 pub participant_id: ParticipantId,
65 pub conversation_id: ConversationId,
67 pub generation: Generation,
69 pub attach_secret: AttachSecret,
71 pub cursor: DeliverySeq,
73 pub enrollment_fingerprint: EnrollmentFingerprint<F>,
75 pub latest_terminal: Option<CommittedBindingTerminal>,
77}
78
79#[derive(Clone, Copy, Debug, PartialEq, Eq)]
81pub enum MembershipInvariantError {
82 TerminalIdentity,
84 TerminalGeneration,
86}
87
88#[derive(Clone, Debug, PartialEq, Eq)]
93pub struct LiveMember<F> {
94 participant_id: ParticipantId,
95 conversation_id: ConversationId,
96 generation: Generation,
97 attach_secret: AttachSecret,
98 cursor: DeliverySeq,
99 enrollment_fingerprint: EnrollmentFingerprint<F>,
100 latest_terminal: Option<CommittedBindingTerminal>,
101}
102
103#[derive(Clone, Copy, Debug, PartialEq, Eq)]
108pub(super) struct LiveMemberCursorUpdate {
109 conversation_id: ConversationId,
110 participant_id: ParticipantId,
111 generation: Generation,
112 from_cursor: DeliverySeq,
113 resulting_cursor: DeliverySeq,
114}
115
116impl LiveMemberCursorUpdate {
117 pub(super) const fn new(
119 conversation_id: ConversationId,
120 participant_id: ParticipantId,
121 generation: Generation,
122 from_cursor: DeliverySeq,
123 resulting_cursor: DeliverySeq,
124 ) -> Self {
125 Self {
126 conversation_id,
127 participant_id,
128 generation,
129 from_cursor,
130 resulting_cursor,
131 }
132 }
133
134 pub(super) const fn previous_cursor(self) -> DeliverySeq {
135 self.from_cursor
136 }
137
138 pub(super) const fn resulting_cursor(self) -> DeliverySeq {
139 self.resulting_cursor
140 }
141}
142
143#[derive(Clone, Copy, Debug, PartialEq, Eq)]
145pub(super) enum LiveMemberCursorUpdateError {
146 Conversation {
148 expected: ConversationId,
149 actual: ConversationId,
150 },
151 Participant {
153 expected: ParticipantId,
154 actual: ParticipantId,
155 },
156 Generation {
158 expected: Generation,
159 actual: Generation,
160 },
161 NonAdvancing {
163 from_cursor: DeliverySeq,
164 resulting_cursor: DeliverySeq,
165 },
166 CursorPrestate {
168 expected_from_cursor: DeliverySeq,
169 resulting_cursor: DeliverySeq,
170 actual_cursor: DeliverySeq,
171 },
172}
173
174impl<F> LiveMember<F> {
175 pub fn restore(state: LiveMemberRestore<F>) -> Result<Self, MembershipInvariantError> {
182 validate_terminal(
183 state.participant_id,
184 state.conversation_id,
185 state.generation,
186 state.latest_terminal,
187 )?;
188 Ok(Self {
189 participant_id: state.participant_id,
190 conversation_id: state.conversation_id,
191 generation: state.generation,
192 attach_secret: state.attach_secret,
193 cursor: state.cursor,
194 enrollment_fingerprint: state.enrollment_fingerprint,
195 latest_terminal: state.latest_terminal,
196 })
197 }
198
199 pub(crate) const fn from_enrollment(
200 participant_id: ParticipantId,
201 conversation_id: ConversationId,
202 attach_secret: AttachSecret,
203 enrollment_fingerprint: EnrollmentFingerprint<F>,
204 ) -> Self {
205 Self {
206 participant_id,
207 conversation_id,
208 generation: Generation::ONE,
209 attach_secret,
210 cursor: 0,
211 enrollment_fingerprint,
212 latest_terminal: None,
213 }
214 }
215
216 #[must_use]
218 pub const fn participant_id(&self) -> ParticipantId {
219 self.participant_id
220 }
221
222 #[must_use]
224 pub const fn conversation_id(&self) -> ConversationId {
225 self.conversation_id
226 }
227
228 #[must_use]
230 pub const fn generation(&self) -> Generation {
231 self.generation
232 }
233
234 #[must_use]
236 pub const fn attach_secret(&self) -> AttachSecret {
237 self.attach_secret
238 }
239
240 #[must_use]
242 pub const fn cursor(&self) -> DeliverySeq {
243 self.cursor
244 }
245
246 #[must_use]
248 pub const fn enrollment_fingerprint(&self) -> &EnrollmentFingerprint<F> {
249 &self.enrollment_fingerprint
250 }
251
252 #[must_use]
254 pub const fn latest_terminal(&self) -> Option<CommittedBindingTerminal> {
255 self.latest_terminal
256 }
257
258 pub(super) fn apply_cursor_update(
260 &mut self,
261 update: LiveMemberCursorUpdate,
262 ) -> Result<(), LiveMemberCursorUpdateError> {
263 if self.conversation_id != update.conversation_id {
264 return Err(LiveMemberCursorUpdateError::Conversation {
265 expected: update.conversation_id,
266 actual: self.conversation_id,
267 });
268 }
269 if self.participant_id != update.participant_id {
270 return Err(LiveMemberCursorUpdateError::Participant {
271 expected: update.participant_id,
272 actual: self.participant_id,
273 });
274 }
275 if self.generation != update.generation {
276 return Err(LiveMemberCursorUpdateError::Generation {
277 expected: update.generation,
278 actual: self.generation,
279 });
280 }
281 if update.resulting_cursor <= update.from_cursor {
282 return Err(LiveMemberCursorUpdateError::NonAdvancing {
283 from_cursor: update.from_cursor,
284 resulting_cursor: update.resulting_cursor,
285 });
286 }
287 if self.cursor == update.resulting_cursor {
288 return Ok(());
289 }
290 if self.cursor != update.from_cursor {
291 return Err(LiveMemberCursorUpdateError::CursorPrestate {
292 expected_from_cursor: update.from_cursor,
293 resulting_cursor: update.resulting_cursor,
294 actual_cursor: self.cursor,
295 });
296 }
297 self.cursor = update.resulting_cursor;
298 Ok(())
299 }
300
301 pub fn with_committed_terminal(
307 mut self,
308 terminal: CommittedBindingTerminal,
309 ) -> Result<Self, MembershipInvariantError> {
310 validate_terminal(
311 self.participant_id,
312 self.conversation_id,
313 self.generation,
314 Some(terminal),
315 )?;
316 self.latest_terminal = Some(terminal);
317 Ok(self)
318 }
319
320 pub(super) fn rotate(
321 mut self,
322 generation: Generation,
323 attach_secret: AttachSecret,
324 cursor: DeliverySeq,
325 terminal: Option<CommittedBindingTerminal>,
326 ) -> Result<Self, MembershipInvariantError> {
327 let latest_terminal = terminal.or(self.latest_terminal);
328 validate_terminal(
329 self.participant_id,
330 self.conversation_id,
331 generation,
332 latest_terminal,
333 )?;
334 self.generation = generation;
335 self.attach_secret = attach_secret;
336 self.cursor = cursor;
337 self.latest_terminal = latest_terminal;
338 Ok(self)
339 }
340}
341
342fn validate_terminal(
343 participant_id: ParticipantId,
344 conversation_id: ConversationId,
345 generation: Generation,
346 terminal: Option<CommittedBindingTerminal>,
347) -> Result<(), MembershipInvariantError> {
348 let Some(terminal) = terminal else {
349 return Ok(());
350 };
351 if terminal.participant_id() != participant_id || terminal.conversation_id() != conversation_id
352 {
353 return Err(MembershipInvariantError::TerminalIdentity);
354 }
355 if terminal.binding_epoch().capability_generation > generation {
356 return Err(MembershipInvariantError::TerminalGeneration);
357 }
358 Ok(())
359}
360
361#[derive(Clone, Debug, PartialEq, Eq)]
366pub struct RetiredIdentity<EF, V, LF> {
367 participant_id: ParticipantId,
368 conversation_id: ConversationId,
369 retired_generation: Generation,
370 enrollment_fingerprint: EnrollmentFingerprint<EF>,
371 leave_attempt_token: LeaveAttemptToken,
372 leave_request_verifier: V,
373 leave_fingerprint: LeaveFingerprint<LF>,
374 left_admission_order: AdmissionOrder,
375 committed_result: LeaveCommitted,
376}
377
378impl<EF, V, LF> RetiredIdentity<EF, V, LF> {
379 #[must_use]
381 pub const fn participant_id(&self) -> ParticipantId {
382 self.participant_id
383 }
384
385 #[must_use]
387 pub const fn conversation_id(&self) -> ConversationId {
388 self.conversation_id
389 }
390
391 #[must_use]
393 pub const fn retired_generation(&self) -> Generation {
394 self.retired_generation
395 }
396
397 #[must_use]
399 pub const fn leave_attempt_token(&self) -> LeaveAttemptToken {
400 self.leave_attempt_token
401 }
402
403 #[must_use]
405 pub const fn committed_result(&self) -> &LeaveCommitted {
406 &self.committed_result
407 }
408
409 #[must_use]
411 pub const fn left_admission_order(&self) -> AdmissionOrder {
412 self.left_admission_order
413 }
414
415 #[must_use]
417 pub const fn leave_request_verifier(&self) -> &V {
418 &self.leave_request_verifier
419 }
420
421 #[must_use]
423 pub const fn enrollment_fingerprint(&self) -> &EnrollmentFingerprint<EF> {
424 &self.enrollment_fingerprint
425 }
426
427 #[must_use]
429 pub const fn leave_fingerprint(&self) -> &LeaveFingerprint<LF> {
430 &self.leave_fingerprint
431 }
432
433 #[allow(clippy::too_many_arguments)]
434 pub(super) fn restore(
435 participant_id: ParticipantId,
436 conversation_id: ConversationId,
437 retired_generation: Generation,
438 enrollment_fingerprint: EnrollmentFingerprint<EF>,
439 leave_attempt_token: LeaveAttemptToken,
440 leave_request_verifier: V,
441 leave_fingerprint: LeaveFingerprint<LF>,
442 left_transaction_order: TransactionOrder,
443 committed_result: LeaveCommitted,
444 ) -> Result<Self, RetirementError> {
445 if committed_result.conversation_id() != conversation_id {
446 return Err(RetirementError::Conversation);
447 }
448 if committed_result.participant_id() != participant_id {
449 return Err(RetirementError::Participant);
450 }
451 if committed_result.presented_generation() != retired_generation {
452 return Err(RetirementError::Generation);
453 }
454 if committed_result.retired_generation() != retired_generation {
455 return Err(RetirementError::RetiredGeneration);
456 }
457 if committed_result.leave_attempt_token() != leave_attempt_token {
458 return Err(RetirementError::Token);
459 }
460 let left_admission_order = AdmissionOrder::new(
461 left_transaction_order,
462 CandidatePhase::MembershipExit,
463 participant_id,
464 );
465 Ok(Self {
466 participant_id,
467 conversation_id,
468 retired_generation,
469 enrollment_fingerprint,
470 leave_attempt_token,
471 leave_request_verifier,
472 leave_fingerprint,
473 left_admission_order,
474 committed_result,
475 })
476 }
477}
478
479#[derive(Clone, Debug, PartialEq, Eq)]
481pub enum IdentityState<EF, V, LF> {
482 Live(LiveMember<EF>),
484 Retired(RetiredIdentity<EF, V, LF>),
486}
487
488#[derive(Debug, PartialEq, Eq)]
495pub struct LeaveCommit<EF, V, LF> {
496 identity: IdentityState<EF, V, LF>,
497 frontiers: ClaimFrontiers,
498}
499
500impl<EF, V, LF> LeaveCommit<EF, V, LF> {
501 #[must_use]
503 pub const fn identity(&self) -> &IdentityState<EF, V, LF> {
504 &self.identity
505 }
506
507 #[must_use]
509 pub const fn frontiers(&self) -> &ClaimFrontiers {
510 &self.frontiers
511 }
512
513 #[must_use]
515 pub fn into_parts(self) -> (IdentityState<EF, V, LF>, ClaimFrontiers) {
516 (self.identity, self.frontiers)
517 }
518}
519
520#[derive(Clone, Copy, Debug, PartialEq, Eq)]
522pub enum RetirementError {
523 Conversation,
525 Participant,
527 Generation,
529 RetiredGeneration,
531 Token,
533 LeftAdmissionOrder,
535}
536
537#[derive(Clone, Copy, Debug, PartialEq, Eq)]
539pub enum LeaveVerificationError {
540 Conversation,
542 Participant,
544 Generation,
546 Secret,
548}
549
550pub struct VerifiedLeaveRequest<V, LF> {
552 conversation_id: ConversationId,
553 participant_id: ParticipantId,
554 generation: Generation,
555 leave_attempt_token: LeaveAttemptToken,
556 leave_request_verifier: V,
557 leave_fingerprint: LeaveFingerprint<LF>,
558}
559
560#[derive(Clone, Copy, Debug, PartialEq, Eq)]
562pub struct LeaveCommitParameters {
563 pub left_delivery_seq: DeliverySeq,
565}
566
567#[derive(Debug, PartialEq, Eq)]
569struct NoInterveningTupleProof {
570 pending_order: AdmissionOrder,
571 left_transaction_order: TransactionOrder,
572}
573
574impl NoInterveningTupleProof {
575 fn matches(&self, pending: PendingFinalization) -> bool {
576 self.pending_order == pending.admission_order()
577 && self.left_transaction_order > self.pending_order.transaction_order()
578 }
579}
580
581#[derive(Debug, PartialEq, Eq)]
602pub struct PreparedLeaveAuthority {
603 frontiers: ClaimFrontiers,
604 conversation_id: ConversationId,
605 participant_id: ParticipantId,
606 kind: PreparedLeaveKind,
607}
608
609#[derive(Debug, PartialEq, Eq)]
610enum PreparedLeaveKind {
611 Settled {
612 ended_binding_epoch: Option<BindingEpoch>,
613 left_transaction_order: TransactionOrder,
614 },
615 Pending {
616 binding_epoch: BindingEpoch,
617 no_intervening: NoInterveningTupleProof,
618 },
619}
620
621impl PreparedLeaveAuthority {
622 pub(super) const fn settled(
623 frontiers: ClaimFrontiers,
624 conversation_id: ConversationId,
625 participant_id: ParticipantId,
626 ended_binding_epoch: Option<BindingEpoch>,
627 left_transaction_order: TransactionOrder,
628 ) -> Self {
629 Self {
630 frontiers,
631 conversation_id,
632 participant_id,
633 kind: PreparedLeaveKind::Settled {
634 ended_binding_epoch,
635 left_transaction_order,
636 },
637 }
638 }
639
640 pub(super) const fn pending(
641 frontiers: ClaimFrontiers,
642 conversation_id: ConversationId,
643 participant_id: ParticipantId,
644 binding_epoch: BindingEpoch,
645 pending_order: AdmissionOrder,
646 left_transaction_order: TransactionOrder,
647 ) -> Self {
648 Self {
649 frontiers,
650 conversation_id,
651 participant_id,
652 kind: PreparedLeaveKind::Pending {
653 binding_epoch,
654 no_intervening: NoInterveningTupleProof {
655 pending_order,
656 left_transaction_order,
657 },
658 },
659 }
660 }
661
662 fn consume_settled(
663 self,
664 member_conversation_id: ConversationId,
665 member_participant_id: ParticipantId,
666 ended_binding_epoch: Option<BindingEpoch>,
667 ) -> Result<(ClaimFrontiers, TransactionOrder), LeaveCommitError> {
668 let Self {
669 frontiers,
670 conversation_id,
671 participant_id,
672 kind,
673 } = self;
674 let PreparedLeaveKind::Settled {
675 ended_binding_epoch: authorized_epoch,
676 left_transaction_order,
677 } = kind
678 else {
679 return Err(LeaveCommitError::PreparedAuthority);
680 };
681 if conversation_id != member_conversation_id
682 || participant_id != member_participant_id
683 || authorized_epoch != ended_binding_epoch
684 {
685 return Err(LeaveCommitError::PreparedAuthority);
686 }
687 Ok((frontiers, left_transaction_order))
688 }
689
690 fn consume_pending(
691 self,
692 pending: PendingFinalization,
693 ) -> Result<(ClaimFrontiers, TransactionOrder), LeaveCommitError> {
694 let Self {
695 frontiers,
696 conversation_id,
697 participant_id,
698 kind,
699 } = self;
700 let PreparedLeaveKind::Pending {
701 binding_epoch,
702 no_intervening,
703 } = kind
704 else {
705 return Err(LeaveCommitError::PreparedAuthority);
706 };
707 if conversation_id != pending.conversation_id()
708 || participant_id != pending.participant_id()
709 || binding_epoch != pending.binding_epoch()
710 || !no_intervening.matches(pending)
711 {
712 return Err(LeaveCommitError::PreparedAuthority);
713 }
714 Ok((frontiers, no_intervening.left_transaction_order))
715 }
716}
717
718#[derive(Clone, Copy, Debug, PartialEq, Eq)]
720pub struct PendingLeaveCommitParameters {
721 pub terminal_delivery_seq: DeliverySeq,
723 pub left_delivery_seq: DeliverySeq,
725}
726
727#[derive(Clone, Copy, Debug, PartialEq, Eq)]
729pub enum LeaveCommitError {
730 PreparedAuthority,
732 VerifiedAuthority,
734 BindingAuthority,
736 PendingTerminalRequiresComposition,
738 NoInterveningTuple,
740 PendingDetachState,
742 TerminalHistory,
744 TerminalSequenceOrder,
746 SequenceAuthority,
748 ResultingFrontier,
750 RetirementInvariant(RetirementError),
752}
753
754impl<F> LiveMember<F> {
755 pub fn verify_leave_request<V, LF>(
761 &self,
762 request: &LeaveRequest,
763 secret_proof: AttachSecretProof,
764 leave_request_verifier: V,
765 leave_fingerprint: LeaveFingerprint<LF>,
766 ) -> Result<VerifiedLeaveRequest<V, LF>, LeaveVerificationError> {
767 if request.conversation_id != self.conversation_id {
768 return Err(LeaveVerificationError::Conversation);
769 }
770 if request.participant_id != self.participant_id {
771 return Err(LeaveVerificationError::Participant);
772 }
773 if request.capability_generation != self.generation {
774 return Err(LeaveVerificationError::Generation);
775 }
776 if secret_proof == AttachSecretProof::Mismatch {
777 return Err(LeaveVerificationError::Secret);
778 }
779 Ok(VerifiedLeaveRequest {
780 conversation_id: request.conversation_id,
781 participant_id: request.participant_id,
782 generation: request.capability_generation,
783 leave_attempt_token: request.leave_attempt_token,
784 leave_request_verifier,
785 leave_fingerprint,
786 })
787 }
788
789 fn retire<V, LF>(
790 self,
791 leave_attempt_token: LeaveAttemptToken,
792 leave_request_verifier: V,
793 leave_fingerprint: LeaveFingerprint<LF>,
794 left_admission_order: AdmissionOrder,
795 committed_result: LeaveCommitted,
796 ) -> Result<RetiredIdentity<F, V, LF>, RetirementError> {
797 if committed_result.conversation_id() != self.conversation_id {
798 return Err(RetirementError::Conversation);
799 }
800 if committed_result.participant_id() != self.participant_id {
801 return Err(RetirementError::Participant);
802 }
803 if committed_result.presented_generation() != self.generation {
804 return Err(RetirementError::Generation);
805 }
806 if committed_result.retired_generation() != self.generation {
807 return Err(RetirementError::RetiredGeneration);
808 }
809 if committed_result.leave_attempt_token() != leave_attempt_token {
810 return Err(RetirementError::Token);
811 }
812 if left_admission_order.candidate_phase() != CandidatePhase::MembershipExit
813 || left_admission_order.participant_index() != self.participant_id
814 {
815 return Err(RetirementError::LeftAdmissionOrder);
816 }
817 Ok(RetiredIdentity {
818 participant_id: self.participant_id,
819 conversation_id: self.conversation_id,
820 retired_generation: committed_result.retired_generation(),
821 enrollment_fingerprint: self.enrollment_fingerprint,
822 leave_attempt_token,
823 leave_request_verifier,
824 leave_fingerprint,
825 left_admission_order,
826 committed_result,
827 })
828 }
829}
830
831pub fn commit_leave<EF, V, LF, D>(
838 member: LiveMember<EF>,
839 binding_state: BindingState,
840 detach_cell: DetachCell<D>,
841 verified: VerifiedLeaveRequest<V, LF>,
842 authority: PreparedLeaveAuthority,
843 parameters: LeaveCommitParameters,
844) -> Result<LeaveCommit<EF, V, LF>, LeaveCommitError> {
845 validate_verified(&member, &verified)?;
846 let ended_binding_epoch = match binding_state {
847 BindingState::Detached => None,
848 BindingState::Bound(active) => {
849 validate_active(
850 &member,
851 active.participant_id,
852 active.conversation_id,
853 active.binding_epoch,
854 )?;
855 Some(active.binding_epoch)
856 }
857 BindingState::PendingFinalization(_) => {
858 return Err(LeaveCommitError::PendingTerminalRequiresComposition);
859 }
860 };
861 validate_settled_cell(&member, binding_state, &detach_cell)?;
862 let (frontiers, left_transaction_order) = authority.consume_settled(
863 member.conversation_id,
864 member.participant_id,
865 ended_binding_epoch,
866 )?;
867 let prior_terminal_delivery_seq = member
868 .latest_terminal
869 .map(CommittedBindingTerminal::delivery_seq);
870 validate_sequence_order(prior_terminal_delivery_seq, parameters.left_delivery_seq)?;
871 finish_leave(
872 member,
873 verified,
874 ended_binding_epoch,
875 prior_terminal_delivery_seq,
876 None,
877 left_transaction_order,
878 parameters.left_delivery_seq,
879 detach_cell,
880 frontiers,
881 )
882}
883
884pub fn commit_pending_leave<EF, V, LF, D>(
894 member: LiveMember<EF>,
895 pending: PendingFinalization,
896 detach_cell: DetachCell<D>,
897 verified: VerifiedLeaveRequest<V, LF>,
898 authority: PreparedLeaveAuthority,
899 parameters: PendingLeaveCommitParameters,
900) -> Result<LeaveCommit<EF, V, LF>, LeaveCommitError> {
901 let PendingLeaveCommitParameters {
902 terminal_delivery_seq,
903 left_delivery_seq,
904 } = parameters;
905 validate_verified(&member, &verified)?;
906 validate_pending(&member, pending)?;
907 let (frontiers, left_transaction_order) = authority.consume_pending(pending)?;
908 validate_pending_cell(member.conversation_id, pending, &detach_cell)?;
909 if terminal_delivery_seq >= left_delivery_seq {
910 return Err(LeaveCommitError::TerminalSequenceOrder);
911 }
912 let committed_terminal = pending.commit(terminal_delivery_seq);
913 validate_terminal(
914 member.participant_id,
915 member.conversation_id,
916 member.generation,
917 Some(committed_terminal),
918 )
919 .map_err(|_| LeaveCommitError::BindingAuthority)?;
920 finish_leave(
921 member,
922 verified,
923 None,
924 Some(committed_terminal.delivery_seq()),
925 Some(committed_terminal),
926 left_transaction_order,
927 left_delivery_seq,
928 detach_cell,
929 frontiers,
930 )
931}
932
933fn validate_verified<EF, V, LF>(
934 member: &LiveMember<EF>,
935 verified: &VerifiedLeaveRequest<V, LF>,
936) -> Result<(), LeaveCommitError> {
937 if verified.conversation_id != member.conversation_id
938 || verified.participant_id != member.participant_id
939 || verified.generation != member.generation
940 {
941 return Err(LeaveCommitError::VerifiedAuthority);
942 }
943 Ok(())
944}
945
946fn validate_active<EF>(
947 member: &LiveMember<EF>,
948 participant_id: ParticipantId,
949 conversation_id: ConversationId,
950 binding_epoch: BindingEpoch,
951) -> Result<(), LeaveCommitError> {
952 if participant_id != member.participant_id
953 || conversation_id != member.conversation_id
954 || binding_epoch.capability_generation != member.generation
955 {
956 return Err(LeaveCommitError::BindingAuthority);
957 }
958 Ok(())
959}
960
961fn validate_pending<EF>(
962 member: &LiveMember<EF>,
963 pending: PendingFinalization,
964) -> Result<(), LeaveCommitError> {
965 validate_active(
966 member,
967 pending.participant_id(),
968 pending.conversation_id(),
969 pending.binding_epoch(),
970 )
971}
972
973fn validate_sequence_order(
974 prior: Option<DeliverySeq>,
975 left: DeliverySeq,
976) -> Result<(), LeaveCommitError> {
977 if prior.is_some_and(|sequence| sequence >= left) {
978 return Err(LeaveCommitError::TerminalSequenceOrder);
979 }
980 Ok(())
981}
982
983fn validate_settled_cell<EF, D>(
984 member: &LiveMember<EF>,
985 binding_state: BindingState,
986 detach_cell: &DetachCell<D>,
987) -> Result<(), LeaveCommitError> {
988 match detach_cell {
989 DetachCell::Empty(_) => Ok(()),
990 DetachCell::Pending(_) => Err(LeaveCommitError::PendingDetachState),
991 DetachCell::Committed(cell) => {
992 if binding_state != BindingState::Detached
993 || cell.participant_id() != member.participant_id
994 || cell.request_generation() != member.generation
995 {
996 return Err(LeaveCommitError::TerminalHistory);
997 }
998 let Some(terminal) = member.latest_terminal else {
999 return Err(LeaveCommitError::TerminalHistory);
1000 };
1001 if terminal.detached_cause() != Some(DetachedCause::CleanDeregister)
1002 || terminal.binding_epoch() != cell.committed_binding_epoch()
1003 || terminal.delivery_seq() != cell.detached_delivery_seq()
1004 {
1005 return Err(LeaveCommitError::TerminalHistory);
1006 }
1007 Ok(())
1008 }
1009 DetachCell::Terminalized(cell) => {
1010 if cell.participant_id() != member.participant_id || member.latest_terminal.is_none() {
1011 return Err(LeaveCommitError::TerminalHistory);
1012 }
1013 Ok(())
1014 }
1015 }
1016}
1017
1018fn validate_pending_cell<D>(
1019 conversation_id: ConversationId,
1020 pending: PendingFinalization,
1021 detach_cell: &DetachCell<D>,
1022) -> Result<(), LeaveCommitError> {
1023 match detach_cell {
1024 DetachCell::Pending(cell) => validate_pending_pair(
1025 BindingState::PendingFinalization(pending),
1026 cell,
1027 Some(conversation_id),
1028 )
1029 .map(|_| ())
1030 .map_err(|_| LeaveCommitError::PendingDetachState),
1031 DetachCell::Committed(_) => Err(LeaveCommitError::TerminalHistory),
1032 DetachCell::Terminalized(cell) if cell.participant_id() != pending.participant_id() => {
1033 Err(LeaveCommitError::TerminalHistory)
1034 }
1035 DetachCell::Empty(_) | DetachCell::Terminalized(_) => Ok(()),
1036 }
1037}
1038
1039#[allow(
1040 clippy::too_many_arguments,
1041 reason = "the final Leave constructor keeps every verified authority and both atomic result halves explicit"
1042)]
1043fn finish_leave<EF, V, LF, D>(
1044 member: LiveMember<EF>,
1045 verified: VerifiedLeaveRequest<V, LF>,
1046 ended_binding_epoch: Option<BindingEpoch>,
1047 prior_terminal_delivery_seq: Option<DeliverySeq>,
1048 committed_terminal: Option<CommittedBindingTerminal>,
1049 left_transaction_order: TransactionOrder,
1050 left_delivery_seq: DeliverySeq,
1051 detach_cell: DetachCell<D>,
1052 frontiers: ClaimFrontiers,
1053) -> Result<LeaveCommit<EF, V, LF>, LeaveCommitError> {
1054 let VerifiedLeaveRequest {
1055 conversation_id,
1056 participant_id,
1057 generation,
1058 leave_attempt_token,
1059 leave_request_verifier,
1060 leave_fingerprint,
1061 } = verified;
1062 if generation != member.generation {
1063 return Err(LeaveCommitError::VerifiedAuthority);
1064 }
1065 let Some(committed_result) = LeaveCommitted::new(
1066 conversation_id,
1067 leave_attempt_token,
1068 participant_id,
1069 member.generation,
1070 ended_binding_epoch,
1071 prior_terminal_delivery_seq,
1072 left_delivery_seq,
1073 ) else {
1074 return Err(LeaveCommitError::TerminalSequenceOrder);
1075 };
1076 let frontiers = frontiers.finish_leave_claims(
1077 participant_id,
1078 ended_binding_epoch,
1079 committed_terminal,
1080 left_delivery_seq,
1081 left_transaction_order,
1082 )?;
1083 let retired = member
1084 .retire(
1085 leave_attempt_token,
1086 leave_request_verifier,
1087 leave_fingerprint,
1088 AdmissionOrder::new(
1089 left_transaction_order,
1090 CandidatePhase::MembershipExit,
1091 participant_id,
1092 ),
1093 committed_result,
1094 )
1095 .map_err(LeaveCommitError::RetirementInvariant)?;
1096 let _detach_cell_replaced_by_tombstone = detach_cell;
1097 Ok(LeaveCommit {
1098 identity: IdentityState::Retired(retired),
1099 frontiers,
1100 })
1101}