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