1use crate::algebra::{ResourceVector, WideResourceVector};
2use crate::wire::{BindingEpoch, ConversationId, DeliverySeq, ParticipantId, ParticipantIndex};
3
4use super::{
5 ActiveBinding, CommittedDiedTerminal, ObserverProgressProjection,
6 claim_frontier::{ValidatedMarkerCandidate, ValidatedMarkerRecord},
7};
8
9#[derive(Clone, Copy, Debug, PartialEq, Eq)]
11pub struct ClosureDebt(WideResourceVector);
12
13impl ClosureDebt {
14 #[must_use]
16 pub const fn new(value: WideResourceVector) -> Option<Self> {
17 if value.is_zero() {
18 None
19 } else {
20 Some(Self(value))
21 }
22 }
23
24 #[must_use]
26 pub const fn value(self) -> WideResourceVector {
27 self.0
28 }
29}
30
31#[derive(Clone, Copy, Debug, PartialEq, Eq)]
33pub struct ObserverProjection {
34 through_seq: DeliverySeq,
35}
36
37impl ObserverProjection {
38 #[must_use]
40 pub const fn new(through_seq: DeliverySeq) -> Self {
41 Self { through_seq }
42 }
43
44 #[must_use]
46 pub const fn through_seq(self) -> DeliverySeq {
47 self.through_seq
48 }
49}
50
51#[derive(Clone, Copy, Debug, PartialEq, Eq)]
53pub struct PhysicalCompaction {
54 from_floor: DeliverySeq,
55 through_seq: DeliverySeq,
56}
57
58impl PhysicalCompaction {
59 #[must_use]
61 pub const fn new(from_floor: DeliverySeq, through_seq: DeliverySeq) -> Option<Self> {
62 if from_floor <= through_seq {
63 Some(Self {
64 from_floor,
65 through_seq,
66 })
67 } else {
68 None
69 }
70 }
71
72 #[must_use]
74 pub const fn from_floor(self) -> DeliverySeq {
75 self.from_floor
76 }
77
78 #[must_use]
80 pub const fn through_seq(self) -> DeliverySeq {
81 self.through_seq
82 }
83}
84
85#[allow(clippy::struct_field_names)]
106#[derive(Clone, Copy, Debug, PartialEq, Eq)]
107pub struct MarkerDelivery {
108 conversation_id: ConversationId,
109 participant_id: ParticipantId,
110 binding_epoch: BindingEpoch,
111 marker_delivery_seq: DeliverySeq,
112}
113
114impl MarkerDelivery {
115 #[must_use]
123 pub(super) const fn successor_from_validated_candidate(
124 candidate: ValidatedMarkerCandidate,
125 ) -> StoredEdge {
126 let conversation_id = candidate.conversation_id();
127 let participant_id = candidate.participant_id();
128 let marker_delivery_seq = candidate.delivery_seq();
129 let successor = match candidate.target_binding() {
130 super::FrontierBinding::Bound(binding_epoch) => StoredEdge::MarkerDelivery(Self {
131 conversation_id,
132 participant_id,
133 binding_epoch,
134 marker_delivery_seq,
135 }),
136 super::FrontierBinding::Detached(last_dead_binding_epoch) => {
137 StoredEdge::DetachedMarkerRelease(DetachedMarkerRelease {
138 participant_id,
139 marker_delivery_seq,
140 last_dead_binding_epoch,
141 })
142 }
143 };
144 candidate.consume();
145 successor
146 }
147
148 #[must_use]
150 pub(super) const fn from_validated_record(record: &ValidatedMarkerRecord) -> Self {
151 Self {
152 conversation_id: record.conversation_id(),
153 participant_id: record.participant_id(),
154 binding_epoch: record.binding_epoch(),
155 marker_delivery_seq: record.delivery_seq(),
156 }
157 }
158
159 #[must_use]
161 pub const fn conversation_id(self) -> ConversationId {
162 self.conversation_id
163 }
164
165 #[must_use]
167 pub const fn participant_id(self) -> ParticipantId {
168 self.participant_id
169 }
170
171 #[must_use]
173 pub const fn binding_epoch(self) -> BindingEpoch {
174 self.binding_epoch
175 }
176
177 #[must_use]
179 pub const fn marker_delivery_seq(self) -> DeliverySeq {
180 self.marker_delivery_seq
181 }
182}
183
184#[cfg(test)]
185#[allow(clippy::expect_used, clippy::too_many_lines)]
186pub fn validated_marker_record_for_test(
187 conversation_id: crate::wire::ConversationId,
188 participant_id: ParticipantId,
189 target_binding: super::claim_frontier::FrontierBinding,
190 marker_delivery_seq: DeliverySeq,
191 cursor: DeliverySeq,
192) -> ValidatedMarkerRecord {
193 use alloc::{vec, vec::Vec};
194
195 use crate::outcome::CandidatePhase;
196
197 use super::{
198 AdmissionOrder, OrderClaims, OrderHigh, OrderLedger, RecoverySequenceReserve,
199 SequenceClaims, SequenceLedger,
200 claim_frontier::{
201 BindingTerminalOwner, ClaimFrontiers, ClaimFrontiersRestore, FrontierBinding,
202 FrontierParticipant, ImmutableSequenceCandidate, MarkerProvenance, MarkerRecordRequest,
203 MovableOrderClaim, MovableSequenceClaim, OrderClaimFrontierRestore, OrderDirectOwner,
204 RetainedCausalRecord, RetainedCausalRecordKind, SequenceClaimFrontierRestore,
205 SequenceDirectOwner, SequenceProductRangesRestore, TerminalProductRangeRestore,
206 },
207 };
208
209 let identity_slot_limit = participant_id
210 .checked_add(1)
211 .expect("test participant must fit a half-open identity domain");
212 let exit_seq = marker_delivery_seq
213 .checked_add(1)
214 .expect("test marker must leave an exit-claim suffix");
215 let binding_epoch = match target_binding {
216 FrontierBinding::Bound(epoch) | FrontierBinding::Detached(epoch) => epoch,
217 };
218 let terminal_owner = BindingTerminalOwner {
219 participant_index: participant_id,
220 binding_epoch,
221 };
222 let (sequence_claims, sequence_movable, products, order_claims, order_movable) =
223 match target_binding {
224 FrontierBinding::Bound(_) => {
225 let terminal_seq = exit_seq
226 .checked_add(1)
227 .expect("test marker must leave a terminal-claim suffix");
228 let product_seq = terminal_seq
229 .checked_add(1)
230 .expect("test marker must leave a terminal-product suffix");
231 (
232 SequenceClaims::new(1, 1, 0, RecoverySequenceReserve::None),
233 vec![
234 MovableSequenceClaim {
235 delivery_seq: exit_seq,
236 owner: SequenceDirectOwner::MembershipExit {
237 participant_index: participant_id,
238 },
239 },
240 MovableSequenceClaim {
241 delivery_seq: terminal_seq,
242 owner: SequenceDirectOwner::BindingTerminal(terminal_owner),
243 },
244 ],
245 SequenceProductRangesRestore {
246 live_times_terminal: vec![TerminalProductRangeRestore {
247 start: product_seq,
248 length: 1,
249 terminal: terminal_owner,
250 }],
251 live_times_replacement_terminal: None,
252 other_live_times_exit: vec![],
253 },
254 OrderClaims::new(1, 1, false, false)
255 .expect("bound test claims have no torn recovery pair"),
256 vec![
257 MovableOrderClaim {
258 transaction_order: 1,
259 owner: OrderDirectOwner::ActiveBindingTerminal(terminal_owner),
260 },
261 MovableOrderClaim {
262 transaction_order: 2,
263 owner: OrderDirectOwner::MembershipExit {
264 participant_index: participant_id,
265 },
266 },
267 ],
268 )
269 }
270 FrontierBinding::Detached(_) => (
271 SequenceClaims::new(1, 0, 0, RecoverySequenceReserve::None),
272 vec![MovableSequenceClaim {
273 delivery_seq: exit_seq,
274 owner: SequenceDirectOwner::MembershipExit {
275 participant_index: participant_id,
276 },
277 }],
278 SequenceProductRangesRestore::default(),
279 OrderClaims::new(0, 1, false, false)
280 .expect("detached test claims have no torn recovery pair"),
281 vec![MovableOrderClaim {
282 transaction_order: 1,
283 owner: OrderDirectOwner::MembershipExit {
284 participant_index: participant_id,
285 },
286 }],
287 ),
288 };
289 let sequence_ledger = SequenceLedger::try_new(marker_delivery_seq, sequence_claims)
290 .expect("test sequence frontier is within the numeric suffix");
291 let order_ledger = OrderLedger::try_new(OrderHigh::Allocated(0), order_claims)
292 .expect("test order frontier is within the numeric suffix");
293 let admission_order = AdmissionOrder::new(0, CandidatePhase::CompactionMarker, participant_id);
294 let mut prevalidated = ClaimFrontiers::prevalidate(
295 ClaimFrontiersRestore {
296 conversation_id,
297 active_identities: vec![FrontierParticipant::new(
298 participant_id,
299 cursor,
300 target_binding,
301 )],
302 identity_slot_limit,
303 retained_floor: u128::from(marker_delivery_seq),
304 retained_record_limit: 1,
305 retained_records: vec![RetainedCausalRecord {
306 delivery_seq: marker_delivery_seq,
307 admission_order,
308 kind: RetainedCausalRecordKind::CompactionMarker {
309 participant_index: participant_id,
310 provenance: MarkerProvenance::NonProductM,
311 },
312 }],
313 active_marker_anchors: vec![marker_delivery_seq],
314 historical_marker_deliveries: vec![],
315 historical_causal_facts: vec![],
316 sequence: SequenceClaimFrontierRestore {
317 movable_claims: sequence_movable,
318 immutable_candidates: Vec::<ImmutableSequenceCandidate>::new(),
319 products,
320 recovery: None,
321 },
322 order: OrderClaimFrontierRestore {
323 movable_claims: order_movable,
324 immutable_candidates: vec![],
325 recovery: None,
326 },
327 recovery_marker_delivery_seq: None,
328 },
329 sequence_ledger,
330 order_ledger,
331 )
332 .expect("complete test claim frontier must prevalidate");
333 let record = prevalidated
334 .take_marker_record(MarkerRecordRequest::planned(
335 participant_id,
336 marker_delivery_seq,
337 target_binding,
338 ))
339 .expect("prevalidated test frontier retains its exact marker");
340 if cursor >= marker_delivery_seq {
341 record.delivered_for_test()
342 } else {
343 record
344 }
345}
346
347#[cfg(test)]
348pub fn marker_delivery_for_test(
349 participant_id: ParticipantId,
350 binding_epoch: BindingEpoch,
351 marker_delivery_seq: DeliverySeq,
352) -> Result<MarkerDelivery, super::storage::StorageRestoreError> {
353 let record = validated_marker_record_for_test(
354 1,
355 participant_id,
356 super::claim_frontier::FrontierBinding::Bound(binding_epoch),
357 marker_delivery_seq,
358 marker_delivery_seq.saturating_sub(1),
359 );
360 super::storage::MarkerDeliveryRestore {
361 participant_id,
362 binding_epoch,
363 marker_delivery_seq,
364 }
365 .restore_bound(1, record)
366}
367
368#[derive(Clone, Copy, Debug, PartialEq, Eq)]
386pub struct CursorProgressContinuous {
387 participant_id: ParticipantId,
388 binding_epoch: BindingEpoch,
389 through_seq: DeliverySeq,
390}
391
392impl CursorProgressContinuous {
393 #[cfg(test)]
395 #[must_use]
396 pub(crate) const fn new(
397 participant_id: ParticipantId,
398 binding_epoch: BindingEpoch,
399 through_seq: DeliverySeq,
400 ) -> Self {
401 Self {
402 participant_id,
403 binding_epoch,
404 through_seq,
405 }
406 }
407
408 #[must_use]
410 pub const fn participant_id(self) -> ParticipantId {
411 self.participant_id
412 }
413
414 #[must_use]
416 pub const fn binding_epoch(self) -> BindingEpoch {
417 self.binding_epoch
418 }
419
420 #[must_use]
422 pub const fn through_seq(self) -> DeliverySeq {
423 self.through_seq
424 }
425}
426
427#[derive(Clone, Copy, Debug, PartialEq, Eq)]
434pub struct CursorProgressMarker {
435 conversation_id: ConversationId,
436 participant_id: ParticipantId,
437 binding_epoch: BindingEpoch,
438 through_seq: DeliverySeq,
439 marker_delivery_seq: DeliverySeq,
440}
441
442impl CursorProgressMarker {
443 #[must_use]
445 pub const fn conversation_id(self) -> ConversationId {
446 self.conversation_id
447 }
448
449 #[must_use]
451 pub const fn participant_id(self) -> ParticipantId {
452 self.participant_id
453 }
454
455 #[must_use]
457 pub const fn binding_epoch(self) -> BindingEpoch {
458 self.binding_epoch
459 }
460
461 #[must_use]
463 pub const fn through_seq(self) -> DeliverySeq {
464 self.through_seq
465 }
466
467 #[must_use]
469 pub const fn marker_delivery_seq(self) -> DeliverySeq {
470 self.marker_delivery_seq
471 }
472}
473
474#[derive(Clone, Copy, Debug, PartialEq, Eq)]
490pub enum ParticipantCursorProgress {
491 Continuous(CursorProgressContinuous),
493 Marker(CursorProgressMarker),
495}
496
497impl ParticipantCursorProgress {
498 #[cfg(test)]
500 #[must_use]
501 pub(crate) const fn continuous(
502 participant_id: ParticipantId,
503 binding_epoch: BindingEpoch,
504 through_seq: DeliverySeq,
505 ) -> Self {
506 Self::Continuous(CursorProgressContinuous::new(
507 participant_id,
508 binding_epoch,
509 through_seq,
510 ))
511 }
512
513 pub(super) fn restore_continuous(
514 authority: OrdinaryBindingAuthority,
515 participant_id: ParticipantId,
516 binding_epoch: BindingEpoch,
517 through_seq: DeliverySeq,
518 ) -> Option<Self> {
519 if authority.binding.participant_id != participant_id
520 || authority.binding.binding_epoch != binding_epoch
521 || authority.through_seq != through_seq
522 {
523 return None;
524 }
525 Some(Self::Continuous(CursorProgressContinuous {
526 participant_id,
527 binding_epoch,
528 through_seq,
529 }))
530 }
531
532 #[must_use]
534 pub const fn participant_id(self) -> ParticipantId {
535 match self {
536 Self::Continuous(value) => value.participant_id,
537 Self::Marker(value) => value.participant_id,
538 }
539 }
540
541 #[must_use]
543 pub const fn binding_epoch(self) -> BindingEpoch {
544 match self {
545 Self::Continuous(value) => value.binding_epoch,
546 Self::Marker(value) => value.binding_epoch,
547 }
548 }
549
550 #[must_use]
552 pub const fn through_seq(self) -> DeliverySeq {
553 match self {
554 Self::Continuous(value) => value.through_seq,
555 Self::Marker(value) => value.through_seq,
556 }
557 }
558
559 #[must_use]
561 pub const fn marker_delivery_seq(self) -> Option<DeliverySeq> {
562 match self {
563 Self::Continuous(_) => None,
564 Self::Marker(value) => Some(value.marker_delivery_seq),
565 }
566 }
567}
568
569#[derive(Clone, Copy, Debug, PartialEq, Eq)]
574pub struct DetachedCredentialRecovery {
575 conversation_id: ConversationId,
576 participant_id: ParticipantId,
577 marker_delivery_seq: DeliverySeq,
578 prior_binding_epoch: BindingEpoch,
579}
580
581impl DetachedCredentialRecovery {
582 #[must_use]
584 pub const fn conversation_id(self) -> ConversationId {
585 self.conversation_id
586 }
587
588 #[must_use]
590 pub const fn participant_id(self) -> ParticipantId {
591 self.participant_id
592 }
593
594 #[must_use]
596 pub const fn marker_delivery_seq(self) -> DeliverySeq {
597 self.marker_delivery_seq
598 }
599
600 #[must_use]
602 pub const fn prior_binding_epoch(self) -> BindingEpoch {
603 self.prior_binding_epoch
604 }
605}
606
607#[derive(Clone, Copy, Debug, PartialEq, Eq)]
612pub struct DetachedMarkerRelease {
613 participant_id: ParticipantId,
614 marker_delivery_seq: DeliverySeq,
615 last_dead_binding_epoch: BindingEpoch,
616}
617
618impl DetachedMarkerRelease {
619 #[must_use]
621 pub const fn participant_id(self) -> ParticipantId {
622 self.participant_id
623 }
624
625 #[must_use]
627 pub const fn marker_delivery_seq(self) -> DeliverySeq {
628 self.marker_delivery_seq
629 }
630
631 #[must_use]
633 pub const fn last_dead_binding_epoch(self) -> BindingEpoch {
634 self.last_dead_binding_epoch
635 }
636}
637
638#[derive(Clone, Copy, Debug, PartialEq, Eq)]
643pub struct DetachedCursorRelease {
644 participant_id: ParticipantId,
645 last_dead_binding_epoch: BindingEpoch,
646}
647
648impl DetachedCursorRelease {
649 #[must_use]
651 pub const fn participant_id(self) -> ParticipantId {
652 self.participant_id
653 }
654
655 #[must_use]
657 pub const fn last_dead_binding_epoch(self) -> BindingEpoch {
658 self.last_dead_binding_epoch
659 }
660}
661
662#[derive(Clone, Copy, Debug, PartialEq, Eq)]
664pub enum StoredEdge {
665 ObserverProjection(ObserverProjection),
667 PhysicalCompaction(PhysicalCompaction),
669 MarkerDelivery(MarkerDelivery),
671 ParticipantCursorProgress(ParticipantCursorProgress),
673 DetachedCredentialRecovery(DetachedCredentialRecovery),
675 DetachedMarkerRelease(DetachedMarkerRelease),
677 DetachedCursorRelease(DetachedCursorRelease),
679}
680
681#[derive(Clone, Copy, Debug, PartialEq, Eq)]
683pub enum ClosureState {
684 Clear,
686 Owed {
688 debt: ClosureDebt,
690 edge: StoredEdge,
692 },
693}
694
695#[derive(Clone, Copy, Debug, PartialEq, Eq)]
701pub struct OrdinaryDetachedAttachAdmission {
702 _private: (),
703}
704
705impl ClosureState {
706 pub const fn ordinary_detached_attach_admission(
713 self,
714 ) -> Result<OrdinaryDetachedAttachAdmission, Self> {
715 match self {
716 Self::Clear => Ok(OrdinaryDetachedAttachAdmission { _private: () }),
717 Self::Owed { .. } => Err(self),
718 }
719 }
720}
721
722#[derive(Clone, Copy, Debug, PartialEq, Eq)]
728pub struct DebtCompletion(ClosureState);
729
730impl DebtCompletion {
731 #[must_use]
733 pub const fn clear() -> Self {
734 Self(ClosureState::Clear)
735 }
736
737 #[must_use]
739 pub const fn observer_projection(debt: ClosureDebt, edge: ObserverProjection) -> Self {
740 Self(ClosureState::Owed {
741 debt,
742 edge: StoredEdge::ObserverProjection(edge),
743 })
744 }
745
746 #[must_use]
748 pub const fn physical_compaction(debt: ClosureDebt, edge: PhysicalCompaction) -> Self {
749 Self(ClosureState::Owed {
750 debt,
751 edge: StoredEdge::PhysicalCompaction(edge),
752 })
753 }
754
755 #[must_use]
757 pub const fn into_state(self) -> ClosureState {
758 self.0
759 }
760}
761
762#[derive(Clone, Copy, Debug, PartialEq, Eq)]
805pub struct OrdinaryBindingAuthority {
806 binding: ActiveBinding,
807 through_seq: DeliverySeq,
808}
809
810impl OrdinaryBindingAuthority {
811 pub(crate) const fn new(binding: ActiveBinding, through_seq: DeliverySeq) -> Self {
812 Self {
813 binding,
814 through_seq,
815 }
816 }
817
818 #[must_use]
820 pub const fn binding(self) -> ActiveBinding {
821 self.binding
822 }
823
824 #[must_use]
826 pub const fn through_seq(self) -> DeliverySeq {
827 self.through_seq
828 }
829
830 #[allow(
840 dead_code,
841 reason = "the crate-owned participant-ack operation advances this sealed authority"
842 )]
843 pub(crate) fn cursor_progressed(self, event: Event) -> Result<Self, Self> {
844 let EventKind::CursorProgressed {
845 participant_id,
846 binding_epoch,
847 progress:
848 CursorProgressEvent::Normal {
849 previous_cursor,
850 through_seq,
851 },
852 ..
853 } = event.0
854 else {
855 return Err(self);
856 };
857 if participant_id != self.binding.participant_id
858 || binding_epoch != self.binding.binding_epoch
859 || previous_cursor != self.through_seq
860 {
861 return Err(self);
862 }
863 Ok(Self {
864 through_seq,
865 ..self
866 })
867 }
868
869 pub(crate) fn binding_fate(
876 self,
877 terminal: CommittedDiedTerminal,
878 resulting_floor: DeliverySeq,
879 ) -> Result<OrdinaryBindingFate, Self> {
880 if terminal.participant_id() != self.binding.participant_id
881 || terminal.conversation_id() != self.binding.conversation_id
882 || terminal.binding_epoch() != self.binding.binding_epoch
883 {
884 return Err(self);
885 }
886 Ok(OrdinaryBindingFate {
887 conversation_id: self.binding.conversation_id,
888 through_seq: self.through_seq,
889 resulting_floor,
890 release: DetachedCursorRelease {
891 participant_id: self.binding.participant_id,
892 last_dead_binding_epoch: self.binding.binding_epoch,
893 },
894 })
895 }
896}
897
898#[derive(Clone, Copy, Debug, PartialEq, Eq)]
905pub struct OrdinaryBindingFate {
906 conversation_id: ConversationId,
907 through_seq: DeliverySeq,
908 resulting_floor: DeliverySeq,
909 release: DetachedCursorRelease,
910}
911
912impl OrdinaryBindingFate {
913 #[must_use]
915 pub const fn conversation_id(self) -> ConversationId {
916 self.conversation_id
917 }
918
919 #[must_use]
921 pub const fn through_seq(self) -> DeliverySeq {
922 self.through_seq
923 }
924
925 #[must_use]
927 pub const fn participant_id(self) -> ParticipantId {
928 self.release.participant_id
929 }
930
931 #[must_use]
933 pub const fn last_dead_binding_epoch(self) -> BindingEpoch {
934 self.release.last_dead_binding_epoch
935 }
936
937 #[must_use]
939 pub const fn resulting_floor(self) -> DeliverySeq {
940 self.resulting_floor
941 }
942 #[must_use]
944 pub const fn observer_progress_projection(&self) -> ObserverProgressProjection {
945 ObserverProgressProjection::new(self.conversation_id, self.resulting_floor)
946 }
947
948 #[must_use]
950 pub const fn into_direct_state(self, debt: ClosureDebt) -> ClosureState {
951 owed(debt, StoredEdge::DetachedCursorRelease(self.release))
952 }
953}
954
955#[derive(Clone, Copy, Debug, PartialEq, Eq)]
961pub struct FencedAttachCommit {
962 conversation_id: ConversationId,
963 participant_id: ParticipantId,
964 marker_delivery_seq: DeliverySeq,
965 prior_binding_epoch: BindingEpoch,
966 new_binding_epoch: BindingEpoch,
967 next_state: ClosureState,
968}
969
970impl FencedAttachCommit {
971 #[must_use]
973 pub const fn conversation_id(self) -> ConversationId {
974 self.conversation_id
975 }
976
977 #[must_use]
979 pub const fn participant_id(self) -> ParticipantId {
980 self.participant_id
981 }
982
983 #[must_use]
985 pub const fn marker_delivery_seq(self) -> DeliverySeq {
986 self.marker_delivery_seq
987 }
988
989 #[must_use]
991 pub const fn prior_binding_epoch(self) -> BindingEpoch {
992 self.prior_binding_epoch
993 }
994
995 #[must_use]
997 pub const fn new_binding_epoch(self) -> BindingEpoch {
998 self.new_binding_epoch
999 }
1000
1001 #[must_use]
1003 pub const fn next_state(self) -> ClosureState {
1004 self.next_state
1005 }
1006
1007 pub fn recovered_binding_fate(
1020 self,
1021 event: Event,
1022 ) -> Result<RecoveredBindingFate, ClosureState> {
1023 let EventKind::BindingFateObserved {
1024 participant_id,
1025 binding_epoch,
1026 resulting_floor,
1027 } = event.0
1028 else {
1029 return Err(self.next_state);
1030 };
1031 if participant_id != self.participant_id || binding_epoch != self.new_binding_epoch {
1032 return Err(self.next_state);
1033 }
1034 let ClosureState::Owed { debt, edge } = self.next_state else {
1035 return Err(self.next_state);
1036 };
1037 let predecessor = match edge {
1038 StoredEdge::ObserverProjection(value) => {
1039 RecoveredStorageEdge::ObserverProjection(value)
1040 }
1041 StoredEdge::PhysicalCompaction(value) => {
1042 RecoveredStorageEdge::PhysicalCompaction(value)
1043 }
1044 _ => return Err(self.next_state),
1045 };
1046 Ok(RecoveredBindingFate {
1047 conversation_id: self.conversation_id,
1048 predecessor_debt: debt,
1049 predecessor,
1050 resulting_floor,
1051 release: DetachedCursorRelease {
1052 participant_id,
1053 last_dead_binding_epoch: binding_epoch,
1054 },
1055 })
1056 }
1057}
1058
1059#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1060enum RecoveredStorageEdge {
1061 ObserverProjection(ObserverProjection),
1062 PhysicalCompaction(PhysicalCompaction),
1063}
1064
1065impl RecoveredStorageEdge {
1066 const fn into_stored_edge(self) -> StoredEdge {
1067 match self {
1068 Self::ObserverProjection(value) => StoredEdge::ObserverProjection(value),
1069 Self::PhysicalCompaction(value) => StoredEdge::PhysicalCompaction(value),
1070 }
1071 }
1072}
1073
1074#[derive(Debug, PartialEq, Eq)]
1080pub struct RecoveredBindingFate {
1081 conversation_id: ConversationId,
1082 predecessor_debt: ClosureDebt,
1083 predecessor: RecoveredStorageEdge,
1084 resulting_floor: DeliverySeq,
1085 release: DetachedCursorRelease,
1086}
1087
1088impl RecoveredBindingFate {
1089 #[must_use]
1091 pub const fn conversation_id(&self) -> ConversationId {
1092 self.conversation_id
1093 }
1094
1095 #[must_use]
1097 pub const fn predecessor_state(&self) -> ClosureState {
1098 owed(self.predecessor_debt, self.predecessor.into_stored_edge())
1099 }
1100
1101 #[must_use]
1103 pub const fn participant_id(&self) -> ParticipantId {
1104 self.release.participant_id
1105 }
1106
1107 #[must_use]
1109 pub const fn last_dead_binding_epoch(&self) -> BindingEpoch {
1110 self.release.last_dead_binding_epoch
1111 }
1112
1113 #[must_use]
1115 pub const fn resulting_floor(&self) -> DeliverySeq {
1116 self.resulting_floor
1117 }
1118 #[must_use]
1120 pub const fn observer_progress_projection(&self) -> ObserverProgressProjection {
1121 ObserverProgressProjection::new(self.conversation_id, self.resulting_floor)
1122 }
1123}
1124
1125#[derive(Debug, PartialEq, Eq)]
1132pub struct PendingRecoveredCursorRelease {
1133 debt: ClosureDebt,
1134 predecessor: RecoveredStorageEdge,
1135 release: DetachedCursorRelease,
1136}
1137
1138impl PendingRecoveredCursorRelease {
1139 #[must_use]
1141 pub const fn current_state(&self) -> ClosureState {
1142 owed(self.debt, self.predecessor.into_stored_edge())
1143 }
1144
1145 #[must_use]
1147 pub const fn participant_id(&self) -> ParticipantId {
1148 self.release.participant_id
1149 }
1150
1151 #[must_use]
1153 pub const fn last_dead_binding_epoch(&self) -> BindingEpoch {
1154 self.release.last_dead_binding_epoch
1155 }
1156}
1157
1158#[derive(Debug, PartialEq, Eq)]
1160pub struct RecoveredCursorRelease {
1161 debt: ClosureDebt,
1162 release: DetachedCursorRelease,
1163}
1164
1165impl RecoveredCursorRelease {
1166 #[must_use]
1168 pub const fn debt(&self) -> ClosureDebt {
1169 self.debt
1170 }
1171
1172 #[must_use]
1174 pub const fn edge(&self) -> DetachedCursorRelease {
1175 self.release
1176 }
1177
1178 #[must_use]
1180 pub const fn into_state(self) -> ClosureState {
1181 owed(self.debt, StoredEdge::DetachedCursorRelease(self.release))
1182 }
1183}
1184
1185#[derive(Debug, PartialEq, Eq)]
1187pub enum RecoveredBindingFateTransition {
1188 PendingStorage(PendingRecoveredCursorRelease),
1190 DetachedCursorRelease(RecoveredCursorRelease),
1192}
1193
1194#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1195enum SuccessorUse {
1196 ObserverCompletion,
1197 ObserverMarkerAppend,
1198 ObserverLeave,
1199 PhysicalCompletion,
1200 PhysicalCover,
1201 CursorGreaterAck,
1202}
1203
1204#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1210pub struct ProjectionCompactionSuccessor {
1211 predecessor: StoredEdge,
1212 event: Event,
1213 use_kind: SuccessorUse,
1214 state: ClosureState,
1215}
1216
1217#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1224pub enum CursorFateSuccessor {
1225 DetachedCredentialRecovery(DetachedCredentialRecovery),
1227 DetachedCursorRelease(DetachedCursorRelease),
1229}
1230
1231impl CursorFateSuccessor {
1232 #[must_use]
1234 pub const fn into_stored_edge(self) -> StoredEdge {
1235 match self {
1236 Self::DetachedCredentialRecovery(value) => {
1237 StoredEdge::DetachedCredentialRecovery(value)
1238 }
1239 Self::DetachedCursorRelease(value) => StoredEdge::DetachedCursorRelease(value),
1240 }
1241 }
1242}
1243
1244#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1246pub enum DetachedAttachRefusal {
1247 RecoveryFence,
1249 MarkerNotDelivered,
1251 MarkerMismatch,
1253 DeliveredMarkerAwaitingAck,
1255 EpisodeChurnLimit,
1257 StaleAuthority,
1259 NoBinding,
1261}
1262
1263#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1264enum DetachedClaimTarget {
1265 CredentialRecovery {
1266 marker_delivery_seq: DeliverySeq,
1267 binding_epoch: BindingEpoch,
1268 },
1269 MarkerRelease {
1270 marker_delivery_seq: DeliverySeq,
1271 binding_epoch: BindingEpoch,
1272 },
1273 CursorRelease {
1274 binding_epoch: BindingEpoch,
1275 },
1276}
1277
1278#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1284pub struct KClaimBackedDetachedLeave {
1285 participant_id: ParticipantId,
1286 target: DetachedClaimTarget,
1287 actual_charge: ResourceVector,
1288}
1289
1290impl KClaimBackedDetachedLeave {
1291 #[must_use]
1293 pub const fn participant_id(self) -> ParticipantId {
1294 self.participant_id
1295 }
1296
1297 #[must_use]
1299 pub const fn actual_charge(self) -> ResourceVector {
1300 self.actual_charge
1301 }
1302}
1303
1304#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1313pub struct Event(EventKind);
1314
1315#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1316enum EventKind {
1317 ProjectionCompleted {
1318 through_seq: DeliverySeq,
1319 },
1320 CompactionCompleted {
1321 from_floor: DeliverySeq,
1322 through_seq: DeliverySeq,
1323 resulting_floor: DeliverySeq,
1324 },
1325 MarkerAppended {
1326 marker_delivery_seq: DeliverySeq,
1327 resulting_projection_through: DeliverySeq,
1328 },
1329 MarkerDelivered {
1330 participant_id: ParticipantId,
1331 binding_epoch: BindingEpoch,
1332 marker_delivery_seq: DeliverySeq,
1333 },
1334 CursorProgressed {
1335 participant_index: ParticipantIndex,
1336 participant_id: ParticipantId,
1337 binding_epoch: BindingEpoch,
1338 progress: CursorProgressEvent,
1339 resulting_floor: DeliverySeq,
1340 },
1341 BindingFateObserved {
1342 participant_id: ParticipantId,
1343 binding_epoch: BindingEpoch,
1344 resulting_floor: DeliverySeq,
1345 },
1346 LeaveCommitted {
1347 participant_id: ParticipantId,
1348 authority: LeaveAuthority,
1349 resulting_floor: DeliverySeq,
1350 },
1351 FencedRecoveryCommitted {
1352 participant_id: ParticipantId,
1353 marker_delivery_seq: DeliverySeq,
1354 prior_binding_epoch: BindingEpoch,
1355 new_binding_epoch: BindingEpoch,
1356 resulting_floor: DeliverySeq,
1357 },
1358}
1359
1360#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1361enum CursorProgressEvent {
1362 Normal {
1363 previous_cursor: DeliverySeq,
1364 through_seq: DeliverySeq,
1365 },
1366 Marker {
1367 marker_delivery_seq: DeliverySeq,
1368 },
1369}
1370
1371#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1372enum LeaveAuthority {
1373 Live(BindingEpoch),
1374 Detached,
1375}
1376
1377impl Event {
1378 #[must_use]
1380 pub const fn projection_completed(through_seq: DeliverySeq) -> Self {
1381 Self(EventKind::ProjectionCompleted { through_seq })
1382 }
1383
1384 #[must_use]
1386 pub const fn compaction_completed(
1387 from_floor: DeliverySeq,
1388 through_seq: DeliverySeq,
1389 resulting_floor: DeliverySeq,
1390 ) -> Option<Self> {
1391 if from_floor <= through_seq && resulting_floor > through_seq {
1392 Some(Self(EventKind::CompactionCompleted {
1393 from_floor,
1394 through_seq,
1395 resulting_floor,
1396 }))
1397 } else {
1398 None
1399 }
1400 }
1401
1402 #[must_use]
1404 pub const fn marker_appended(
1405 marker_delivery_seq: DeliverySeq,
1406 resulting_projection_through: DeliverySeq,
1407 ) -> Self {
1408 Self(EventKind::MarkerAppended {
1409 marker_delivery_seq,
1410 resulting_projection_through,
1411 })
1412 }
1413
1414 #[must_use]
1416 pub const fn marker_delivered(
1417 participant_id: ParticipantId,
1418 binding_epoch: BindingEpoch,
1419 marker_delivery_seq: DeliverySeq,
1420 ) -> Self {
1421 Self(EventKind::MarkerDelivered {
1422 participant_id,
1423 binding_epoch,
1424 marker_delivery_seq,
1425 })
1426 }
1427
1428 #[must_use]
1434 pub const fn cursor_progressed(
1435 participant_id: ParticipantId,
1436 binding_epoch: BindingEpoch,
1437 previous_cursor: DeliverySeq,
1438 through_seq: DeliverySeq,
1439 resulting_floor: DeliverySeq,
1440 ) -> Option<Self> {
1441 if through_seq > previous_cursor {
1442 Some(Self(EventKind::CursorProgressed {
1443 participant_index: participant_id,
1444 participant_id,
1445 binding_epoch,
1446 progress: CursorProgressEvent::Normal {
1447 previous_cursor,
1448 through_seq,
1449 },
1450 resulting_floor,
1451 }))
1452 } else {
1453 None
1454 }
1455 }
1456
1457 #[must_use]
1460 pub const fn marker_acknowledged(
1461 participant_id: ParticipantId,
1462 binding_epoch: BindingEpoch,
1463 marker_delivery_seq: DeliverySeq,
1464 resulting_floor: DeliverySeq,
1465 ) -> Self {
1466 Self(EventKind::CursorProgressed {
1467 participant_index: participant_id,
1468 participant_id,
1469 binding_epoch,
1470 progress: CursorProgressEvent::Marker {
1471 marker_delivery_seq,
1472 },
1473 resulting_floor,
1474 })
1475 }
1476
1477 #[must_use]
1479 pub const fn binding_fate_observed(
1480 participant_id: ParticipantId,
1481 binding_epoch: BindingEpoch,
1482 resulting_floor: DeliverySeq,
1483 ) -> Self {
1484 Self(EventKind::BindingFateObserved {
1485 participant_id,
1486 binding_epoch,
1487 resulting_floor,
1488 })
1489 }
1490
1491 #[must_use]
1493 pub const fn live_leave_committed(
1494 participant_id: ParticipantId,
1495 binding_epoch: BindingEpoch,
1496 resulting_floor: DeliverySeq,
1497 ) -> Self {
1498 Self(EventKind::LeaveCommitted {
1499 participant_id,
1500 authority: LeaveAuthority::Live(binding_epoch),
1501 resulting_floor,
1502 })
1503 }
1504
1505 #[must_use]
1507 pub const fn detached_leave_committed(
1508 participant_id: ParticipantId,
1509 resulting_floor: DeliverySeq,
1510 ) -> Self {
1511 Self(EventKind::LeaveCommitted {
1512 participant_id,
1513 authority: LeaveAuthority::Detached,
1514 resulting_floor,
1515 })
1516 }
1517
1518 #[must_use]
1520 pub const fn fenced_recovery_committed(
1521 participant_id: ParticipantId,
1522 marker_delivery_seq: DeliverySeq,
1523 prior_binding_epoch: BindingEpoch,
1524 new_binding_epoch: BindingEpoch,
1525 resulting_floor: DeliverySeq,
1526 ) -> Self {
1527 Self(EventKind::FencedRecoveryCommitted {
1528 participant_id,
1529 marker_delivery_seq,
1530 prior_binding_epoch,
1531 new_binding_epoch,
1532 resulting_floor,
1533 })
1534 }
1535}
1536
1537impl ObserverProjection {
1538 #[must_use]
1544 #[allow(
1545 dead_code,
1546 reason = "the crate-owned binding-fate operation invokes this sealed OP transition"
1547 )]
1548 pub const fn apply_ordinary_binding_fate(
1549 self,
1550 resulting_debt: ClosureDebt,
1551 authority: OrdinaryBindingFate,
1552 ) -> PendingRecoveredCursorRelease {
1553 PendingRecoveredCursorRelease {
1554 debt: resulting_debt,
1555 predecessor: RecoveredStorageEdge::ObserverProjection(self),
1556 release: authority.release,
1557 }
1558 }
1559
1560 pub fn apply_recovered_binding_fate(
1571 self,
1572 debt: ClosureDebt,
1573 resulting_debt: ClosureDebt,
1574 authority: RecoveredBindingFate,
1575 ) -> Result<RecoveredBindingFateTransition, RecoveredBindingFate> {
1576 if authority.predecessor != RecoveredStorageEdge::ObserverProjection(self)
1577 || authority.predecessor_debt != debt
1578 {
1579 return Err(authority);
1580 }
1581 Ok(RecoveredBindingFateTransition::PendingStorage(
1582 PendingRecoveredCursorRelease {
1583 debt: resulting_debt,
1584 predecessor: RecoveredStorageEdge::ObserverProjection(self),
1585 release: authority.release,
1586 },
1587 ))
1588 }
1589
1590 pub fn complete_after_recovered_binding_fate(
1597 self,
1598 event: Event,
1599 resulting_debt: Option<ClosureDebt>,
1600 pending: PendingRecoveredCursorRelease,
1601 ) -> Result<ClosureState, PendingRecoveredCursorRelease> {
1602 self.complete_after_binding_fate(event, resulting_debt, pending)
1603 }
1604
1605 pub fn complete_after_ordinary_binding_fate(
1612 self,
1613 event: Event,
1614 resulting_debt: Option<ClosureDebt>,
1615 pending: PendingRecoveredCursorRelease,
1616 ) -> Result<ClosureState, PendingRecoveredCursorRelease> {
1617 self.complete_after_binding_fate(event, resulting_debt, pending)
1618 }
1619
1620 pub(crate) fn complete_after_binding_fate(
1627 self,
1628 event: Event,
1629 resulting_debt: Option<ClosureDebt>,
1630 pending: PendingRecoveredCursorRelease,
1631 ) -> Result<ClosureState, PendingRecoveredCursorRelease> {
1632 if pending.predecessor != RecoveredStorageEdge::ObserverProjection(self)
1633 || projection_completion_boundary(self, event).is_none()
1634 {
1635 return Err(pending);
1636 }
1637 Ok(preserve_or_clear(
1638 resulting_debt,
1639 StoredEdge::DetachedCursorRelease(pending.release),
1640 ))
1641 }
1642
1643 #[must_use]
1645 pub const fn clear_after_completion(
1646 &self,
1647 event: &Event,
1648 ) -> Option<ProjectionCompactionSuccessor> {
1649 if projection_completion_boundary(*self, *event).is_some() {
1650 Some(ProjectionCompactionSuccessor {
1651 predecessor: StoredEdge::ObserverProjection(*self),
1652 event: *event,
1653 use_kind: SuccessorUse::ObserverCompletion,
1654 state: ClosureState::Clear,
1655 })
1656 } else {
1657 None
1658 }
1659 }
1660
1661 #[must_use]
1663 pub const fn strict_after_completion(
1664 &self,
1665 event: &Event,
1666 debt: ClosureDebt,
1667 edge: StoredEdge,
1668 successor_boundary: DeliverySeq,
1669 ) -> Option<ProjectionCompactionSuccessor> {
1670 let Some(completed_through) = projection_completion_boundary(*self, *event) else {
1671 return None;
1672 };
1673 if successor_boundary <= completed_through
1674 || !strict_edge_matches_boundary(edge, successor_boundary)
1675 {
1676 return None;
1677 }
1678 Some(ProjectionCompactionSuccessor {
1679 predecessor: StoredEdge::ObserverProjection(*self),
1680 event: *event,
1681 use_kind: SuccessorUse::ObserverCompletion,
1682 state: ClosureState::Owed { debt, edge },
1683 })
1684 }
1685
1686 pub fn complete(
1693 self,
1694 debt: ClosureDebt,
1695 event: Event,
1696 successor: ProjectionCompactionSuccessor,
1697 ) -> Result<ClosureState, ClosureState> {
1698 let original = owed(debt, StoredEdge::ObserverProjection(self));
1699 if successor.predecessor == StoredEdge::ObserverProjection(self)
1700 && successor.event == event
1701 && successor.use_kind == SuccessorUse::ObserverCompletion
1702 && projection_completion_boundary(self, event).is_some()
1703 {
1704 Ok(successor.state)
1705 } else {
1706 Err(original)
1707 }
1708 }
1709
1710 #[must_use]
1712 pub const fn later_projection_after_marker(
1713 &self,
1714 event: &Event,
1715 debt: ClosureDebt,
1716 successor: Self,
1717 ) -> Option<ProjectionCompactionSuccessor> {
1718 let EventKind::MarkerAppended {
1719 marker_delivery_seq,
1720 resulting_projection_through,
1721 } = event.0
1722 else {
1723 return None;
1724 };
1725 if marker_delivery_seq <= self.through_seq
1726 || resulting_projection_through < marker_delivery_seq
1727 || successor.through_seq != resulting_projection_through
1728 {
1729 return None;
1730 }
1731 Some(ProjectionCompactionSuccessor {
1732 predecessor: StoredEdge::ObserverProjection(*self),
1733 event: *event,
1734 use_kind: SuccessorUse::ObserverMarkerAppend,
1735 state: owed(debt, StoredEdge::ObserverProjection(successor)),
1736 })
1737 }
1738
1739 pub fn marker_appended(
1746 self,
1747 debt: ClosureDebt,
1748 event: Event,
1749 successor: ProjectionCompactionSuccessor,
1750 ) -> Result<ClosureState, ClosureState> {
1751 let original = owed(debt, StoredEdge::ObserverProjection(self));
1752 if successor.predecessor == StoredEdge::ObserverProjection(self)
1753 && successor.event == event
1754 && successor.use_kind == SuccessorUse::ObserverMarkerAppend
1755 {
1756 Ok(successor.state)
1757 } else {
1758 Err(original)
1759 }
1760 }
1761
1762 #[must_use]
1764 pub const fn later_projection_after_leave(
1765 &self,
1766 event: &Event,
1767 debt: ClosureDebt,
1768 successor: Self,
1769 ) -> Option<ProjectionCompactionSuccessor> {
1770 let EventKind::LeaveCommitted {
1771 resulting_floor, ..
1772 } = event.0
1773 else {
1774 return None;
1775 };
1776 if successor.through_seq <= self.through_seq || successor.through_seq < resulting_floor {
1777 return None;
1778 }
1779 Some(ProjectionCompactionSuccessor {
1780 predecessor: StoredEdge::ObserverProjection(*self),
1781 event: *event,
1782 use_kind: SuccessorUse::ObserverLeave,
1783 state: owed(debt, StoredEdge::ObserverProjection(successor)),
1784 })
1785 }
1786
1787 pub fn leave_with_later_projection(
1794 self,
1795 debt: ClosureDebt,
1796 event: Event,
1797 successor: ProjectionCompactionSuccessor,
1798 ) -> Result<ClosureState, ClosureState> {
1799 let original = owed(debt, StoredEdge::ObserverProjection(self));
1800 if successor.predecessor == StoredEdge::ObserverProjection(self)
1801 && successor.event == event
1802 && successor.use_kind == SuccessorUse::ObserverLeave
1803 {
1804 Ok(successor.state)
1805 } else {
1806 Err(original)
1807 }
1808 }
1809
1810 pub const fn independent_event(
1818 self,
1819 debt: ClosureDebt,
1820 event: Event,
1821 resulting_debt: Option<ClosureDebt>,
1822 ) -> Result<ClosureState, ClosureState> {
1823 let original = owed(debt, StoredEdge::ObserverProjection(self));
1824 if !matches!(
1825 event.0,
1826 EventKind::CursorProgressed { .. }
1827 | EventKind::BindingFateObserved { .. }
1828 | EventKind::LeaveCommitted { .. }
1829 ) {
1830 return Err(original);
1831 }
1832 Ok(preserve_or_clear(
1833 resulting_debt,
1834 StoredEdge::ObserverProjection(self),
1835 ))
1836 }
1837
1838 pub const fn charged_binding_change(
1845 self,
1846 debt: ClosureDebt,
1847 episode_churn_used: u64,
1848 delta_cycles: u64,
1849 episode_churn_limit: u64,
1850 resulting_debt: Option<ClosureDebt>,
1851 ) -> Result<ClosureState, (ClosureState, DetachedAttachRefusal)> {
1852 let original = owed(debt, StoredEdge::ObserverProjection(self));
1853 if !charged_churn_fits(episode_churn_used, delta_cycles, episode_churn_limit) {
1854 return Err((original, DetachedAttachRefusal::EpisodeChurnLimit));
1855 }
1856 Ok(preserve_or_clear(
1857 resulting_debt,
1858 StoredEdge::ObserverProjection(self),
1859 ))
1860 }
1861}
1862
1863impl PhysicalCompaction {
1864 #[must_use]
1866 #[allow(
1867 dead_code,
1868 reason = "the crate-owned binding-fate replay boundary invokes this sealed PC transition"
1869 )]
1870 pub(crate) const fn apply_ordinary_binding_fate(
1871 self,
1872 resulting_debt: ClosureDebt,
1873 authority: OrdinaryBindingFate,
1874 ) -> RecoveredBindingFateTransition {
1875 if authority.resulting_floor > self.through_seq {
1876 RecoveredBindingFateTransition::DetachedCursorRelease(RecoveredCursorRelease {
1877 debt: resulting_debt,
1878 release: authority.release,
1879 })
1880 } else {
1881 RecoveredBindingFateTransition::PendingStorage(PendingRecoveredCursorRelease {
1882 debt: resulting_debt,
1883 predecessor: RecoveredStorageEdge::PhysicalCompaction(self),
1884 release: authority.release,
1885 })
1886 }
1887 }
1888
1889 pub fn apply_recovered_binding_fate(
1900 self,
1901 debt: ClosureDebt,
1902 resulting_debt: ClosureDebt,
1903 authority: RecoveredBindingFate,
1904 ) -> Result<RecoveredBindingFateTransition, RecoveredBindingFate> {
1905 if authority.predecessor != RecoveredStorageEdge::PhysicalCompaction(self)
1906 || authority.predecessor_debt != debt
1907 {
1908 return Err(authority);
1909 }
1910 if authority.resulting_floor > self.through_seq {
1911 Ok(RecoveredBindingFateTransition::DetachedCursorRelease(
1912 RecoveredCursorRelease {
1913 debt: resulting_debt,
1914 release: authority.release,
1915 },
1916 ))
1917 } else {
1918 Ok(RecoveredBindingFateTransition::PendingStorage(
1919 PendingRecoveredCursorRelease {
1920 debt: resulting_debt,
1921 predecessor: RecoveredStorageEdge::PhysicalCompaction(self),
1922 release: authority.release,
1923 },
1924 ))
1925 }
1926 }
1927
1928 pub fn complete_after_recovered_binding_fate(
1935 self,
1936 event: Event,
1937 resulting_debt: Option<ClosureDebt>,
1938 pending: PendingRecoveredCursorRelease,
1939 ) -> Result<ClosureState, PendingRecoveredCursorRelease> {
1940 self.complete_after_binding_fate(event, resulting_debt, pending)
1941 }
1942
1943 pub(crate) fn complete_after_binding_fate(
1950 self,
1951 event: Event,
1952 resulting_debt: Option<ClosureDebt>,
1953 pending: PendingRecoveredCursorRelease,
1954 ) -> Result<ClosureState, PendingRecoveredCursorRelease> {
1955 if pending.predecessor != RecoveredStorageEdge::PhysicalCompaction(self)
1956 || physical_completion_floor(self, event).is_none()
1957 {
1958 return Err(pending);
1959 }
1960 Ok(preserve_or_clear(
1961 resulting_debt,
1962 StoredEdge::DetachedCursorRelease(pending.release),
1963 ))
1964 }
1965
1966 #[must_use]
1968 pub const fn clear_after_completion(
1969 &self,
1970 event: &Event,
1971 ) -> Option<ProjectionCompactionSuccessor> {
1972 if physical_completion_floor(*self, *event).is_some() {
1973 Some(ProjectionCompactionSuccessor {
1974 predecessor: StoredEdge::PhysicalCompaction(*self),
1975 event: *event,
1976 use_kind: SuccessorUse::PhysicalCompletion,
1977 state: ClosureState::Clear,
1978 })
1979 } else {
1980 None
1981 }
1982 }
1983
1984 #[must_use]
1986 pub const fn strict_after_completion(
1987 &self,
1988 event: &Event,
1989 debt: ClosureDebt,
1990 edge: StoredEdge,
1991 successor_boundary: DeliverySeq,
1992 ) -> Option<ProjectionCompactionSuccessor> {
1993 let Some(resulting_floor) = physical_completion_floor(*self, *event) else {
1994 return None;
1995 };
1996 if successor_boundary < resulting_floor
1997 || !strict_edge_matches_boundary(edge, successor_boundary)
1998 {
1999 return None;
2000 }
2001 Some(ProjectionCompactionSuccessor {
2002 predecessor: StoredEdge::PhysicalCompaction(*self),
2003 event: *event,
2004 use_kind: SuccessorUse::PhysicalCompletion,
2005 state: ClosureState::Owed { debt, edge },
2006 })
2007 }
2008
2009 pub fn complete(
2016 self,
2017 debt: ClosureDebt,
2018 event: Event,
2019 successor: ProjectionCompactionSuccessor,
2020 ) -> Result<ClosureState, ClosureState> {
2021 let original = owed(debt, StoredEdge::PhysicalCompaction(self));
2022 if successor.predecessor == StoredEdge::PhysicalCompaction(self)
2023 && successor.event == event
2024 && successor.use_kind == SuccessorUse::PhysicalCompletion
2025 && physical_completion_floor(self, event).is_some()
2026 {
2027 Ok(successor.state)
2028 } else {
2029 Err(original)
2030 }
2031 }
2032
2033 pub const fn marker_appended(
2041 self,
2042 debt: ClosureDebt,
2043 event: Event,
2044 ) -> Result<ClosureState, ClosureState> {
2045 let original = owed(debt, StoredEdge::PhysicalCompaction(self));
2046 let EventKind::MarkerAppended {
2047 marker_delivery_seq,
2048 resulting_projection_through,
2049 } = event.0
2050 else {
2051 return Err(original);
2052 };
2053 if marker_delivery_seq <= self.through_seq
2054 || resulting_projection_through < marker_delivery_seq
2055 {
2056 return Err(original);
2057 }
2058 Ok(original)
2059 }
2060
2061 pub const fn preserve_progress(
2069 self,
2070 debt: ClosureDebt,
2071 event: Event,
2072 resulting_debt: ClosureDebt,
2073 ) -> Result<ClosureState, ClosureState> {
2074 let original = owed(debt, StoredEdge::PhysicalCompaction(self));
2075 let Some(resulting_floor) = progress_event_floor(event) else {
2076 return Err(original);
2077 };
2078 if resulting_floor > self.through_seq {
2079 return Err(original);
2080 }
2081 Ok(owed(resulting_debt, StoredEdge::PhysicalCompaction(self)))
2082 }
2083
2084 #[must_use]
2086 pub const fn clear_after_progress(
2087 &self,
2088 event: &Event,
2089 ) -> Option<ProjectionCompactionSuccessor> {
2090 let Some(resulting_floor) = progress_event_floor(*event) else {
2091 return None;
2092 };
2093 if resulting_floor <= self.through_seq {
2094 return None;
2095 }
2096 Some(ProjectionCompactionSuccessor {
2097 predecessor: StoredEdge::PhysicalCompaction(*self),
2098 event: *event,
2099 use_kind: SuccessorUse::PhysicalCover,
2100 state: ClosureState::Clear,
2101 })
2102 }
2103
2104 #[must_use]
2106 pub const fn strict_after_progress(
2107 &self,
2108 event: &Event,
2109 debt: ClosureDebt,
2110 edge: StoredEdge,
2111 successor_boundary: DeliverySeq,
2112 ) -> Option<ProjectionCompactionSuccessor> {
2113 let Some(resulting_floor) = progress_event_floor(*event) else {
2114 return None;
2115 };
2116 if resulting_floor <= self.through_seq
2117 || successor_boundary < resulting_floor
2118 || !strict_edge_matches_boundary(edge, successor_boundary)
2119 {
2120 return None;
2121 }
2122 Some(ProjectionCompactionSuccessor {
2123 predecessor: StoredEdge::PhysicalCompaction(*self),
2124 event: *event,
2125 use_kind: SuccessorUse::PhysicalCover,
2126 state: ClosureState::Owed { debt, edge },
2127 })
2128 }
2129
2130 pub fn covered_by_progress(
2137 self,
2138 debt: ClosureDebt,
2139 event: Event,
2140 successor: ProjectionCompactionSuccessor,
2141 ) -> Result<ClosureState, ClosureState> {
2142 let original = owed(debt, StoredEdge::PhysicalCompaction(self));
2143 if successor.predecessor == StoredEdge::PhysicalCompaction(self)
2144 && successor.event == event
2145 && successor.use_kind == SuccessorUse::PhysicalCover
2146 {
2147 Ok(successor.state)
2148 } else {
2149 Err(original)
2150 }
2151 }
2152
2153 #[must_use]
2155 pub const fn unchanged(self, debt: ClosureDebt) -> ClosureState {
2156 owed(debt, StoredEdge::PhysicalCompaction(self))
2157 }
2158
2159 pub const fn charged_binding_change_preserving(
2166 self,
2167 debt: ClosureDebt,
2168 episode_churn_used: u64,
2169 delta_cycles: u64,
2170 episode_churn_limit: u64,
2171 resulting_floor: DeliverySeq,
2172 resulting_debt: ClosureDebt,
2173 ) -> Result<ClosureState, (ClosureState, DetachedAttachRefusal)> {
2174 let original = owed(debt, StoredEdge::PhysicalCompaction(self));
2175 if !charged_churn_fits(episode_churn_used, delta_cycles, episode_churn_limit) {
2176 return Err((original, DetachedAttachRefusal::EpisodeChurnLimit));
2177 }
2178 if resulting_floor > self.through_seq {
2179 return Err((original, DetachedAttachRefusal::StaleAuthority));
2180 }
2181 Ok(owed(resulting_debt, StoredEdge::PhysicalCompaction(self)))
2182 }
2183
2184 #[allow(clippy::too_many_arguments)]
2191 pub const fn charged_binding_change_covering(
2192 self,
2193 debt: ClosureDebt,
2194 episode_churn_used: u64,
2195 delta_cycles: u64,
2196 episode_churn_limit: u64,
2197 resulting_floor: DeliverySeq,
2198 resulting_debt: ClosureDebt,
2199 edge: StoredEdge,
2200 successor_boundary: DeliverySeq,
2201 ) -> Result<ClosureState, (ClosureState, DetachedAttachRefusal)> {
2202 let original = owed(debt, StoredEdge::PhysicalCompaction(self));
2203 if !charged_churn_fits(episode_churn_used, delta_cycles, episode_churn_limit) {
2204 return Err((original, DetachedAttachRefusal::EpisodeChurnLimit));
2205 }
2206 if resulting_floor <= self.through_seq
2207 || successor_boundary < resulting_floor
2208 || !strict_edge_matches_boundary(edge, successor_boundary)
2209 {
2210 return Err((original, DetachedAttachRefusal::StaleAuthority));
2211 }
2212 Ok(owed(resulting_debt, edge))
2213 }
2214}
2215
2216impl MarkerDelivery {
2217 pub fn delivered_progress(self, event: Event) -> Result<ParticipantCursorProgress, Self> {
2230 let EventKind::MarkerDelivered {
2231 participant_id,
2232 binding_epoch,
2233 marker_delivery_seq,
2234 } = event.0
2235 else {
2236 return Err(self);
2237 };
2238 if participant_id != self.participant_id
2239 || binding_epoch != self.binding_epoch
2240 || marker_delivery_seq != self.marker_delivery_seq
2241 {
2242 return Err(self);
2243 }
2244 Ok(ParticipantCursorProgress::Marker(CursorProgressMarker {
2245 conversation_id: self.conversation_id,
2246 participant_id,
2247 binding_epoch,
2248 through_seq: marker_delivery_seq,
2249 marker_delivery_seq,
2250 }))
2251 }
2252
2253 pub fn delivered(self, debt: ClosureDebt, event: Event) -> Result<ClosureState, ClosureState> {
2263 let original = owed(debt, StoredEdge::MarkerDelivery(self));
2264 let Ok(progress) = self.delivered_progress(event) else {
2265 return Err(original);
2266 };
2267 Ok(owed(debt, StoredEdge::ParticipantCursorProgress(progress)))
2268 }
2269
2270 pub const fn lower_progress(
2278 self,
2279 debt: ClosureDebt,
2280 event: Event,
2281 resulting_debt: Option<ClosureDebt>,
2282 ) -> Result<ClosureState, ClosureState> {
2283 let original = owed(debt, StoredEdge::MarkerDelivery(self));
2284 let is_lower = match event.0 {
2285 EventKind::CursorProgressed {
2286 progress: CursorProgressEvent::Normal { through_seq, .. },
2287 ..
2288 }
2289 | EventKind::ProjectionCompleted { through_seq } => {
2290 through_seq < self.marker_delivery_seq
2291 }
2292 EventKind::CompactionCompleted {
2293 through_seq,
2294 resulting_floor,
2295 ..
2296 } => {
2297 through_seq < self.marker_delivery_seq
2298 && resulting_floor <= self.marker_delivery_seq
2299 }
2300 _ => false,
2301 };
2302 if !is_lower {
2303 return Err(original);
2304 }
2305 Ok(preserve_or_clear(
2306 resulting_debt,
2307 StoredEdge::MarkerDelivery(self),
2308 ))
2309 }
2310
2311 pub fn binding_fate(
2318 self,
2319 debt: ClosureDebt,
2320 event: Event,
2321 ) -> Result<ClosureState, ClosureState> {
2322 let original = owed(debt, StoredEdge::MarkerDelivery(self));
2323 let EventKind::BindingFateObserved {
2324 participant_id,
2325 binding_epoch,
2326 ..
2327 } = event.0
2328 else {
2329 return Err(original);
2330 };
2331 if participant_id != self.participant_id || binding_epoch != self.binding_epoch {
2332 return Err(original);
2333 }
2334 Ok(owed(
2335 debt,
2336 StoredEdge::DetachedMarkerRelease(DetachedMarkerRelease {
2337 participant_id,
2338 marker_delivery_seq: self.marker_delivery_seq,
2339 last_dead_binding_epoch: binding_epoch,
2340 }),
2341 ))
2342 }
2343
2344 pub const fn retarget(
2351 self,
2352 new_binding_epoch: BindingEpoch,
2353 episode_churn_used: u64,
2354 delta_cycles: u64,
2355 episode_churn_limit: u64,
2356 ) -> Result<Self, (Self, DetachedAttachRefusal)> {
2357 if !charged_churn_fits(episode_churn_used, delta_cycles, episode_churn_limit) {
2358 return Err((self, DetachedAttachRefusal::EpisodeChurnLimit));
2359 }
2360 if !is_next_generation(self.binding_epoch, new_binding_epoch) {
2361 return Err((self, DetachedAttachRefusal::StaleAuthority));
2362 }
2363 Ok(Self {
2364 binding_epoch: new_binding_epoch,
2365 ..self
2366 })
2367 }
2368
2369 pub fn leave(
2376 self,
2377 debt: ClosureDebt,
2378 event: Event,
2379 successor: DebtCompletion,
2380 ) -> Result<ClosureState, ClosureState> {
2381 let original = owed(debt, StoredEdge::MarkerDelivery(self));
2382 let EventKind::LeaveCommitted {
2383 participant_id,
2384 authority: LeaveAuthority::Live(binding_epoch),
2385 ..
2386 } = event.0
2387 else {
2388 return Err(original);
2389 };
2390 if participant_id != self.participant_id || binding_epoch != self.binding_epoch {
2391 return Err(original);
2392 }
2393 Ok(successor.into_state())
2394 }
2395}
2396
2397impl ParticipantCursorProgress {
2398 pub fn complete_ack(
2406 self,
2407 debt: ClosureDebt,
2408 event: Event,
2409 successor: DebtCompletion,
2410 ) -> Result<ClosureState, ClosureState> {
2411 let original = owed(debt, StoredEdge::ParticipantCursorProgress(self));
2412 let exact = match (self, event.0) {
2413 (
2414 Self::Continuous(value),
2415 EventKind::CursorProgressed {
2416 participant_id,
2417 binding_epoch,
2418 progress: CursorProgressEvent::Normal { through_seq, .. },
2419 ..
2420 },
2421 ) => {
2422 participant_id == value.participant_id
2423 && binding_epoch == value.binding_epoch
2424 && through_seq == value.through_seq
2425 }
2426 (
2427 Self::Marker(value),
2428 EventKind::CursorProgressed {
2429 participant_id,
2430 binding_epoch,
2431 progress: CursorProgressEvent::Normal { through_seq, .. },
2432 ..
2433 },
2434 ) => {
2435 participant_id == value.participant_id
2436 && binding_epoch == value.binding_epoch
2437 && through_seq == value.through_seq
2438 }
2439 (
2440 Self::Marker(value),
2441 EventKind::CursorProgressed {
2442 participant_id,
2443 binding_epoch,
2444 progress:
2445 CursorProgressEvent::Marker {
2446 marker_delivery_seq,
2447 },
2448 ..
2449 },
2450 ) => {
2451 participant_id == value.participant_id
2452 && binding_epoch == value.binding_epoch
2453 && marker_delivery_seq == value.marker_delivery_seq
2454 }
2455 _ => false,
2456 };
2457 if exact {
2458 Ok(successor.into_state())
2459 } else {
2460 Err(original)
2461 }
2462 }
2463
2464 pub fn lesser_ack(
2471 self,
2472 debt: ClosureDebt,
2473 event: Event,
2474 resulting_debt: ClosureDebt,
2475 ) -> Result<ClosureState, ClosureState> {
2476 let original = owed(debt, StoredEdge::ParticipantCursorProgress(self));
2477 let EventKind::CursorProgressed {
2478 participant_id,
2479 binding_epoch,
2480 progress: CursorProgressEvent::Normal { through_seq, .. },
2481 ..
2482 } = event.0
2483 else {
2484 return Err(original);
2485 };
2486 if participant_id != self.participant_id()
2487 || binding_epoch != self.binding_epoch()
2488 || through_seq >= self.through_seq()
2489 {
2490 return Err(original);
2491 }
2492 Ok(owed(
2493 resulting_debt,
2494 StoredEdge::ParticipantCursorProgress(self),
2495 ))
2496 }
2497
2498 #[must_use]
2500 pub fn clear_after_greater_ack(&self, event: &Event) -> Option<ProjectionCompactionSuccessor> {
2501 if !greater_ack_matches(*self, *event) {
2502 return None;
2503 }
2504 Some(ProjectionCompactionSuccessor {
2505 predecessor: StoredEdge::ParticipantCursorProgress(*self),
2506 event: *event,
2507 use_kind: SuccessorUse::CursorGreaterAck,
2508 state: ClosureState::Clear,
2509 })
2510 }
2511
2512 #[must_use]
2514 pub fn strict_after_greater_ack(
2515 &self,
2516 event: &Event,
2517 debt: ClosureDebt,
2518 edge: StoredEdge,
2519 successor_boundary: DeliverySeq,
2520 ) -> Option<ProjectionCompactionSuccessor> {
2521 if !greater_ack_matches(*self, *event)
2522 || successor_boundary <= cursor_event_boundary(*event)
2523 || !strict_edge_matches_boundary(edge, successor_boundary)
2524 {
2525 return None;
2526 }
2527 Some(ProjectionCompactionSuccessor {
2528 predecessor: StoredEdge::ParticipantCursorProgress(*self),
2529 event: *event,
2530 use_kind: SuccessorUse::CursorGreaterAck,
2531 state: ClosureState::Owed { debt, edge },
2532 })
2533 }
2534
2535 pub fn greater_ack(
2542 self,
2543 debt: ClosureDebt,
2544 event: Event,
2545 successor: ProjectionCompactionSuccessor,
2546 ) -> Result<ClosureState, ClosureState> {
2547 let original = owed(debt, StoredEdge::ParticipantCursorProgress(self));
2548 if successor.predecessor == StoredEdge::ParticipantCursorProgress(self)
2549 && successor.event == event
2550 && successor.use_kind == SuccessorUse::CursorGreaterAck
2551 && greater_ack_matches(self, event)
2552 {
2553 Ok(successor.state)
2554 } else {
2555 Err(original)
2556 }
2557 }
2558
2559 #[must_use]
2561 pub const fn unchanged(self, debt: ClosureDebt) -> ClosureState {
2562 owed(debt, StoredEdge::ParticipantCursorProgress(self))
2563 }
2564
2565 pub const fn storage_progress(
2574 self,
2575 debt: ClosureDebt,
2576 event: Event,
2577 resulting_debt: Option<ClosureDebt>,
2578 ) -> Result<ClosureState, ClosureState> {
2579 let original = owed(debt, StoredEdge::ParticipantCursorProgress(self));
2580 let valid = match event.0 {
2581 EventKind::ProjectionCompleted { .. } => true,
2582 EventKind::CompactionCompleted {
2583 through_seq,
2584 resulting_floor,
2585 ..
2586 } => match self.marker_delivery_seq() {
2587 None => true,
2588 Some(marker) => through_seq < marker && resulting_floor <= marker,
2589 },
2590 _ => false,
2591 };
2592 if !valid {
2593 return Err(original);
2594 }
2595 Ok(preserve_or_clear(
2596 resulting_debt,
2597 StoredEdge::ParticipantCursorProgress(self),
2598 ))
2599 }
2600
2601 pub fn binding_fate(
2613 self,
2614 debt: ClosureDebt,
2615 event: Event,
2616 ) -> Result<CursorFateSuccessor, ClosureState> {
2617 let original = owed(debt, StoredEdge::ParticipantCursorProgress(self));
2618 let Self::Marker(value) = self else {
2619 return Err(original);
2620 };
2621 let EventKind::BindingFateObserved {
2622 participant_id,
2623 binding_epoch,
2624 ..
2625 } = event.0
2626 else {
2627 return Err(original);
2628 };
2629 if participant_id != value.participant_id || binding_epoch != value.binding_epoch {
2630 return Err(original);
2631 }
2632 Ok(CursorFateSuccessor::DetachedCredentialRecovery(
2633 DetachedCredentialRecovery {
2634 conversation_id: value.conversation_id,
2635 participant_id: value.participant_id,
2636 marker_delivery_seq: value.marker_delivery_seq,
2637 prior_binding_epoch: value.binding_epoch,
2638 },
2639 ))
2640 }
2641
2642 pub const fn retarget(
2649 self,
2650 new_binding_epoch: BindingEpoch,
2651 episode_churn_used: u64,
2652 delta_cycles: u64,
2653 episode_churn_limit: u64,
2654 ) -> Result<Self, (Self, DetachedAttachRefusal)> {
2655 let Self::Continuous(value) = self else {
2656 return Err((self, DetachedAttachRefusal::DeliveredMarkerAwaitingAck));
2657 };
2658 if !charged_churn_fits(episode_churn_used, delta_cycles, episode_churn_limit) {
2659 return Err((self, DetachedAttachRefusal::EpisodeChurnLimit));
2660 }
2661 if !is_next_generation(value.binding_epoch, new_binding_epoch) {
2662 return Err((self, DetachedAttachRefusal::StaleAuthority));
2663 }
2664 Ok(Self::Continuous(CursorProgressContinuous {
2665 binding_epoch: new_binding_epoch,
2666 ..value
2667 }))
2668 }
2669
2670 pub fn leave(
2677 self,
2678 debt: ClosureDebt,
2679 event: Event,
2680 successor: DebtCompletion,
2681 ) -> Result<ClosureState, ClosureState> {
2682 let original = owed(debt, StoredEdge::ParticipantCursorProgress(self));
2683 let EventKind::LeaveCommitted {
2684 participant_id,
2685 authority: LeaveAuthority::Live(binding_epoch),
2686 ..
2687 } = event.0
2688 else {
2689 return Err(original);
2690 };
2691 if participant_id != self.participant_id() || binding_epoch != self.binding_epoch() {
2692 return Err(original);
2693 }
2694 Ok(successor.into_state())
2695 }
2696}
2697
2698impl DetachedCredentialRecovery {
2699 #[must_use]
2701 pub const fn validate_leave_claim(
2702 &self,
2703 participant_id: ParticipantId,
2704 actual_charge: ResourceVector,
2705 remaining_k: ResourceVector,
2706 exit_claims: u64,
2707 ) -> Option<KClaimBackedDetachedLeave> {
2708 validate_detached_claim(
2709 self.participant_id,
2710 DetachedClaimTarget::CredentialRecovery {
2711 marker_delivery_seq: self.marker_delivery_seq,
2712 binding_epoch: self.prior_binding_epoch,
2713 },
2714 participant_id,
2715 actual_charge,
2716 remaining_k,
2717 exit_claims,
2718 )
2719 }
2720
2721 pub fn fenced_attach(
2728 self,
2729 debt: ClosureDebt,
2730 event: Event,
2731 successor: DebtCompletion,
2732 ) -> Result<FencedAttachCommit, ClosureState> {
2733 let original = owed(debt, StoredEdge::DetachedCredentialRecovery(self));
2734 let EventKind::FencedRecoveryCommitted {
2735 participant_id,
2736 marker_delivery_seq,
2737 prior_binding_epoch,
2738 new_binding_epoch,
2739 ..
2740 } = event.0
2741 else {
2742 return Err(original);
2743 };
2744 if participant_id != self.participant_id
2745 || marker_delivery_seq != self.marker_delivery_seq
2746 || prior_binding_epoch != self.prior_binding_epoch
2747 || !is_next_generation(prior_binding_epoch, new_binding_epoch)
2748 {
2749 return Err(original);
2750 }
2751 Ok(FencedAttachCommit {
2752 conversation_id: self.conversation_id,
2753 participant_id,
2754 marker_delivery_seq,
2755 prior_binding_epoch,
2756 new_binding_epoch,
2757 next_state: successor.into_state(),
2758 })
2759 }
2760
2761 pub fn detached_leave(
2768 self,
2769 debt: ClosureDebt,
2770 event: Event,
2771 evidence: KClaimBackedDetachedLeave,
2772 successor: DebtCompletion,
2773 ) -> Result<ClosureState, ClosureState> {
2774 let original = owed(debt, StoredEdge::DetachedCredentialRecovery(self));
2775 let EventKind::LeaveCommitted {
2776 participant_id,
2777 authority: LeaveAuthority::Detached,
2778 ..
2779 } = event.0
2780 else {
2781 return Err(original);
2782 };
2783 let target = DetachedClaimTarget::CredentialRecovery {
2784 marker_delivery_seq: self.marker_delivery_seq,
2785 binding_epoch: self.prior_binding_epoch,
2786 };
2787 if participant_id != self.participant_id
2788 || evidence.participant_id != self.participant_id
2789 || evidence.target != target
2790 {
2791 return Err(original);
2792 }
2793 Ok(successor.into_state())
2794 }
2795
2796 #[must_use]
2798 pub const fn ordinary_attach_refusal(self) -> DetachedAttachRefusal {
2799 let _ = self;
2800 DetachedAttachRefusal::RecoveryFence
2801 }
2802
2803 #[must_use]
2805 pub const fn marker_attach_refusal(
2806 self,
2807 presented_marker: DeliverySeq,
2808 ) -> Option<DetachedAttachRefusal> {
2809 if presented_marker == self.marker_delivery_seq {
2810 None
2811 } else {
2812 Some(DetachedAttachRefusal::MarkerMismatch)
2813 }
2814 }
2815
2816 #[must_use]
2818 pub const fn authority_superseded(self) -> (Self, DetachedAttachRefusal) {
2819 (self, DetachedAttachRefusal::StaleAuthority)
2820 }
2821
2822 pub const fn unrelated_event(
2830 self,
2831 debt: ClosureDebt,
2832 event: Event,
2833 resulting_debt: Option<ClosureDebt>,
2834 ) -> Result<ClosureState, ClosureState> {
2835 unrelated_detached_event(
2836 StoredEdge::DetachedCredentialRecovery(self),
2837 self.participant_id,
2838 debt,
2839 event,
2840 resulting_debt,
2841 )
2842 }
2843}
2844
2845mod sealed {
2846 pub trait Sealed {}
2847}
2848
2849pub trait LeaveOnlyEdge: sealed::Sealed + Sized + Copy {
2851 fn participant_id(self) -> ParticipantId;
2853
2854 fn validate_leave_claim(
2857 &self,
2858 participant_id: ParticipantId,
2859 actual_charge: ResourceVector,
2860 remaining_k: ResourceVector,
2861 exit_claims: u64,
2862 ) -> Option<KClaimBackedDetachedLeave>;
2863
2864 fn leave(
2871 self,
2872 debt: ClosureDebt,
2873 event: Event,
2874 evidence: KClaimBackedDetachedLeave,
2875 successor: DebtCompletion,
2876 ) -> Result<ClosureState, ClosureState>;
2877
2878 fn repeat_fate(self, event: Event) -> Result<Self, Self>;
2885
2886 fn authority_superseded(self) -> (Self, DetachedAttachRefusal) {
2888 (self, DetachedAttachRefusal::StaleAuthority)
2889 }
2890
2891 fn binding_required_refusal(self) -> DetachedAttachRefusal {
2893 let _ = self;
2894 DetachedAttachRefusal::NoBinding
2895 }
2896
2897 fn unrelated_event(
2905 self,
2906 debt: ClosureDebt,
2907 event: Event,
2908 resulting_debt: Option<ClosureDebt>,
2909 ) -> Result<ClosureState, ClosureState>;
2910}
2911
2912impl sealed::Sealed for DetachedMarkerRelease {}
2913impl sealed::Sealed for DetachedCursorRelease {}
2914
2915impl LeaveOnlyEdge for DetachedMarkerRelease {
2916 fn participant_id(self) -> ParticipantId {
2917 self.participant_id
2918 }
2919
2920 fn validate_leave_claim(
2921 &self,
2922 participant_id: ParticipantId,
2923 actual_charge: ResourceVector,
2924 remaining_k: ResourceVector,
2925 exit_claims: u64,
2926 ) -> Option<KClaimBackedDetachedLeave> {
2927 validate_detached_claim(
2928 self.participant_id,
2929 DetachedClaimTarget::MarkerRelease {
2930 marker_delivery_seq: self.marker_delivery_seq,
2931 binding_epoch: self.last_dead_binding_epoch,
2932 },
2933 participant_id,
2934 actual_charge,
2935 remaining_k,
2936 exit_claims,
2937 )
2938 }
2939
2940 fn leave(
2941 self,
2942 debt: ClosureDebt,
2943 event: Event,
2944 evidence: KClaimBackedDetachedLeave,
2945 successor: DebtCompletion,
2946 ) -> Result<ClosureState, ClosureState> {
2947 let original = owed(debt, StoredEdge::DetachedMarkerRelease(self));
2948 let EventKind::LeaveCommitted {
2949 participant_id,
2950 authority: LeaveAuthority::Detached,
2951 ..
2952 } = event.0
2953 else {
2954 return Err(original);
2955 };
2956 let target = DetachedClaimTarget::MarkerRelease {
2957 marker_delivery_seq: self.marker_delivery_seq,
2958 binding_epoch: self.last_dead_binding_epoch,
2959 };
2960 if participant_id != self.participant_id
2961 || evidence.participant_id != self.participant_id
2962 || evidence.target != target
2963 {
2964 return Err(original);
2965 }
2966 Ok(successor.into_state())
2967 }
2968
2969 fn repeat_fate(self, event: Event) -> Result<Self, Self> {
2970 let EventKind::BindingFateObserved {
2971 participant_id,
2972 binding_epoch,
2973 ..
2974 } = event.0
2975 else {
2976 return Err(self);
2977 };
2978 if participant_id == self.participant_id && binding_epoch == self.last_dead_binding_epoch {
2979 Ok(self)
2980 } else {
2981 Err(self)
2982 }
2983 }
2984
2985 fn unrelated_event(
2986 self,
2987 debt: ClosureDebt,
2988 event: Event,
2989 resulting_debt: Option<ClosureDebt>,
2990 ) -> Result<ClosureState, ClosureState> {
2991 unrelated_detached_event(
2992 StoredEdge::DetachedMarkerRelease(self),
2993 self.participant_id,
2994 debt,
2995 event,
2996 resulting_debt,
2997 )
2998 }
2999}
3000
3001impl DetachedMarkerRelease {
3002 #[must_use]
3004 pub const fn ordinary_attach_refusal(self) -> DetachedAttachRefusal {
3005 let _ = self;
3006 DetachedAttachRefusal::RecoveryFence
3007 }
3008
3009 #[must_use]
3012 pub const fn marker_attach_refusal(
3013 self,
3014 presented_marker: DeliverySeq,
3015 ) -> DetachedAttachRefusal {
3016 if presented_marker == self.marker_delivery_seq {
3017 DetachedAttachRefusal::MarkerNotDelivered
3018 } else {
3019 DetachedAttachRefusal::MarkerMismatch
3020 }
3021 }
3022}
3023
3024impl LeaveOnlyEdge for DetachedCursorRelease {
3025 fn participant_id(self) -> ParticipantId {
3026 self.participant_id
3027 }
3028
3029 fn validate_leave_claim(
3030 &self,
3031 participant_id: ParticipantId,
3032 actual_charge: ResourceVector,
3033 remaining_k: ResourceVector,
3034 exit_claims: u64,
3035 ) -> Option<KClaimBackedDetachedLeave> {
3036 validate_detached_claim(
3037 self.participant_id,
3038 DetachedClaimTarget::CursorRelease {
3039 binding_epoch: self.last_dead_binding_epoch,
3040 },
3041 participant_id,
3042 actual_charge,
3043 remaining_k,
3044 exit_claims,
3045 )
3046 }
3047
3048 fn leave(
3049 self,
3050 debt: ClosureDebt,
3051 event: Event,
3052 evidence: KClaimBackedDetachedLeave,
3053 successor: DebtCompletion,
3054 ) -> Result<ClosureState, ClosureState> {
3055 let original = owed(debt, StoredEdge::DetachedCursorRelease(self));
3056 let EventKind::LeaveCommitted {
3057 participant_id,
3058 authority: LeaveAuthority::Detached,
3059 ..
3060 } = event.0
3061 else {
3062 return Err(original);
3063 };
3064 let target = DetachedClaimTarget::CursorRelease {
3065 binding_epoch: self.last_dead_binding_epoch,
3066 };
3067 if participant_id != self.participant_id
3068 || evidence.participant_id != self.participant_id
3069 || evidence.target != target
3070 {
3071 return Err(original);
3072 }
3073 Ok(successor.into_state())
3074 }
3075
3076 fn repeat_fate(self, event: Event) -> Result<Self, Self> {
3077 let EventKind::BindingFateObserved {
3078 participant_id,
3079 binding_epoch,
3080 ..
3081 } = event.0
3082 else {
3083 return Err(self);
3084 };
3085 if participant_id == self.participant_id && binding_epoch == self.last_dead_binding_epoch {
3086 Ok(self)
3087 } else {
3088 Err(self)
3089 }
3090 }
3091
3092 fn unrelated_event(
3093 self,
3094 debt: ClosureDebt,
3095 event: Event,
3096 resulting_debt: Option<ClosureDebt>,
3097 ) -> Result<ClosureState, ClosureState> {
3098 unrelated_detached_event(
3099 StoredEdge::DetachedCursorRelease(self),
3100 self.participant_id,
3101 debt,
3102 event,
3103 resulting_debt,
3104 )
3105 }
3106}
3107
3108impl DetachedCursorRelease {
3109 #[must_use]
3111 pub const fn ordinary_attach_refusal(self) -> DetachedAttachRefusal {
3112 let _ = self;
3113 DetachedAttachRefusal::RecoveryFence
3114 }
3115
3116 #[must_use]
3118 pub const fn marker_attach_refusal(self) -> DetachedAttachRefusal {
3119 let _ = self;
3120 DetachedAttachRefusal::MarkerMismatch
3121 }
3122}
3123
3124const fn owed(debt: ClosureDebt, edge: StoredEdge) -> ClosureState {
3125 ClosureState::Owed { debt, edge }
3126}
3127
3128const fn preserve_or_clear(resulting_debt: Option<ClosureDebt>, edge: StoredEdge) -> ClosureState {
3129 match resulting_debt {
3130 Some(debt) => owed(debt, edge),
3131 None => ClosureState::Clear,
3132 }
3133}
3134
3135const fn projection_completion_boundary(
3136 edge: ObserverProjection,
3137 event: Event,
3138) -> Option<DeliverySeq> {
3139 let EventKind::ProjectionCompleted { through_seq } = event.0 else {
3140 return None;
3141 };
3142 if through_seq == edge.through_seq {
3143 Some(through_seq)
3144 } else {
3145 None
3146 }
3147}
3148
3149const fn physical_completion_floor(edge: PhysicalCompaction, event: Event) -> Option<DeliverySeq> {
3150 match event.0 {
3151 EventKind::CompactionCompleted {
3152 from_floor,
3153 through_seq,
3154 resulting_floor,
3155 } if from_floor == edge.from_floor
3156 && through_seq == edge.through_seq
3157 && resulting_floor > edge.through_seq =>
3158 {
3159 Some(resulting_floor)
3160 }
3161 _ => None,
3162 }
3163}
3164
3165const fn progress_event_floor(event: Event) -> Option<DeliverySeq> {
3166 match event.0 {
3167 EventKind::CursorProgressed {
3168 resulting_floor, ..
3169 }
3170 | EventKind::BindingFateObserved {
3171 resulting_floor, ..
3172 }
3173 | EventKind::LeaveCommitted {
3174 resulting_floor, ..
3175 } => Some(resulting_floor),
3176 _ => None,
3177 }
3178}
3179
3180const fn cursor_event_boundary(event: Event) -> DeliverySeq {
3181 match event.0 {
3182 EventKind::CursorProgressed {
3183 progress: CursorProgressEvent::Normal { through_seq, .. },
3184 ..
3185 } => through_seq,
3186 _ => 0,
3187 }
3188}
3189
3190fn greater_ack_matches(edge: ParticipantCursorProgress, event: Event) -> bool {
3191 let EventKind::CursorProgressed {
3192 participant_id,
3193 binding_epoch,
3194 progress:
3195 CursorProgressEvent::Normal {
3196 previous_cursor,
3197 through_seq,
3198 },
3199 ..
3200 } = event.0
3201 else {
3202 return false;
3203 };
3204 participant_id == edge.participant_id()
3205 && binding_epoch == edge.binding_epoch()
3206 && previous_cursor < edge.through_seq()
3207 && through_seq > edge.through_seq()
3208}
3209
3210const fn strict_edge_matches_boundary(edge: StoredEdge, boundary: DeliverySeq) -> bool {
3211 match edge {
3212 StoredEdge::ObserverProjection(value) => value.through_seq == boundary,
3213 StoredEdge::PhysicalCompaction(value) => value.through_seq == boundary,
3214 StoredEdge::MarkerDelivery(value) => value.marker_delivery_seq == boundary,
3215 StoredEdge::ParticipantCursorProgress(value) => value.through_seq() == boundary,
3216 StoredEdge::DetachedCredentialRecovery(_) => false,
3217 StoredEdge::DetachedMarkerRelease(value) => value.marker_delivery_seq == boundary,
3218 StoredEdge::DetachedCursorRelease(_) => true,
3222 }
3223}
3224
3225const fn charged_churn_fits(used: u64, delta: u64, limit: u64) -> bool {
3226 delta > 0 && widen_u64(used) + widen_u64(delta) <= widen_u64(limit)
3227}
3228
3229#[allow(clippy::cast_lossless)]
3230const fn widen_u64(value: u64) -> u128 {
3231 value as u128
3232}
3233
3234const fn is_next_generation(old: BindingEpoch, new: BindingEpoch) -> bool {
3235 match old.capability_generation.get().checked_add(1) {
3236 Some(expected) => new.capability_generation.get() == expected,
3237 None => false,
3238 }
3239}
3240
3241const fn validate_detached_claim(
3242 owner: ParticipantId,
3243 target: DetachedClaimTarget,
3244 participant_id: ParticipantId,
3245 actual_charge: ResourceVector,
3246 remaining_k: ResourceVector,
3247 exit_claims: u64,
3248) -> Option<KClaimBackedDetachedLeave> {
3249 if participant_id != owner
3250 || actual_charge.entries == 0
3251 || actual_charge.bytes == 0
3252 || actual_charge.entries > remaining_k.entries
3253 || actual_charge.bytes > remaining_k.bytes
3254 || exit_claims == 0
3255 {
3256 return None;
3257 }
3258 Some(KClaimBackedDetachedLeave {
3259 participant_id,
3260 target,
3261 actual_charge,
3262 })
3263}
3264
3265const fn event_participant(event: Event) -> Option<ParticipantId> {
3266 match event.0 {
3267 EventKind::MarkerDelivered { participant_id, .. }
3268 | EventKind::CursorProgressed { participant_id, .. }
3269 | EventKind::BindingFateObserved { participant_id, .. }
3270 | EventKind::LeaveCommitted { participant_id, .. }
3271 | EventKind::FencedRecoveryCommitted { participant_id, .. } => Some(participant_id),
3272 EventKind::ProjectionCompleted { .. }
3273 | EventKind::CompactionCompleted { .. }
3274 | EventKind::MarkerAppended { .. } => None,
3275 }
3276}
3277
3278const fn unrelated_detached_event(
3279 edge: StoredEdge,
3280 owner: ParticipantId,
3281 debt: ClosureDebt,
3282 event: Event,
3283 resulting_debt: Option<ClosureDebt>,
3284) -> Result<ClosureState, ClosureState> {
3285 let original = owed(debt, edge);
3286 let Some(participant_id) = event_participant(event) else {
3287 return Err(original);
3288 };
3289 if participant_id == owner {
3290 return Err(original);
3291 }
3292 Ok(preserve_or_clear(resulting_debt, edge))
3293}