Skip to main content

liminal_protocol/lifecycle/
edge.rs

1use alloc::boxed::Box;
2
3use crate::algebra::{ResourceVector, WideResourceVector};
4use crate::wire::{BindingEpoch, ConversationId, DeliverySeq, ParticipantId, ParticipantIndex};
5
6use super::{
7    ActiveBinding, CommittedDiedTerminal, ObserverProgressProjection,
8    claim_frontier::{ValidatedMarkerCandidate, ValidatedMarkerRecord},
9    storage::{
10        DetachedCredentialRecoveryRestore, DetachedMarkerReleaseProvenanceRestore,
11        DetachedMarkerReleaseRestore, MarkerCursorProgressRestore, MarkerDeliveryRestore,
12        OrdinaryBindingAuthorityRestore, OrdinaryBindingFateRestore, RestoredBindingFateTerminal,
13        StoredEdgeRestore,
14    },
15};
16
17/// Nonzero componentwise closure debt.
18#[derive(Clone, Copy, Debug, PartialEq, Eq)]
19pub struct ClosureDebt(WideResourceVector);
20
21impl ClosureDebt {
22    /// Creates debt only when at least one component is nonzero.
23    #[must_use]
24    pub const fn new(value: WideResourceVector) -> Option<Self> {
25        if value.is_zero() {
26            None
27        } else {
28            Some(Self(value))
29        }
30    }
31
32    /// Returns exact entry/byte debt.
33    #[must_use]
34    pub const fn value(self) -> WideResourceVector {
35        self.0
36    }
37}
38
39/// Observer-projection witness.
40#[derive(Clone, Copy, Debug, PartialEq, Eq)]
41pub struct ObserverProjection {
42    through_seq: DeliverySeq,
43}
44
45impl ObserverProjection {
46    /// Creates an exact observer-projection witness.
47    #[must_use]
48    pub const fn new(through_seq: DeliverySeq) -> Self {
49        Self { through_seq }
50    }
51
52    /// Returns the exact projection boundary.
53    #[must_use]
54    pub const fn through_seq(self) -> DeliverySeq {
55        self.through_seq
56    }
57
58    /// Describes this projection as the capsule that restores it.
59    #[must_use]
60    pub const fn storage_description(self) -> StoredEdgeRestore {
61        StoredEdgeRestore::ObserverProjection {
62            through_seq: self.through_seq,
63        }
64    }
65}
66
67/// Physical-compaction range witness.
68#[derive(Clone, Copy, Debug, PartialEq, Eq)]
69pub struct PhysicalCompaction {
70    from_floor: DeliverySeq,
71    through_seq: DeliverySeq,
72}
73
74impl PhysicalCompaction {
75    /// Creates a nonempty, ordered compaction range.
76    #[must_use]
77    pub const fn new(from_floor: DeliverySeq, through_seq: DeliverySeq) -> Option<Self> {
78        if from_floor <= through_seq {
79            Some(Self {
80                from_floor,
81                through_seq,
82            })
83        } else {
84            None
85        }
86    }
87
88    /// Returns the exact first sequence in the compaction range.
89    #[must_use]
90    pub const fn from_floor(self) -> DeliverySeq {
91        self.from_floor
92    }
93
94    /// Returns the exact inclusive compaction boundary.
95    #[must_use]
96    pub const fn through_seq(self) -> DeliverySeq {
97        self.through_seq
98    }
99
100    /// Describes this compaction range as the capsule that restores it.
101    #[must_use]
102    pub const fn storage_description(self) -> StoredEdgeRestore {
103        StoredEdgeRestore::PhysicalCompaction {
104            from_floor: self.from_floor,
105            through_seq: self.through_seq,
106        }
107    }
108}
109
110/// Exact marker-delivery witness.
111///
112/// This witness has no public constructor. Fresh delivery is produced only by
113/// the claim frontier's consuming marker-drain transition; cold restoration
114/// requires its sealed retained-marker-record authority. Raw participant,
115/// binding, and sequence values therefore cannot create recovery authority.
116///
117/// ```compile_fail
118/// use liminal_protocol::{
119///     lifecycle::MarkerDelivery,
120///     wire::{BindingEpoch, ConnectionIncarnation, Generation},
121/// };
122///
123/// let epoch = BindingEpoch::new(
124///     ConnectionIncarnation::new(1, 1),
125///     Generation::ONE,
126/// );
127/// let _ = MarkerDelivery::new(7, epoch, 11);
128/// ```
129// The frozen tag spells this required field `marker_delivery_seq`.
130#[allow(clippy::struct_field_names)]
131#[derive(Clone, Copy, Debug, PartialEq, Eq)]
132pub struct MarkerDelivery {
133    conversation_id: ConversationId,
134    participant_id: ParticipantId,
135    binding_epoch: BindingEpoch,
136    marker_delivery_seq: DeliverySeq,
137}
138
139impl MarkerDelivery {
140    /// Creates the exact post-append marker successor only from one
141    /// frontier-consumed marker candidate.
142    ///
143    /// A candidate whose target epoch is still bound selects live delivery. A
144    /// candidate whose target epoch has already died selects the undelivered
145    /// detached release directly, so no transient live-delivery authority is
146    /// fabricated for a detached participant.
147    #[must_use]
148    pub(super) const fn successor_from_validated_candidate(
149        candidate: ValidatedMarkerCandidate,
150    ) -> StoredEdge {
151        let conversation_id = candidate.conversation_id();
152        let participant_id = candidate.participant_id();
153        let marker_delivery_seq = candidate.delivery_seq();
154        let successor = match candidate.target_binding() {
155            super::FrontierBinding::Bound(binding_epoch) => StoredEdge::MarkerDelivery(Self {
156                conversation_id,
157                participant_id,
158                binding_epoch,
159                marker_delivery_seq,
160            }),
161            super::FrontierBinding::Detached(last_dead_binding_epoch) => {
162                StoredEdge::DetachedMarkerRelease(DetachedMarkerRelease {
163                    conversation_id,
164                    participant_id,
165                    marker_delivery_seq,
166                    last_dead_binding_epoch,
167                    provenance: DetachedMarkerReleaseProvenance::Candidate,
168                })
169            }
170        };
171        candidate.consume();
172        successor
173    }
174
175    /// Rebuilds delivery only from one frontier-validated retained marker.
176    #[must_use]
177    pub(super) const fn from_validated_record(record: &ValidatedMarkerRecord) -> Self {
178        Self {
179            conversation_id: record.conversation_id(),
180            participant_id: record.participant_id(),
181            binding_epoch: record.binding_epoch(),
182            marker_delivery_seq: record.delivery_seq(),
183        }
184    }
185
186    /// Returns the conversation whose frontier authority minted this delivery.
187    #[must_use]
188    pub const fn conversation_id(self) -> ConversationId {
189        self.conversation_id
190    }
191
192    /// Returns the marker owner.
193    #[must_use]
194    pub const fn participant_id(self) -> ParticipantId {
195        self.participant_id
196    }
197
198    /// Returns the exact delivery epoch.
199    #[must_use]
200    pub const fn binding_epoch(self) -> BindingEpoch {
201        self.binding_epoch
202    }
203
204    /// Returns the exact marker sequence.
205    #[must_use]
206    pub const fn marker_delivery_seq(self) -> DeliverySeq {
207        self.marker_delivery_seq
208    }
209
210    /// Describes this delivery as the capsule that restores it.
211    ///
212    /// The conversation is absent from the capsule by design: restore takes it
213    /// from the conversation being restored and matches it against the
214    /// retained marker record, so writing it here would duplicate an authority
215    /// the capsule does not own.
216    #[must_use]
217    pub const fn storage_description(self) -> StoredEdgeRestore {
218        StoredEdgeRestore::MarkerDelivery(MarkerDeliveryRestore {
219            participant_id: self.participant_id,
220            binding_epoch: self.binding_epoch,
221            marker_delivery_seq: self.marker_delivery_seq,
222        })
223    }
224}
225
226#[cfg(any(test, feature = "test-support"))]
227#[allow(clippy::expect_used, clippy::too_many_lines)]
228pub fn validated_marker_record_for_test(
229    conversation_id: crate::wire::ConversationId,
230    participant_id: ParticipantId,
231    target_binding: super::claim_frontier::FrontierBinding,
232    marker_delivery_seq: DeliverySeq,
233    cursor: DeliverySeq,
234) -> ValidatedMarkerRecord {
235    use alloc::{vec, vec::Vec};
236
237    use crate::outcome::CandidatePhase;
238
239    use super::{
240        AdmissionOrder, OrderClaims, OrderHigh, OrderLedger, RecoverySequenceReserve,
241        SequenceClaims, SequenceLedger,
242        claim_frontier::{
243            BindingTerminalOwner, ClaimFrontiers, ClaimFrontiersRestore, FrontierBinding,
244            FrontierParticipant, ImmutableSequenceCandidate, MarkerProvenance, MarkerRecordRequest,
245            MovableOrderClaim, MovableSequenceClaim, OrderClaimFrontierRestore, OrderDirectOwner,
246            RetainedCausalRecord, RetainedCausalRecordKind, SequenceClaimFrontierRestore,
247            SequenceDirectOwner, SequenceProductRangesRestore, TerminalProductRangeRestore,
248        },
249    };
250
251    let identity_slot_limit = participant_id
252        .checked_add(1)
253        .expect("test participant must fit a half-open identity domain");
254    let exit_seq = marker_delivery_seq
255        .checked_add(1)
256        .expect("test marker must leave an exit-claim suffix");
257    let binding_epoch = match target_binding {
258        FrontierBinding::Bound(epoch) | FrontierBinding::Detached(epoch) => epoch,
259    };
260    let terminal_owner = BindingTerminalOwner {
261        participant_index: participant_id,
262        binding_epoch,
263    };
264    let (sequence_claims, sequence_movable, products, order_claims, order_movable) =
265        match target_binding {
266            FrontierBinding::Bound(_) => {
267                let terminal_seq = exit_seq
268                    .checked_add(1)
269                    .expect("test marker must leave a terminal-claim suffix");
270                let product_seq = terminal_seq
271                    .checked_add(1)
272                    .expect("test marker must leave a terminal-product suffix");
273                (
274                    SequenceClaims::new(1, 1, 0, RecoverySequenceReserve::None),
275                    vec![
276                        MovableSequenceClaim {
277                            delivery_seq: exit_seq,
278                            owner: SequenceDirectOwner::MembershipExit {
279                                participant_index: participant_id,
280                            },
281                        },
282                        MovableSequenceClaim {
283                            delivery_seq: terminal_seq,
284                            owner: SequenceDirectOwner::BindingTerminal(terminal_owner),
285                        },
286                    ],
287                    SequenceProductRangesRestore {
288                        live_times_terminal: vec![TerminalProductRangeRestore {
289                            start: product_seq,
290                            length: 1,
291                            terminal: terminal_owner,
292                        }],
293                        live_times_replacement_terminal: None,
294                        other_live_times_exit: vec![],
295                    },
296                    OrderClaims::new(1, 1, false, false)
297                        .expect("bound test claims have no torn recovery pair"),
298                    vec![
299                        MovableOrderClaim {
300                            transaction_order: 1,
301                            owner: OrderDirectOwner::ActiveBindingTerminal(terminal_owner),
302                        },
303                        MovableOrderClaim {
304                            transaction_order: 2,
305                            owner: OrderDirectOwner::MembershipExit {
306                                participant_index: participant_id,
307                            },
308                        },
309                    ],
310                )
311            }
312            FrontierBinding::Detached(_) => (
313                SequenceClaims::new(1, 0, 0, RecoverySequenceReserve::None),
314                vec![MovableSequenceClaim {
315                    delivery_seq: exit_seq,
316                    owner: SequenceDirectOwner::MembershipExit {
317                        participant_index: participant_id,
318                    },
319                }],
320                SequenceProductRangesRestore::default(),
321                OrderClaims::new(0, 1, false, false)
322                    .expect("detached test claims have no torn recovery pair"),
323                vec![MovableOrderClaim {
324                    transaction_order: 1,
325                    owner: OrderDirectOwner::MembershipExit {
326                        participant_index: participant_id,
327                    },
328                }],
329            ),
330        };
331    let sequence_ledger = SequenceLedger::try_new(marker_delivery_seq, sequence_claims)
332        .expect("test sequence frontier is within the numeric suffix");
333    let order_ledger = OrderLedger::try_new(OrderHigh::Allocated(0), order_claims)
334        .expect("test order frontier is within the numeric suffix");
335    let admission_order = AdmissionOrder::new(0, CandidatePhase::CompactionMarker, participant_id);
336    let mut prevalidated = ClaimFrontiers::prevalidate(
337        ClaimFrontiersRestore {
338            conversation_id,
339            active_identities: vec![FrontierParticipant::new(
340                participant_id,
341                cursor,
342                target_binding,
343            )],
344            identity_slot_limit,
345            retained_floor: u128::from(marker_delivery_seq),
346            retained_record_limit: 1,
347            retained_records: vec![RetainedCausalRecord {
348                delivery_seq: marker_delivery_seq,
349                admission_order,
350                kind: RetainedCausalRecordKind::CompactionMarker {
351                    participant_index: participant_id,
352                    provenance: MarkerProvenance::NonProductM,
353                },
354            }],
355            active_marker_anchors: vec![marker_delivery_seq],
356            historical_marker_deliveries: vec![],
357            historical_causal_facts: vec![],
358            sequence: SequenceClaimFrontierRestore {
359                movable_claims: sequence_movable,
360                immutable_candidates: Vec::<ImmutableSequenceCandidate>::new(),
361                products,
362                recovery: None,
363            },
364            order: OrderClaimFrontierRestore {
365                movable_claims: order_movable,
366                immutable_candidates: vec![],
367                recovery: None,
368            },
369            recovery_marker_delivery_seq: None,
370        },
371        sequence_ledger,
372        order_ledger,
373    )
374    .expect("complete test claim frontier must prevalidate");
375    let record = prevalidated
376        .take_marker_record(MarkerRecordRequest::planned(
377            participant_id,
378            marker_delivery_seq,
379            target_binding,
380        ))
381        .expect("prevalidated test frontier retains its exact marker");
382    if cursor >= marker_delivery_seq {
383        record.delivered_for_test()
384    } else {
385        record
386    }
387}
388
389#[cfg(any(test, feature = "test-support"))]
390pub fn marker_delivery_for_test(
391    participant_id: ParticipantId,
392    binding_epoch: BindingEpoch,
393    marker_delivery_seq: DeliverySeq,
394) -> Result<MarkerDelivery, super::storage::StorageRestoreError> {
395    let record = validated_marker_record_for_test(
396        1,
397        participant_id,
398        super::claim_frontier::FrontierBinding::Bound(binding_epoch),
399        marker_delivery_seq,
400        marker_delivery_seq.saturating_sub(1),
401    );
402    super::storage::MarkerDeliveryRestore {
403        participant_id,
404        binding_epoch,
405        marker_delivery_seq,
406    }
407    .restore_bound(1, record)
408}
409
410/// Continuous cursor-progress witness with no delivered marker.
411///
412/// This witness deliberately has no public constructor. A caller outside this
413/// crate cannot turn raw participant/epoch values into executable binding-fate
414/// authority; recovered-epoch fate must instead originate from
415/// `FencedAttachCommit::recovered_binding_fate`.
416///
417/// ```compile_fail
418/// use liminal_protocol::{
419///     lifecycle::CursorProgressContinuous,
420///     wire::BindingEpoch,
421/// };
422///
423/// fn fabricate(epoch: BindingEpoch) {
424///     let _ = CursorProgressContinuous::new(7, epoch, 11);
425/// }
426/// ```
427#[derive(Clone, Copy, Debug, PartialEq, Eq)]
428pub struct CursorProgressContinuous {
429    participant_id: ParticipantId,
430    binding_epoch: BindingEpoch,
431    through_seq: DeliverySeq,
432}
433
434impl CursorProgressContinuous {
435    /// Creates an exact current-epoch continuous-cursor witness internally.
436    #[cfg(test)]
437    #[must_use]
438    pub(crate) const fn new(
439        participant_id: ParticipantId,
440        binding_epoch: BindingEpoch,
441        through_seq: DeliverySeq,
442    ) -> Self {
443        Self {
444            participant_id,
445            binding_epoch,
446            through_seq,
447        }
448    }
449
450    /// Returns the participant whose cursor is required.
451    #[must_use]
452    pub const fn participant_id(self) -> ParticipantId {
453        self.participant_id
454    }
455
456    /// Returns the exact binding epoch.
457    #[must_use]
458    pub const fn binding_epoch(self) -> BindingEpoch {
459        self.binding_epoch
460    }
461
462    /// Returns the required cumulative boundary.
463    #[must_use]
464    pub const fn through_seq(self) -> DeliverySeq {
465        self.through_seq
466    }
467
468    /// Describes this witness as the capsule that restores it.
469    ///
470    /// The capsule states an ordinary-attach authority the witness does not
471    /// hold, but restore accepts no authority other than the one this witness
472    /// implies: `restore_continuous` requires the authority's participant,
473    /// epoch and cursor to equal the witness's own three fields, so every
474    /// field of the authority except its conversation is read off the witness.
475    /// The conversation is the one being described, which is why it is an
476    /// argument and not provenance -- the same reason the marker delivery's
477    /// capsule omits it.
478    #[must_use]
479    pub const fn storage_description(self, conversation_id: ConversationId) -> StoredEdgeRestore {
480        StoredEdgeRestore::ParticipantCursorProgressContinuous {
481            participant_id: self.participant_id,
482            binding_epoch: self.binding_epoch,
483            through_seq: self.through_seq,
484            authority: OrdinaryBindingAuthorityRestore {
485                binding: ActiveBinding {
486                    participant_id: self.participant_id,
487                    conversation_id,
488                    binding_epoch: self.binding_epoch,
489                },
490                through_seq: self.through_seq,
491            },
492        }
493    }
494}
495
496/// Marker-backed cursor-progress witness.
497///
498/// This value has no public constructor. It is produced only by consuming an
499/// exact [`MarkerDelivery`] with its matching [`Event::marker_delivered`]. That
500/// makes the durable exact-epoch delivery fact required by the frozen contract
501/// a type-level precondition for detached credential recovery.
502#[derive(Clone, Copy, Debug, PartialEq, Eq)]
503pub struct CursorProgressMarker {
504    conversation_id: ConversationId,
505    participant_id: ParticipantId,
506    binding_epoch: BindingEpoch,
507    through_seq: DeliverySeq,
508    marker_delivery_seq: DeliverySeq,
509}
510
511impl CursorProgressMarker {
512    /// Returns the conversation inherited from the exact marker delivery.
513    #[must_use]
514    pub const fn conversation_id(self) -> ConversationId {
515        self.conversation_id
516    }
517
518    /// Returns the participant whose marker must be accepted.
519    #[must_use]
520    pub const fn participant_id(self) -> ParticipantId {
521        self.participant_id
522    }
523
524    /// Returns the exact epoch that received the marker.
525    #[must_use]
526    pub const fn binding_epoch(self) -> BindingEpoch {
527        self.binding_epoch
528    }
529
530    /// Returns the required cumulative boundary.
531    #[must_use]
532    pub const fn through_seq(self) -> DeliverySeq {
533        self.through_seq
534    }
535
536    /// Returns the exact delivered marker.
537    #[must_use]
538    pub const fn marker_delivery_seq(self) -> DeliverySeq {
539        self.marker_delivery_seq
540    }
541
542    /// Describes this witness as the capsule that restores it.
543    ///
544    /// Including the nested delivery predecessor, which restore requires to
545    /// agree with the witness field for field, so it has no field of its own
546    /// to supply.
547    #[must_use]
548    pub const fn storage_description(self) -> StoredEdgeRestore {
549        StoredEdgeRestore::ParticipantCursorProgressMarker(self.checkpoint_marker_source())
550    }
551
552    pub(in crate::lifecycle) const fn checkpoint_marker_source(
553        self,
554    ) -> MarkerCursorProgressRestore {
555        MarkerCursorProgressRestore {
556            conversation_id: self.conversation_id,
557            participant_id: self.participant_id,
558            binding_epoch: self.binding_epoch,
559            through_seq: self.through_seq,
560            marker_delivery_seq: self.marker_delivery_seq,
561            delivery: MarkerDeliveryRestore {
562                participant_id: self.participant_id,
563                binding_epoch: self.binding_epoch,
564                marker_delivery_seq: self.marker_delivery_seq,
565            },
566        }
567    }
568}
569
570/// Cursor progress split into typestates rather than an optional marker bag.
571///
572/// Continuous construction is crate-private so matching raw participant and
573/// epoch values cannot fabricate `DetachedCursorRelease` authority.
574///
575/// ```compile_fail
576/// use liminal_protocol::{
577///     lifecycle::ParticipantCursorProgress,
578///     wire::BindingEpoch,
579/// };
580///
581/// fn fabricate(epoch: BindingEpoch) {
582///     let _ = ParticipantCursorProgress::continuous(7, epoch, 11);
583/// }
584/// ```
585#[derive(Clone, Copy, Debug, PartialEq, Eq)]
586pub enum ParticipantCursorProgress {
587    /// Continuous cursor witness.
588    Continuous(CursorProgressContinuous),
589    /// Exact marker acknowledgement witness, derivable only from delivery.
590    Marker(CursorProgressMarker),
591}
592
593impl ParticipantCursorProgress {
594    pub(in crate::lifecycle) const fn checkpoint_marker_source(
595        self,
596    ) -> Option<MarkerCursorProgressRestore> {
597        match self {
598            Self::Continuous(_) => None,
599            Self::Marker(value) => Some(value.checkpoint_marker_source()),
600        }
601    }
602
603    /// Creates a continuous, no-marker cursor witness internally.
604    #[cfg(test)]
605    #[must_use]
606    pub(crate) const fn continuous(
607        participant_id: ParticipantId,
608        binding_epoch: BindingEpoch,
609        through_seq: DeliverySeq,
610    ) -> Self {
611        Self::Continuous(CursorProgressContinuous::new(
612            participant_id,
613            binding_epoch,
614            through_seq,
615        ))
616    }
617
618    pub(super) fn restore_continuous(
619        authority: OrdinaryBindingAuthority,
620        participant_id: ParticipantId,
621        binding_epoch: BindingEpoch,
622        through_seq: DeliverySeq,
623    ) -> Option<Self> {
624        if authority.binding.participant_id != participant_id
625            || authority.binding.binding_epoch != binding_epoch
626            || authority.through_seq != through_seq
627        {
628            return None;
629        }
630        Some(Self::Continuous(CursorProgressContinuous {
631            participant_id,
632            binding_epoch,
633            through_seq,
634        }))
635    }
636
637    /// Returns the participant whose cursor is required.
638    #[must_use]
639    pub const fn participant_id(self) -> ParticipantId {
640        match self {
641            Self::Continuous(value) => value.participant_id,
642            Self::Marker(value) => value.participant_id,
643        }
644    }
645
646    /// Returns the exact binding epoch.
647    #[must_use]
648    pub const fn binding_epoch(self) -> BindingEpoch {
649        match self {
650            Self::Continuous(value) => value.binding_epoch,
651            Self::Marker(value) => value.binding_epoch,
652        }
653    }
654
655    /// Returns the required cumulative boundary.
656    #[must_use]
657    pub const fn through_seq(self) -> DeliverySeq {
658        match self {
659            Self::Continuous(value) => value.through_seq,
660            Self::Marker(value) => value.through_seq,
661        }
662    }
663
664    /// Returns the exact delivered marker when this is marker-backed.
665    #[must_use]
666    pub const fn marker_delivery_seq(self) -> Option<DeliverySeq> {
667        match self {
668            Self::Continuous(_) => None,
669            Self::Marker(value) => Some(value.marker_delivery_seq),
670        }
671    }
672}
673
674/// Detached fenced credential-recovery witness.
675///
676/// This state is produced only by the exact binding fate of a marker-backed
677/// cursor witness; callers cannot fabricate a durable delivery fact.
678#[derive(Clone, Copy, Debug, PartialEq, Eq)]
679pub struct DetachedCredentialRecovery {
680    conversation_id: ConversationId,
681    participant_id: ParticipantId,
682    marker_delivery_seq: DeliverySeq,
683    prior_binding_epoch: BindingEpoch,
684    /// The floor the binding-fate transaction that selected this recovery
685    /// measured.
686    ///
687    /// Predecessor provenance: the fate event carries it and this edge used to
688    /// drop it on the floor of the destructure, so a description of the
689    /// recovery could not be produced from live state without being told a
690    /// floor by something that had not measured it.
691    resulting_floor: DeliverySeq,
692}
693
694impl DetachedCredentialRecovery {
695    /// Returns the floor measured by the binding fate that selected this
696    /// recovery.
697    #[must_use]
698    pub const fn resulting_floor(self) -> DeliverySeq {
699        self.resulting_floor
700    }
701
702    /// Describes this recovery as the capsule that restores it, given the
703    /// terminal of the binding fate that selected it.
704    ///
705    /// The terminal is not stored on the edge -- the crate's size lint refuses
706    /// it, at 43 errors for 88 bytes -- so it is resolved from the member the
707    /// recovery belongs to and handed in here. That is sound because a standing
708    /// detached edge refuses ordinary detached attach
709    /// ([`ClosureState::ordinary_detached_attach_admission`]), so the member
710    /// cannot have committed a later terminal while this edge stands.
711    ///
712    /// Every other field is the edge's own, including the whole marker-progress
713    /// predecessor: the restore requires the progress's cursor to equal its
714    /// marker sequence and its nested delivery to agree field for field, so the
715    /// predecessor states nothing this edge does not already know.
716    ///
717    /// # Errors
718    ///
719    /// Returns the terminal unchanged unless it names this recovery's own
720    /// participant, prior epoch and conversation -- the exact check the restore
721    /// makes before it will accept the capsule, made here so a description that
722    /// could not be restored is never written.
723    pub fn storage_description(
724        self,
725        terminal: RestoredBindingFateTerminal,
726    ) -> Result<StoredEdgeRestore, RestoredBindingFateTerminal> {
727        if terminal.participant_id() != self.participant_id
728            || terminal.binding_epoch() != self.prior_binding_epoch
729            || terminal.conversation_id() != self.conversation_id
730        {
731            return Err(terminal);
732        }
733        Ok(StoredEdgeRestore::DetachedCredentialRecovery(
734            DetachedCredentialRecoveryRestore {
735                participant_id: self.participant_id,
736                marker_delivery_seq: self.marker_delivery_seq,
737                prior_binding_epoch: self.prior_binding_epoch,
738                resulting_floor: self.resulting_floor,
739                terminal: terminal.storage_description(),
740                progress: MarkerCursorProgressRestore {
741                    conversation_id: self.conversation_id,
742                    participant_id: self.participant_id,
743                    binding_epoch: self.prior_binding_epoch,
744                    through_seq: self.marker_delivery_seq,
745                    marker_delivery_seq: self.marker_delivery_seq,
746                    delivery: MarkerDeliveryRestore {
747                        participant_id: self.participant_id,
748                        binding_epoch: self.prior_binding_epoch,
749                        marker_delivery_seq: self.marker_delivery_seq,
750                    },
751                },
752            },
753        ))
754    }
755
756    /// Rebuilds the harmless, copyable recovery description after the storage
757    /// layer has validated its complete terminal and marker-progress audit.
758    ///
759    /// Marker-occurrence authority is deliberately not part of this value; the
760    /// move-only frontier owner must still consume its private validated record
761    /// before a fenced proof can be minted.
762    pub(super) const fn from_storage_description(
763        conversation_id: ConversationId,
764        participant_id: ParticipantId,
765        marker_delivery_seq: DeliverySeq,
766        prior_binding_epoch: BindingEpoch,
767        resulting_floor: DeliverySeq,
768    ) -> Self {
769        Self {
770            conversation_id,
771            participant_id,
772            marker_delivery_seq,
773            prior_binding_epoch,
774            resulting_floor,
775        }
776    }
777
778    /// Returns the conversation inherited from the marker-backed cursor witness.
779    #[must_use]
780    pub const fn conversation_id(self) -> ConversationId {
781        self.conversation_id
782    }
783
784    /// Returns the detached participant.
785    #[must_use]
786    pub const fn participant_id(self) -> ParticipantId {
787        self.participant_id
788    }
789
790    /// Returns the delivered recovery marker.
791    #[must_use]
792    pub const fn marker_delivery_seq(self) -> DeliverySeq {
793        self.marker_delivery_seq
794    }
795
796    /// Returns the prior authoritative epoch.
797    #[must_use]
798    pub const fn prior_binding_epoch(self) -> BindingEpoch {
799        self.prior_binding_epoch
800    }
801}
802
803#[cfg(any(test, feature = "test-support"))]
804pub fn validated_marker_record_for_recovery_test(
805    recovery: DetachedCredentialRecovery,
806) -> ValidatedMarkerRecord {
807    validated_marker_record_for_test(
808        recovery.conversation_id(),
809        recovery.participant_id(),
810        super::claim_frontier::FrontierBinding::Detached(recovery.prior_binding_epoch()),
811        recovery.marker_delivery_seq(),
812        recovery.marker_delivery_seq(),
813    )
814}
815
816/// Leave-only undelivered-marker release witness.
817///
818/// This state is produced only when exact binding fate consumes a marker that
819/// has not reached [`CursorProgressMarker`].
820#[derive(Clone, Copy, Debug, PartialEq, Eq)]
821pub struct DetachedMarkerRelease {
822    /// The conversation this release belongs to.
823    ///
824    /// Predecessor provenance: it is in hand at both sites that form this
825    /// edge and was dropped by both, so the capsule that describes a release
826    /// could not be produced from live state — it had to be told the
827    /// conversation by something else. An edge that cannot say which
828    /// conversation it belongs to is not a description of anything.
829    conversation_id: ConversationId,
830    participant_id: ParticipantId,
831    marker_delivery_seq: DeliverySeq,
832    last_dead_binding_epoch: BindingEpoch,
833    /// Which of the two formation paths produced this release.
834    ///
835    /// Predecessor provenance: the two paths leave different histories behind
836    /// them -- one a durable terminal, the other nothing at all -- and the edge
837    /// used to look identical either way. A release that cannot say which it is
838    /// cannot be described, because the description would have to claim a
839    /// terminal for a path that never had one.
840    provenance: DetachedMarkerReleaseProvenance,
841}
842
843/// Which of the two paths formed a detached marker release.
844///
845/// Both arms exist from this type's birth, so no downstream matcher is broken
846/// by the distinction being drawn.
847#[derive(Clone, Copy, Debug, PartialEq, Eq)]
848pub enum DetachedMarkerReleaseProvenance {
849    /// A pre-delivery binding fate consumed a live delivery, leaving the
850    /// durable terminal that fate validated.
851    Fate,
852    /// A frontier marker candidate whose target epoch was already dead selected
853    /// the release directly. No fate was observed, so no terminal exists and no
854    /// floor was ever measured for it.
855    Candidate,
856}
857
858impl DetachedMarkerRelease {
859    /// Returns the conversation this release belongs to.
860    #[must_use]
861    pub const fn conversation_id(self) -> ConversationId {
862        self.conversation_id
863    }
864
865    /// Returns which of the two paths formed this release.
866    #[must_use]
867    pub const fn provenance(self) -> DetachedMarkerReleaseProvenance {
868        self.provenance
869    }
870
871    /// Rebuilds the release after the storage layer has validated its whole
872    /// audit: the undelivered-marker predecessor, and -- on the fate path only
873    /// -- the durable terminal that fate left.
874    pub(super) const fn from_storage_description(
875        conversation_id: ConversationId,
876        participant_id: ParticipantId,
877        marker_delivery_seq: DeliverySeq,
878        last_dead_binding_epoch: BindingEpoch,
879        provenance: DetachedMarkerReleaseProvenance,
880    ) -> Self {
881        Self {
882            conversation_id,
883            participant_id,
884            marker_delivery_seq,
885            last_dead_binding_epoch,
886            provenance,
887        }
888    }
889
890    /// Returns the detached participant.
891    #[must_use]
892    pub const fn participant_id(self) -> ParticipantId {
893        self.participant_id
894    }
895
896    /// Returns the undelivered marker.
897    #[must_use]
898    pub const fn marker_delivery_seq(self) -> DeliverySeq {
899        self.marker_delivery_seq
900    }
901
902    /// Returns the dead binding epoch.
903    #[must_use]
904    pub const fn last_dead_binding_epoch(self) -> BindingEpoch {
905        self.last_dead_binding_epoch
906    }
907
908    /// Describes this release as the capsule that restores it, given the
909    /// terminal of the fate that selected it, where there was one.
910    ///
911    /// A fate-selected release must be given its terminal; a candidate-selected
912    /// release must be given none, because none was ever observed for it. The
913    /// terminal itself is not stored on the edge because the crate's size lint
914    /// refuses it, and resolving it from the member is sound for the reason
915    /// given on [`DetachedCredentialRecovery::storage_description`].
916    ///
917    /// # Errors
918    ///
919    /// Returns what it was given unless the release's own path is matched: a
920    /// terminal naming this release's participant, epoch and conversation for
921    /// the fate path, and no terminal at all for the candidate path.
922    pub fn storage_description(
923        self,
924        terminal: Option<RestoredBindingFateTerminal>,
925    ) -> Result<StoredEdgeRestore, Option<RestoredBindingFateTerminal>> {
926        let provenance = match (self.provenance, terminal) {
927            (DetachedMarkerReleaseProvenance::Fate, Some(terminal)) => {
928                if terminal.participant_id() != self.participant_id
929                    || terminal.binding_epoch() != self.last_dead_binding_epoch
930                    || terminal.conversation_id() != self.conversation_id
931                {
932                    return Err(Some(terminal));
933                }
934                DetachedMarkerReleaseProvenanceRestore::Fate {
935                    terminal: terminal.storage_description(),
936                }
937            }
938            (DetachedMarkerReleaseProvenance::Candidate, None) => {
939                DetachedMarkerReleaseProvenanceRestore::Candidate
940            }
941            (DetachedMarkerReleaseProvenance::Fate, None)
942            | (DetachedMarkerReleaseProvenance::Candidate, Some(_)) => return Err(terminal),
943        };
944        Ok(StoredEdgeRestore::DetachedMarkerRelease(
945            DetachedMarkerReleaseRestore {
946                conversation_id: self.conversation_id,
947                participant_id: self.participant_id,
948                marker_delivery_seq: self.marker_delivery_seq,
949                last_dead_binding_epoch: self.last_dead_binding_epoch,
950                delivery: MarkerDeliveryRestore {
951                    participant_id: self.participant_id,
952                    binding_epoch: self.last_dead_binding_epoch,
953                    marker_delivery_seq: self.marker_delivery_seq,
954                },
955                provenance,
956            },
957        ))
958    }
959}
960
961/// Leave-only detached-cursor release witness.
962///
963/// This state is produced only when exact binding fate consumes a continuous
964/// cursor witness with no marker.
965#[derive(Clone, Copy, Debug, PartialEq, Eq)]
966pub struct DetachedCursorRelease {
967    participant_id: ParticipantId,
968    last_dead_binding_epoch: BindingEpoch,
969}
970
971impl DetachedCursorRelease {
972    /// Returns the detached participant.
973    #[must_use]
974    pub const fn participant_id(self) -> ParticipantId {
975        self.participant_id
976    }
977
978    /// Returns the dead binding epoch.
979    #[must_use]
980    pub const fn last_dead_binding_epoch(self) -> BindingEpoch {
981        self.last_dead_binding_epoch
982    }
983}
984
985/// Exact seven non-clear stored edge kinds.
986#[derive(Clone, Copy, Debug, PartialEq, Eq)]
987pub enum StoredEdge {
988    /// Observer projection.
989    ObserverProjection(ObserverProjection),
990    /// Physical compaction.
991    PhysicalCompaction(PhysicalCompaction),
992    /// Marker delivery.
993    MarkerDelivery(MarkerDelivery),
994    /// Participant cursor progress.
995    ParticipantCursorProgress(ParticipantCursorProgress),
996    /// Detached credential recovery.
997    DetachedCredentialRecovery(DetachedCredentialRecovery),
998    /// Detached marker release.
999    DetachedMarkerRelease(DetachedMarkerRelease),
1000    /// Detached cursor release.
1001    DetachedCursorRelease(DetachedCursorRelease),
1002}
1003
1004/// Closure state makes a clear edge with nonzero debt unconstructible.
1005#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1006pub enum ClosureState {
1007    /// No edge and zero debt.
1008    Clear,
1009    /// Nonzero debt paired with one exact stored witness.
1010    Owed {
1011        /// Exact nonzero debt.
1012        debt: ClosureDebt,
1013        /// Current repayment witness.
1014        edge: StoredEdge,
1015    },
1016}
1017
1018/// Opaque proof that ordinary detached attach entered from a legal closure state.
1019///
1020/// Only [`ClosureState::ordinary_detached_attach_admission`] constructs this
1021/// value. Recovery-fenced DCR, DMR, and `DCursor` states therefore cannot enter
1022/// the ordinary detached-attach path.
1023#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1024pub struct OrdinaryDetachedAttachAdmission {
1025    _private: (),
1026}
1027
1028impl ClosureState {
1029    /// Admits ordinary detached attach only from clear closure state.
1030    ///
1031    /// # Errors
1032    ///
1033    /// Returns the unchanged owed state for every stored edge, including DCR,
1034    /// DMR, and `DCursor`.
1035    pub const fn ordinary_detached_attach_admission(
1036        self,
1037    ) -> Result<OrdinaryDetachedAttachAdmission, Self> {
1038        match self {
1039            Self::Clear => Ok(OrdinaryDetachedAttachAdmission { _private: () }),
1040            Self::Owed { .. } => Err(self),
1041        }
1042    }
1043}
1044
1045/// Validated completion restricted to clear, observer projection, or physical
1046/// compaction.
1047///
1048/// Fields are private so DCR, marker delivery, PCP, DMR, and `DCursor` cannot be
1049/// smuggled through a detached attach or Leave completion.
1050#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1051pub struct DebtCompletion(ClosureState);
1052
1053impl DebtCompletion {
1054    /// Selects the only legal edge-free state.
1055    #[must_use]
1056    pub const fn clear() -> Self {
1057        Self(ClosureState::Clear)
1058    }
1059
1060    /// Selects an independent observer-projection successor under nonzero debt.
1061    #[must_use]
1062    pub const fn observer_projection(debt: ClosureDebt, edge: ObserverProjection) -> Self {
1063        Self(ClosureState::Owed {
1064            debt,
1065            edge: StoredEdge::ObserverProjection(edge),
1066        })
1067    }
1068
1069    /// Selects an independent physical-compaction successor under nonzero debt.
1070    #[must_use]
1071    pub const fn physical_compaction(debt: ClosureDebt, edge: PhysicalCompaction) -> Self {
1072        Self(ClosureState::Owed {
1073            debt,
1074            edge: StoredEdge::PhysicalCompaction(edge),
1075        })
1076    }
1077
1078    /// Returns the validated closure state.
1079    #[must_use]
1080    pub const fn into_state(self) -> ClosureState {
1081        self.0
1082    }
1083}
1084
1085/// Opaque authority for the current epoch produced by an ordinary attach.
1086///
1087/// Only a successful non-fenced attach commit can construct this value. It is
1088/// therefore disjoint from [`FencedAttachCommit`]: a recovered binding cannot
1089/// use the ordinary no-marker fate path.
1090///
1091/// ```compile_fail
1092/// use liminal_protocol::lifecycle::{ActiveBinding, OrdinaryBindingAuthority};
1093///
1094/// fn fabricate(binding: ActiveBinding) {
1095///     let _ = OrdinaryBindingAuthority::new(binding, 11);
1096/// }
1097/// ```
1098///
1099/// An ordinary-attach fork also cannot extract authority through the public
1100/// surface. Only the protocol-owned aggregate/replay path may consume it:
1101///
1102/// ```compile_fail
1103/// use liminal_protocol::lifecycle::AttachCommit;
1104///
1105/// fn splice<F, V>(commit: &AttachCommit<F, V>) {
1106///     let _ = commit.ordinary_binding_authority();
1107/// }
1108/// ```
1109///
1110/// Even code handed the opaque type cannot execute its fate transition:
1111///
1112/// ```compile_fail
1113/// use liminal_protocol::lifecycle::{CommittedDiedTerminal, OrdinaryBindingAuthority};
1114///
1115/// fn execute(authority: OrdinaryBindingAuthority, terminal: CommittedDiedTerminal) {
1116///     let _ = authority.binding_fate(terminal, 11);
1117/// }
1118/// ```
1119///
1120/// ```compile_fail
1121/// use liminal_protocol::lifecycle::{Event, OrdinaryBindingAuthority};
1122///
1123/// fn advance(authority: OrdinaryBindingAuthority, event: Event) {
1124///     let _ = authority.cursor_progressed(event);
1125/// }
1126/// ```
1127#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1128pub struct OrdinaryBindingAuthority {
1129    binding: ActiveBinding,
1130    through_seq: DeliverySeq,
1131}
1132
1133impl OrdinaryBindingAuthority {
1134    pub(crate) const fn new(binding: ActiveBinding, through_seq: DeliverySeq) -> Self {
1135        Self {
1136            binding,
1137            through_seq,
1138        }
1139    }
1140
1141    /// Returns the exact authoritative binding committed by ordinary attach.
1142    #[must_use]
1143    pub const fn binding(self) -> ActiveBinding {
1144        self.binding
1145    }
1146
1147    /// Returns the durable no-marker cursor carried through ordinary attach.
1148    #[must_use]
1149    pub const fn through_seq(self) -> DeliverySeq {
1150        self.through_seq
1151    }
1152
1153    /// Advances this ordinary binding's cursor through one exact normal ack.
1154    ///
1155    /// The returned authority preserves its attach provenance while replacing
1156    /// the cursor only when participant, epoch, and previous boundary all match.
1157    ///
1158    /// # Errors
1159    ///
1160    /// Returns this authority unchanged for another event class, participant,
1161    /// epoch, or previous cursor.
1162    pub(crate) fn cursor_progressed(self, event: Event) -> Result<Self, Self> {
1163        let EventKind::CursorProgressed {
1164            participant_id,
1165            binding_epoch,
1166            progress:
1167                CursorProgressEvent::Normal {
1168                    previous_cursor,
1169                    through_seq,
1170                },
1171            ..
1172        } = event.0
1173        else {
1174            return Err(self);
1175        };
1176        self.participant_ack_progressed(
1177            self.binding.conversation_id,
1178            participant_id,
1179            binding_epoch,
1180            previous_cursor,
1181            through_seq,
1182        )
1183    }
1184
1185    /// Replays one protocol-selected normal acknowledgement into this authority.
1186    pub(crate) fn participant_ack_progressed(
1187        self,
1188        conversation_id: ConversationId,
1189        participant_id: ParticipantId,
1190        binding_epoch: BindingEpoch,
1191        previous_cursor: DeliverySeq,
1192        through_seq: DeliverySeq,
1193    ) -> Result<Self, Self> {
1194        if conversation_id != self.binding.conversation_id
1195            || participant_id != self.binding.participant_id
1196            || binding_epoch != self.binding.binding_epoch
1197            || previous_cursor != self.through_seq
1198            || through_seq <= previous_cursor
1199        {
1200            return Err(self);
1201        }
1202        Ok(Self {
1203            through_seq,
1204            ..self
1205        })
1206    }
1207
1208    /// Consumes the exact durable death of this ordinary binding.
1209    ///
1210    /// # Errors
1211    ///
1212    /// Returns this authority unchanged unless the terminal names the same
1213    /// participant, conversation, and binding epoch.
1214    pub(crate) fn binding_fate(
1215        self,
1216        terminal: CommittedDiedTerminal,
1217        resulting_floor: DeliverySeq,
1218    ) -> Result<OrdinaryBindingFate, Self> {
1219        if terminal.participant_id() != self.binding.participant_id
1220            || terminal.conversation_id() != self.binding.conversation_id
1221            || terminal.binding_epoch() != self.binding.binding_epoch
1222        {
1223            return Err(self);
1224        }
1225        Ok(OrdinaryBindingFate {
1226            conversation_id: self.binding.conversation_id,
1227            through_seq: self.through_seq,
1228            resulting_floor,
1229            terminal,
1230            release: DetachedCursorRelease {
1231                participant_id: self.binding.participant_id,
1232                last_dead_binding_epoch: self.binding.binding_epoch,
1233            },
1234        })
1235    }
1236}
1237
1238/// Exact no-marker fate derived from an ordinary attach and its durable death.
1239///
1240/// Fields are private and the only public producer consumes an
1241/// [`AttachCommit`](crate::lifecycle::AttachCommit) carrying ordinary
1242/// provenance. A fenced attach cannot produce this type, so
1243/// executing it cannot bypass `FencedAttachCommit::recovered_binding_fate`.
1244#[derive(Clone, Copy, Debug, Eq)]
1245pub struct OrdinaryBindingFate {
1246    conversation_id: ConversationId,
1247    through_seq: DeliverySeq,
1248    resulting_floor: DeliverySeq,
1249    terminal: CommittedDiedTerminal,
1250    release: DetachedCursorRelease,
1251}
1252
1253impl PartialEq for OrdinaryBindingFate {
1254    fn eq(&self, other: &Self) -> bool {
1255        (
1256            self.conversation_id,
1257            self.through_seq,
1258            self.resulting_floor,
1259            self.release,
1260        ) == (
1261            other.conversation_id,
1262            other.through_seq,
1263            other.resulting_floor,
1264            other.release,
1265        )
1266    }
1267}
1268
1269impl OrdinaryBindingFate {
1270    pub(super) const fn storage_description(&self) -> OrdinaryBindingFateRestore {
1271        OrdinaryBindingFateRestore {
1272            authority: OrdinaryBindingAuthorityRestore {
1273                binding: ActiveBinding {
1274                    conversation_id: self.conversation_id,
1275                    participant_id: self.release.participant_id,
1276                    binding_epoch: self.release.last_dead_binding_epoch,
1277                },
1278                through_seq: self.through_seq,
1279            },
1280            terminal: super::CommittedBindingTerminal::Died(self.terminal).storage_description(),
1281            resulting_floor: self.resulting_floor,
1282        }
1283    }
1284
1285    /// Returns the conversation validated against the committed `Died` terminal.
1286    #[must_use]
1287    pub const fn conversation_id(self) -> ConversationId {
1288        self.conversation_id
1289    }
1290
1291    /// Returns the durable cursor preceding the ordinary binding's death.
1292    #[must_use]
1293    pub const fn through_seq(self) -> DeliverySeq {
1294        self.through_seq
1295    }
1296
1297    /// Returns the participant whose ordinary binding died.
1298    #[must_use]
1299    pub const fn participant_id(self) -> ParticipantId {
1300        self.release.participant_id
1301    }
1302
1303    /// Returns the exact dead binding epoch whose fate was observed.
1304    #[must_use]
1305    pub const fn last_dead_binding_epoch(self) -> BindingEpoch {
1306        self.release.last_dead_binding_epoch
1307    }
1308
1309    /// Returns the measured floor from the binding-fate transaction.
1310    #[must_use]
1311    pub const fn resulting_floor(self) -> DeliverySeq {
1312        self.resulting_floor
1313    }
1314    /// Projects the exact floor measured by this binding fate.
1315    #[must_use]
1316    pub const fn observer_progress_projection(&self) -> ObserverProgressProjection {
1317        ObserverProgressProjection::sourced(
1318            self.conversation_id,
1319            self.resulting_floor,
1320            super::checkpoint_codec::ObserverProjectionSource::Ordinary {
1321                binding: super::ActiveBinding {
1322                    conversation_id: self.conversation_id,
1323                    participant_id: self.release.participant_id,
1324                    binding_epoch: self.release.last_dead_binding_epoch,
1325                },
1326                through_seq: self.through_seq,
1327                terminal: super::CommittedBindingTerminal::Died(self.terminal)
1328                    .storage_description(),
1329            },
1330        )
1331    }
1332
1333    /// Selects direct `DetachedCursorRelease` when no storage edge precedes it.
1334    #[must_use]
1335    pub const fn into_direct_state(self, debt: ClosureDebt) -> ClosureState {
1336        owed(debt, StoredEdge::DetachedCursorRelease(self.release))
1337    }
1338}
1339
1340/// Opaque proof that an exact marker-fenced attach committed.
1341///
1342/// Only the move-consuming frontier-owner mint constructs this value. Ordinary
1343/// attach therefore cannot fabricate marker acceptance or advance a cursor
1344/// merely by presenting a marker sequence.
1345#[derive(Debug, PartialEq, Eq)]
1346pub struct FencedAttachCommit {
1347    conversation_id: ConversationId,
1348    participant_id: ParticipantId,
1349    marker_delivery_seq: DeliverySeq,
1350    prior_binding_epoch: BindingEpoch,
1351    new_binding_epoch: BindingEpoch,
1352    next_state: ClosureState,
1353}
1354
1355impl FencedAttachCommit {
1356    /// Returns the conversation inherited from the consumed recovery edge.
1357    #[must_use]
1358    pub const fn conversation_id(&self) -> ConversationId {
1359        self.conversation_id
1360    }
1361
1362    /// Returns the participant whose fenced recovery committed.
1363    #[must_use]
1364    pub const fn participant_id(&self) -> ParticipantId {
1365        self.participant_id
1366    }
1367
1368    /// Returns the exact delivered marker accepted by the commit.
1369    #[must_use]
1370    pub const fn marker_delivery_seq(&self) -> DeliverySeq {
1371        self.marker_delivery_seq
1372    }
1373
1374    /// Returns the exact dead binding epoch that durably received the marker.
1375    #[must_use]
1376    pub const fn prior_binding_epoch(&self) -> BindingEpoch {
1377        self.prior_binding_epoch
1378    }
1379
1380    /// Returns the exact newly committed authoritative binding epoch.
1381    #[must_use]
1382    pub const fn new_binding_epoch(&self) -> BindingEpoch {
1383        self.new_binding_epoch
1384    }
1385
1386    /// Returns the measured clear, observer-projection, or compaction successor.
1387    #[must_use]
1388    pub const fn next_state(&self) -> ClosureState {
1389        self.next_state
1390    }
1391
1392    /// Revalidates a durable fenced proof against its retained marker record.
1393    ///
1394    /// This is restoration, not a proof mint: the frontier continues to own the
1395    /// retained record while storage reconstructs the already-committed proof.
1396    pub(super) fn restore_validated(
1397        recovery: DetachedCredentialRecovery,
1398        record_authority: &ValidatedMarkerRecord,
1399        debt: ClosureDebt,
1400        event: Event,
1401        successor: DebtCompletion,
1402    ) -> Option<Self> {
1403        if debt.value().is_zero()
1404            || record_authority.conversation_id() != recovery.conversation_id
1405            || record_authority.participant_id() != recovery.participant_id
1406            || record_authority.delivery_seq() != recovery.marker_delivery_seq
1407            || record_authority.target_binding()
1408                != super::FrontierBinding::Detached(recovery.prior_binding_epoch)
1409            || record_authority.occurrence()
1410                != super::claim_frontier::MarkerRecordOccurrence::Delivered
1411        {
1412            return None;
1413        }
1414        let EventKind::FencedRecoveryCommitted {
1415            participant_id,
1416            marker_delivery_seq,
1417            prior_binding_epoch,
1418            new_binding_epoch,
1419            ..
1420        } = event.0
1421        else {
1422            return None;
1423        };
1424        if participant_id != recovery.participant_id
1425            || marker_delivery_seq != recovery.marker_delivery_seq
1426            || prior_binding_epoch != recovery.prior_binding_epoch
1427            || !is_next_generation(prior_binding_epoch, new_binding_epoch)
1428        {
1429            return None;
1430        }
1431        Some(Self {
1432            conversation_id: recovery.conversation_id,
1433            participant_id,
1434            marker_delivery_seq,
1435            prior_binding_epoch,
1436            new_binding_epoch,
1437            next_state: successor.into_state(),
1438        })
1439    }
1440
1441    /// Validates the exact fate of this commit's recovered binding epoch.
1442    ///
1443    /// The returned authority retains both the fenced-attach provenance and its
1444    /// exact nonzero-debt OP/PC successor. It must be consumed by that stored
1445    /// edge's recovered-fate transition, so a fate that precedes storage
1446    /// completion cannot lose the required `DetachedCursorRelease` suffix.
1447    ///
1448    /// # Errors
1449    ///
1450    /// Returns the unchanged post-attach state unless the event names this
1451    /// participant and the exact newly committed binding epoch, or when the
1452    /// fenced attach had already cleared debt.
1453    pub(super) fn recovered_binding_fate(
1454        self,
1455        event: Event,
1456    ) -> Result<RecoveredBindingFate, Box<Self>> {
1457        let EventKind::BindingFateObserved {
1458            participant_id,
1459            binding_epoch,
1460            resulting_floor,
1461        } = event.0
1462        else {
1463            return Err(Box::new(self));
1464        };
1465        if participant_id != self.participant_id || binding_epoch != self.new_binding_epoch {
1466            return Err(Box::new(self));
1467        }
1468        let ClosureState::Owed { debt, edge } = self.next_state else {
1469            return Err(Box::new(self));
1470        };
1471        let predecessor = match edge {
1472            StoredEdge::ObserverProjection(value) => {
1473                RecoveredStorageEdge::ObserverProjection(value)
1474            }
1475            StoredEdge::PhysicalCompaction(value) => {
1476                RecoveredStorageEdge::PhysicalCompaction(value)
1477            }
1478            _ => return Err(Box::new(self)),
1479        };
1480        Ok(RecoveredBindingFate {
1481            conversation_id: self.conversation_id,
1482            predecessor_debt: debt,
1483            predecessor,
1484            resulting_floor,
1485            release: DetachedCursorRelease {
1486                participant_id,
1487                last_dead_binding_epoch: binding_epoch,
1488            },
1489        })
1490    }
1491}
1492
1493#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1494enum RecoveredStorageEdge {
1495    ObserverProjection(ObserverProjection),
1496    PhysicalCompaction(PhysicalCompaction),
1497}
1498
1499impl RecoveredStorageEdge {
1500    const fn into_stored_edge(self) -> StoredEdge {
1501        match self {
1502            Self::ObserverProjection(value) => StoredEdge::ObserverProjection(value),
1503            Self::PhysicalCompaction(value) => StoredEdge::PhysicalCompaction(value),
1504        }
1505    }
1506}
1507
1508/// Exact recovered-binding fate authority derived from a fenced attach.
1509///
1510/// Fields are private: only `FencedAttachCommit::recovered_binding_fate` can
1511/// bind a no-marker cursor release to the newly recovered epoch and the exact
1512/// OP/PC state installed by that attach.
1513#[derive(Debug, PartialEq, Eq)]
1514pub struct RecoveredBindingFate {
1515    conversation_id: ConversationId,
1516    predecessor_debt: ClosureDebt,
1517    predecessor: RecoveredStorageEdge,
1518    resulting_floor: DeliverySeq,
1519    release: DetachedCursorRelease,
1520}
1521
1522impl RecoveredBindingFate {
1523    /// Returns the conversation inherited from the fenced-attach provenance.
1524    #[must_use]
1525    pub const fn conversation_id(&self) -> ConversationId {
1526        self.conversation_id
1527    }
1528
1529    /// Returns the exact post-attach state to which this authority is bound.
1530    #[must_use]
1531    pub const fn predecessor_state(&self) -> ClosureState {
1532        owed(self.predecessor_debt, self.predecessor.into_stored_edge())
1533    }
1534
1535    /// Returns the participant whose recovered binding died.
1536    #[must_use]
1537    pub const fn participant_id(&self) -> ParticipantId {
1538        self.release.participant_id
1539    }
1540
1541    /// Returns the exact recovered epoch whose fate was observed.
1542    #[must_use]
1543    pub const fn last_dead_binding_epoch(&self) -> BindingEpoch {
1544        self.release.last_dead_binding_epoch
1545    }
1546
1547    /// Returns the floor measured in the binding-fate transaction.
1548    #[must_use]
1549    pub const fn resulting_floor(&self) -> DeliverySeq {
1550        self.resulting_floor
1551    }
1552    /// Projects the exact floor measured by this recovered binding fate.
1553    #[must_use]
1554    pub const fn observer_progress_projection(&self) -> ObserverProgressProjection {
1555        ObserverProgressProjection::sourced(
1556            self.conversation_id,
1557            self.resulting_floor,
1558            super::checkpoint_codec::ObserverProjectionSource::Recovered {
1559                binding: super::ActiveBinding {
1560                    conversation_id: self.conversation_id,
1561                    participant_id: self.release.participant_id,
1562                    binding_epoch: self.release.last_dead_binding_epoch,
1563                },
1564                predecessor: match self.predecessor {
1565                    RecoveredStorageEdge::ObserverProjection(edge) => {
1566                        super::DebtCompletionRestore::ObserverProjection {
1567                            debt: self.predecessor_debt.value(),
1568                            through_seq: edge.through_seq(),
1569                        }
1570                    }
1571                    RecoveredStorageEdge::PhysicalCompaction(edge) => {
1572                        super::DebtCompletionRestore::PhysicalCompaction {
1573                            debt: self.predecessor_debt.value(),
1574                            from_floor: edge.from_floor(),
1575                            through_seq: edge.through_seq(),
1576                        }
1577                    }
1578                },
1579            },
1580        )
1581    }
1582}
1583
1584/// Latent cursor-release suffix while an earlier OP/PC witness remains stored.
1585///
1586/// This opaque value must survive alongside the preserved storage edge. Exact
1587/// completion of that edge consumes it and installs `DetachedCursorRelease`, or
1588/// clears it only when closure debt reaches zero. Both ordinary binding fate
1589/// and fenced recovered fate produce this common post-provenance state.
1590#[derive(Debug, PartialEq, Eq)]
1591pub struct PendingRecoveredCursorRelease {
1592    debt: ClosureDebt,
1593    predecessor: RecoveredStorageEdge,
1594    release: DetachedCursorRelease,
1595}
1596
1597impl PendingRecoveredCursorRelease {
1598    /// Returns the exact OP/PC state that remains current before completion.
1599    #[must_use]
1600    pub const fn current_state(&self) -> ClosureState {
1601        owed(self.debt, self.predecessor.into_stored_edge())
1602    }
1603
1604    /// Returns the participant whose cursor release is pending.
1605    #[must_use]
1606    pub const fn participant_id(&self) -> ParticipantId {
1607        self.release.participant_id
1608    }
1609
1610    /// Returns the exact recovered epoch whose cursor release is pending.
1611    #[must_use]
1612    pub const fn last_dead_binding_epoch(&self) -> BindingEpoch {
1613        self.release.last_dead_binding_epoch
1614    }
1615}
1616
1617/// Exact released state when binding fate covers storage immediately.
1618#[derive(Debug, PartialEq, Eq)]
1619pub struct RecoveredCursorRelease {
1620    debt: ClosureDebt,
1621    release: DetachedCursorRelease,
1622}
1623
1624impl RecoveredCursorRelease {
1625    /// Returns the nonzero debt carried by the cursor-release edge.
1626    #[must_use]
1627    pub const fn debt(&self) -> ClosureDebt {
1628        self.debt
1629    }
1630
1631    /// Returns the exact derived cursor-release witness.
1632    #[must_use]
1633    pub const fn edge(&self) -> DetachedCursorRelease {
1634        self.release
1635    }
1636
1637    /// Installs the exact derived cursor-release state.
1638    #[must_use]
1639    pub const fn into_state(self) -> ClosureState {
1640        owed(self.debt, StoredEdge::DetachedCursorRelease(self.release))
1641    }
1642}
1643
1644/// Preserve-or-cover result for cursor-releasing binding fate against OP/PC.
1645#[derive(Debug, PartialEq, Eq)]
1646pub enum RecoveredBindingFateTransition {
1647    /// Storage remains current and carries a latent cursor-release suffix.
1648    PendingStorage(PendingRecoveredCursorRelease),
1649    /// The fate floor covered storage and selected cursor release immediately.
1650    DetachedCursorRelease(RecoveredCursorRelease),
1651}
1652
1653#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1654enum SuccessorUse {
1655    ObserverCompletion,
1656    ObserverMarkerAppend,
1657    ObserverLeave,
1658    PhysicalCompletion,
1659    PhysicalCover,
1660    CursorGreaterAck,
1661}
1662
1663/// Strict/later successor authority for OP, PC, and greater cumulative ack.
1664///
1665/// The predecessor, consumed event, and validated resulting state are private.
1666/// A value can be obtained only from the exact predecessor edge's builder, so a
1667/// caller cannot substitute an earlier edge or direct DCR at application time.
1668#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1669pub struct ProjectionCompactionSuccessor {
1670    predecessor: StoredEdge,
1671    event: Event,
1672    use_kind: SuccessorUse,
1673    state: ClosureState,
1674}
1675
1676/// Closed cursor-fate result taxonomy retained for API compatibility.
1677///
1678/// [`ParticipantCursorProgress::binding_fate`] produces only the marker-backed
1679/// recovery arm. Executable cursor release is instead installed through
1680/// [`OrdinaryBindingFate`] or [`RecoveredBindingFate`], both of which carry the
1681/// required predecessor authority.
1682#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1683pub enum CursorFateSuccessor {
1684    /// Marker-backed state becomes fenced detached recovery.
1685    DetachedCredentialRecovery(DetachedCredentialRecovery),
1686    /// Reserved cursor-release taxonomy arm; raw continuous fate cannot produce it.
1687    DetachedCursorRelease(DetachedCursorRelease),
1688}
1689
1690impl CursorFateSuccessor {
1691    /// Converts the derived fate into its stored edge.
1692    #[must_use]
1693    pub const fn into_stored_edge(self) -> StoredEdge {
1694        match self {
1695            Self::DetachedCredentialRecovery(value) => {
1696                StoredEdge::DetachedCredentialRecovery(value)
1697            }
1698            Self::DetachedCursorRelease(value) => StoredEdge::DetachedCursorRelease(value),
1699        }
1700    }
1701}
1702
1703/// Exact refusal selected by a detached edge or charged retarget check.
1704#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1705pub enum DetachedAttachRefusal {
1706    /// Ordinary attach violates the recovery fence.
1707    RecoveryFence,
1708    /// Presented marker was never delivered.
1709    MarkerNotDelivered,
1710    /// Edge owns no matching marker.
1711    MarkerMismatch,
1712    /// Marker-backed PCP must be acknowledged before supersession.
1713    DeliveredMarkerAwaitingAck,
1714    /// The proposed positive churn delta exceeds the episode limit.
1715    EpisodeChurnLimit,
1716    /// The proposed binding epoch does not immediately supersede this epoch.
1717    StaleAuthority,
1718    /// Binding-required work cannot run for a detached edge owner.
1719    NoBinding,
1720}
1721
1722#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1723enum DetachedClaimTarget {
1724    CredentialRecovery {
1725        marker_delivery_seq: DeliverySeq,
1726        binding_epoch: BindingEpoch,
1727    },
1728    MarkerRelease {
1729        marker_delivery_seq: DeliverySeq,
1730        binding_epoch: BindingEpoch,
1731    },
1732    CursorRelease {
1733        binding_epoch: BindingEpoch,
1734    },
1735}
1736
1737/// Validated evidence for an exact-current K-backed detached Leave.
1738///
1739/// There is no public constructor. Each detached edge validates participant,
1740/// exact edge target, positive actual record charge, remaining K, and the
1741/// available exit claim before producing this edge-bound value.
1742#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1743pub struct KClaimBackedDetachedLeave {
1744    participant_id: ParticipantId,
1745    target: DetachedClaimTarget,
1746    actual_charge: ResourceVector,
1747}
1748
1749impl KClaimBackedDetachedLeave {
1750    /// Returns the exact detached participant.
1751    #[must_use]
1752    pub const fn participant_id(self) -> ParticipantId {
1753        self.participant_id
1754    }
1755
1756    /// Returns the exact charge already checked against remaining K.
1757    #[must_use]
1758    pub const fn actual_charge(self) -> ResourceVector {
1759        self.actual_charge
1760    }
1761}
1762
1763/// Opaque typed completion event.
1764///
1765/// Constructors validate each event's local scalar shape. Edge transitions then
1766/// consume the event and match its participant, binding, marker, range, and
1767/// boundary against the exact stored predecessor. The private kind set is the
1768/// frozen eight-kind register: marker and normal acknowledgements share
1769/// `CursorProgressed`, while live and detached alternatives share
1770/// `LeaveCommitted`; the convenience constructors do not invent occurrences.
1771#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1772pub struct Event(EventKind);
1773
1774#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1775enum EventKind {
1776    ProjectionCompleted {
1777        through_seq: DeliverySeq,
1778    },
1779    CompactionCompleted {
1780        from_floor: DeliverySeq,
1781        through_seq: DeliverySeq,
1782        resulting_floor: DeliverySeq,
1783    },
1784    MarkerAppended {
1785        marker_delivery_seq: DeliverySeq,
1786        resulting_projection_through: DeliverySeq,
1787    },
1788    MarkerDelivered {
1789        participant_id: ParticipantId,
1790        binding_epoch: BindingEpoch,
1791        marker_delivery_seq: DeliverySeq,
1792    },
1793    CursorProgressed {
1794        participant_index: ParticipantIndex,
1795        participant_id: ParticipantId,
1796        binding_epoch: BindingEpoch,
1797        progress: CursorProgressEvent,
1798        resulting_floor: DeliverySeq,
1799    },
1800    BindingFateObserved {
1801        participant_id: ParticipantId,
1802        binding_epoch: BindingEpoch,
1803        resulting_floor: DeliverySeq,
1804    },
1805    LeaveCommitted {
1806        participant_id: ParticipantId,
1807        authority: LeaveAuthority,
1808        resulting_floor: DeliverySeq,
1809    },
1810    FencedRecoveryCommitted {
1811        participant_id: ParticipantId,
1812        marker_delivery_seq: DeliverySeq,
1813        prior_binding_epoch: BindingEpoch,
1814        new_binding_epoch: BindingEpoch,
1815        resulting_floor: DeliverySeq,
1816    },
1817}
1818
1819#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1820enum CursorProgressEvent {
1821    Normal {
1822        previous_cursor: DeliverySeq,
1823        through_seq: DeliverySeq,
1824    },
1825    Marker {
1826        marker_delivery_seq: DeliverySeq,
1827    },
1828}
1829
1830#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1831enum LeaveAuthority {
1832    Live(BindingEpoch),
1833    Detached,
1834}
1835
1836impl Event {
1837    /// Records observer projection through an exact sequence.
1838    #[must_use]
1839    pub const fn projection_completed(through_seq: DeliverySeq) -> Self {
1840        Self(EventKind::ProjectionCompleted { through_seq })
1841    }
1842
1843    /// Records exact physical compaction and its resulting floor.
1844    #[must_use]
1845    pub const fn compaction_completed(
1846        from_floor: DeliverySeq,
1847        through_seq: DeliverySeq,
1848        resulting_floor: DeliverySeq,
1849    ) -> Option<Self> {
1850        if from_floor <= through_seq && resulting_floor > through_seq {
1851            Some(Self(EventKind::CompactionCompleted {
1852                from_floor,
1853                through_seq,
1854                resulting_floor,
1855            }))
1856        } else {
1857            None
1858        }
1859    }
1860
1861    /// Records a preclaimed marker append that extends an OP suffix.
1862    #[must_use]
1863    pub const fn marker_appended(
1864        marker_delivery_seq: DeliverySeq,
1865        resulting_projection_through: DeliverySeq,
1866    ) -> Self {
1867        Self(EventKind::MarkerAppended {
1868            marker_delivery_seq,
1869            resulting_projection_through,
1870        })
1871    }
1872
1873    /// Records final-emitter delivery of an exact marker to an exact epoch.
1874    #[must_use]
1875    pub const fn marker_delivered(
1876        participant_id: ParticipantId,
1877        binding_epoch: BindingEpoch,
1878        marker_delivery_seq: DeliverySeq,
1879    ) -> Self {
1880        Self(EventKind::MarkerDelivered {
1881            participant_id,
1882            binding_epoch,
1883            marker_delivery_seq,
1884        })
1885    }
1886
1887    /// Records a strictly advancing cumulative normal ack.
1888    ///
1889    /// V1's permanent participant id is the participant index, so the stored
1890    /// occurrence key is derived from `participant_id` rather than accepted as
1891    /// an independently forgeable value.
1892    #[must_use]
1893    pub const fn cursor_progressed(
1894        participant_id: ParticipantId,
1895        binding_epoch: BindingEpoch,
1896        previous_cursor: DeliverySeq,
1897        through_seq: DeliverySeq,
1898        resulting_floor: DeliverySeq,
1899    ) -> Option<Self> {
1900        if through_seq > previous_cursor {
1901            Some(Self(EventKind::CursorProgressed {
1902                participant_index: participant_id,
1903                participant_id,
1904                binding_epoch,
1905                progress: CursorProgressEvent::Normal {
1906                    previous_cursor,
1907                    through_seq,
1908                },
1909                resulting_floor,
1910            }))
1911        } else {
1912            None
1913        }
1914    }
1915
1916    /// Records acceptance of one exact delivered marker, deriving its
1917    /// participant-index occurrence key from the permanent participant id.
1918    #[must_use]
1919    pub const fn marker_acknowledged(
1920        participant_id: ParticipantId,
1921        binding_epoch: BindingEpoch,
1922        marker_delivery_seq: DeliverySeq,
1923        resulting_floor: DeliverySeq,
1924    ) -> Self {
1925        Self(EventKind::CursorProgressed {
1926            participant_index: participant_id,
1927            participant_id,
1928            binding_epoch,
1929            progress: CursorProgressEvent::Marker {
1930                marker_delivery_seq,
1931            },
1932            resulting_floor,
1933        })
1934    }
1935
1936    /// Records exact binding fate and its measured floor effect.
1937    #[must_use]
1938    pub const fn binding_fate_observed(
1939        participant_id: ParticipantId,
1940        binding_epoch: BindingEpoch,
1941        resulting_floor: DeliverySeq,
1942    ) -> Self {
1943        Self(EventKind::BindingFateObserved {
1944            participant_id,
1945            binding_epoch,
1946            resulting_floor,
1947        })
1948    }
1949
1950    /// Records a live-bound Leave and its measured floor effect.
1951    #[must_use]
1952    pub const fn live_leave_committed(
1953        participant_id: ParticipantId,
1954        binding_epoch: BindingEpoch,
1955        resulting_floor: DeliverySeq,
1956    ) -> Self {
1957        Self(EventKind::LeaveCommitted {
1958            participant_id,
1959            authority: LeaveAuthority::Live(binding_epoch),
1960            resulting_floor,
1961        })
1962    }
1963
1964    /// Records a detached Leave and its measured floor effect.
1965    #[must_use]
1966    pub const fn detached_leave_committed(
1967        participant_id: ParticipantId,
1968        resulting_floor: DeliverySeq,
1969    ) -> Self {
1970        Self(EventKind::LeaveCommitted {
1971            participant_id,
1972            authority: LeaveAuthority::Detached,
1973            resulting_floor,
1974        })
1975    }
1976
1977    /// Records exact fenced recovery into a new binding epoch.
1978    #[must_use]
1979    pub const fn fenced_recovery_committed(
1980        participant_id: ParticipantId,
1981        marker_delivery_seq: DeliverySeq,
1982        prior_binding_epoch: BindingEpoch,
1983        new_binding_epoch: BindingEpoch,
1984        resulting_floor: DeliverySeq,
1985    ) -> Self {
1986        Self(EventKind::FencedRecoveryCommitted {
1987            participant_id,
1988            marker_delivery_seq,
1989            prior_binding_epoch,
1990            new_binding_epoch,
1991            resulting_floor,
1992        })
1993    }
1994}
1995
1996impl ObserverProjection {
1997    /// Applies ordinary no-marker binding fate while this OP remains current.
1998    ///
1999    /// The opaque fate carries ordinary-attach and exact-terminal provenance;
2000    /// projection completion must retain its cursor-release suffix while debt
2001    /// remains.
2002    #[must_use]
2003    #[allow(
2004        dead_code,
2005        reason = "the crate-owned binding-fate operation invokes this sealed OP transition"
2006    )]
2007    pub const fn apply_ordinary_binding_fate(
2008        self,
2009        resulting_debt: ClosureDebt,
2010        authority: OrdinaryBindingFate,
2011    ) -> PendingRecoveredCursorRelease {
2012        PendingRecoveredCursorRelease {
2013            debt: resulting_debt,
2014            predecessor: RecoveredStorageEdge::ObserverProjection(self),
2015            release: authority.release,
2016        }
2017    }
2018
2019    /// Applies recovered binding fate while this exact OP remains incomplete.
2020    ///
2021    /// OP is independent of binding fate, so nonzero debt preserves it and the
2022    /// returned opaque value retains the exact `DetachedCursorRelease` suffix
2023    /// until projection completion.
2024    ///
2025    /// # Errors
2026    ///
2027    /// Returns the unconsumed authority unless it was derived for this exact OP
2028    /// and its exact post-attach debt.
2029    pub fn apply_recovered_binding_fate(
2030        self,
2031        debt: ClosureDebt,
2032        resulting_debt: ClosureDebt,
2033        authority: RecoveredBindingFate,
2034    ) -> Result<RecoveredBindingFateTransition, RecoveredBindingFate> {
2035        if authority.predecessor != RecoveredStorageEdge::ObserverProjection(self)
2036            || authority.predecessor_debt != debt
2037        {
2038            return Err(authority);
2039        }
2040        Ok(RecoveredBindingFateTransition::PendingStorage(
2041            PendingRecoveredCursorRelease {
2042                debt: resulting_debt,
2043                predecessor: RecoveredStorageEdge::ObserverProjection(self),
2044                release: authority.release,
2045            },
2046        ))
2047    }
2048
2049    /// Consumes a latent recovered cursor suffix on exact OP completion.
2050    ///
2051    /// # Errors
2052    ///
2053    /// Returns the pending authority intact unless it belongs to this exact OP
2054    /// and the completion event reaches its exact boundary.
2055    pub fn complete_after_recovered_binding_fate(
2056        self,
2057        event: Event,
2058        resulting_debt: Option<ClosureDebt>,
2059        pending: PendingRecoveredCursorRelease,
2060    ) -> Result<ClosureState, PendingRecoveredCursorRelease> {
2061        self.complete_after_binding_fate(event, resulting_debt, pending)
2062    }
2063
2064    /// Consumes a latent ordinary cursor suffix on exact OP completion.
2065    ///
2066    /// # Errors
2067    ///
2068    /// Returns the pending authority intact unless it belongs to this exact OP
2069    /// and the completion event reaches the stored projection boundary.
2070    pub fn complete_after_ordinary_binding_fate(
2071        self,
2072        event: Event,
2073        resulting_debt: Option<ClosureDebt>,
2074        pending: PendingRecoveredCursorRelease,
2075    ) -> Result<ClosureState, PendingRecoveredCursorRelease> {
2076        self.complete_after_binding_fate(event, resulting_debt, pending)
2077    }
2078
2079    /// Consumes a latent cursor-release suffix on exact OP completion.
2080    ///
2081    /// # Errors
2082    ///
2083    /// Returns the pending authority intact unless it belongs to this exact OP
2084    /// and the completion event reaches its exact boundary.
2085    pub(crate) fn complete_after_binding_fate(
2086        self,
2087        event: Event,
2088        resulting_debt: Option<ClosureDebt>,
2089        pending: PendingRecoveredCursorRelease,
2090    ) -> Result<ClosureState, PendingRecoveredCursorRelease> {
2091        if pending.predecessor != RecoveredStorageEdge::ObserverProjection(self)
2092            || projection_completion_boundary(self, event).is_none()
2093        {
2094            return Err(pending);
2095        }
2096        Ok(preserve_or_clear(
2097            resulting_debt,
2098            StoredEdge::DetachedCursorRelease(pending.release),
2099        ))
2100    }
2101
2102    /// Validates clear selection after this exact projection completes.
2103    #[must_use]
2104    pub const fn clear_after_completion(
2105        &self,
2106        event: &Event,
2107    ) -> Option<ProjectionCompactionSuccessor> {
2108        if projection_completion_boundary(*self, *event).is_some() {
2109            Some(ProjectionCompactionSuccessor {
2110                predecessor: StoredEdge::ObserverProjection(*self),
2111                event: *event,
2112                use_kind: SuccessorUse::ObserverCompletion,
2113                state: ClosureState::Clear,
2114            })
2115        } else {
2116            None
2117        }
2118    }
2119
2120    /// Validates a non-DCR strict suffix after this exact projection completes.
2121    #[must_use]
2122    pub const fn strict_after_completion(
2123        &self,
2124        event: &Event,
2125        debt: ClosureDebt,
2126        edge: StoredEdge,
2127        successor_boundary: DeliverySeq,
2128    ) -> Option<ProjectionCompactionSuccessor> {
2129        let Some(completed_through) = projection_completion_boundary(*self, *event) else {
2130            return None;
2131        };
2132        if successor_boundary <= completed_through
2133            || !strict_edge_matches_boundary(edge, successor_boundary)
2134        {
2135            return None;
2136        }
2137        Some(ProjectionCompactionSuccessor {
2138            predecessor: StoredEdge::ObserverProjection(*self),
2139            event: *event,
2140            use_kind: SuccessorUse::ObserverCompletion,
2141            state: ClosureState::Owed { debt, edge },
2142        })
2143    }
2144
2145    /// Consumes exact projection completion and its predecessor-bound successor.
2146    ///
2147    /// # Errors
2148    ///
2149    /// Returns the unchanged owed state when the event or successor authority
2150    /// was not built for this exact projection.
2151    pub fn complete(
2152        self,
2153        debt: ClosureDebt,
2154        event: Event,
2155        successor: ProjectionCompactionSuccessor,
2156    ) -> Result<ClosureState, ClosureState> {
2157        let original = owed(debt, StoredEdge::ObserverProjection(self));
2158        if successor.predecessor == StoredEdge::ObserverProjection(self)
2159            && successor.event == event
2160            && successor.use_kind == SuccessorUse::ObserverCompletion
2161            && projection_completion_boundary(self, event).is_some()
2162        {
2163            Ok(successor.state)
2164        } else {
2165            Err(original)
2166        }
2167    }
2168
2169    /// Validates the exact later OP selected by a preclaimed marker append.
2170    #[must_use]
2171    pub const fn later_projection_after_marker(
2172        &self,
2173        event: &Event,
2174        debt: ClosureDebt,
2175        successor: Self,
2176    ) -> Option<ProjectionCompactionSuccessor> {
2177        let EventKind::MarkerAppended {
2178            marker_delivery_seq,
2179            resulting_projection_through,
2180        } = event.0
2181        else {
2182            return None;
2183        };
2184        if marker_delivery_seq <= self.through_seq
2185            || resulting_projection_through < marker_delivery_seq
2186            || successor.through_seq != resulting_projection_through
2187        {
2188            return None;
2189        }
2190        Some(ProjectionCompactionSuccessor {
2191            predecessor: StoredEdge::ObserverProjection(*self),
2192            event: *event,
2193            use_kind: SuccessorUse::ObserverMarkerAppend,
2194            state: owed(debt, StoredEdge::ObserverProjection(successor)),
2195        })
2196    }
2197
2198    /// Consumes the marker occurrence and atomically installs its exact later OP.
2199    ///
2200    /// # Errors
2201    ///
2202    /// Returns the unchanged owed state when the event-bound successor was
2203    /// built for another projection or occurrence.
2204    pub fn marker_appended(
2205        self,
2206        debt: ClosureDebt,
2207        event: Event,
2208        successor: ProjectionCompactionSuccessor,
2209    ) -> Result<ClosureState, ClosureState> {
2210        let original = owed(debt, StoredEdge::ObserverProjection(self));
2211        if successor.predecessor == StoredEdge::ObserverProjection(self)
2212            && successor.event == event
2213            && successor.use_kind == SuccessorUse::ObserverMarkerAppend
2214        {
2215            Ok(successor.state)
2216        } else {
2217            Err(original)
2218        }
2219    }
2220
2221    /// Validates the exact later OP selected atomically by a live or detached Leave.
2222    #[must_use]
2223    pub const fn later_projection_after_leave(
2224        &self,
2225        event: &Event,
2226        debt: ClosureDebt,
2227        successor: Self,
2228    ) -> Option<ProjectionCompactionSuccessor> {
2229        let EventKind::LeaveCommitted {
2230            resulting_floor, ..
2231        } = event.0
2232        else {
2233            return None;
2234        };
2235        if successor.through_seq <= self.through_seq || successor.through_seq < resulting_floor {
2236            return None;
2237        }
2238        Some(ProjectionCompactionSuccessor {
2239            predecessor: StoredEdge::ObserverProjection(*self),
2240            event: *event,
2241            use_kind: SuccessorUse::ObserverLeave,
2242            state: owed(debt, StoredEdge::ObserverProjection(successor)),
2243        })
2244    }
2245
2246    /// Consumes exact Leave and atomically installs its predecessor-bound later OP.
2247    ///
2248    /// # Errors
2249    ///
2250    /// Returns the unchanged owed state unless the successor was built for
2251    /// this exact OP and Leave occurrence.
2252    pub fn leave_with_later_projection(
2253        self,
2254        debt: ClosureDebt,
2255        event: Event,
2256        successor: ProjectionCompactionSuccessor,
2257    ) -> Result<ClosureState, ClosureState> {
2258        let original = owed(debt, StoredEdge::ObserverProjection(self));
2259        if successor.predecessor == StoredEdge::ObserverProjection(self)
2260            && successor.event == event
2261            && successor.use_kind == SuccessorUse::ObserverLeave
2262        {
2263            Ok(successor.state)
2264        } else {
2265            Err(original)
2266        }
2267    }
2268
2269    /// Consumes an independently valid cursor, marker, fate, or Leave event and
2270    /// preserves this exact OP while debt remains, or clears it with debt.
2271    ///
2272    /// # Errors
2273    ///
2274    /// Returns the unchanged owed state for an event outside those independent
2275    /// invalidator classes.
2276    pub const fn independent_event(
2277        self,
2278        debt: ClosureDebt,
2279        event: Event,
2280        resulting_debt: Option<ClosureDebt>,
2281    ) -> Result<ClosureState, ClosureState> {
2282        let original = owed(debt, StoredEdge::ObserverProjection(self));
2283        if !matches!(
2284            event.0,
2285            EventKind::CursorProgressed { .. }
2286                | EventKind::BindingFateObserved { .. }
2287                | EventKind::LeaveCommitted { .. }
2288        ) {
2289            return Err(original);
2290        }
2291        Ok(preserve_or_clear(
2292            resulting_debt,
2293            StoredEdge::ObserverProjection(self),
2294        ))
2295    }
2296
2297    /// Applies a binding change only after a positive charged churn preflight.
2298    ///
2299    /// # Errors
2300    ///
2301    /// Returns the unchanged state and `EpisodeChurnLimit` when the delta is
2302    /// zero or would exceed the episode limit.
2303    pub const fn charged_binding_change(
2304        self,
2305        debt: ClosureDebt,
2306        episode_churn_used: u64,
2307        delta_cycles: u64,
2308        episode_churn_limit: u64,
2309        resulting_debt: Option<ClosureDebt>,
2310    ) -> Result<ClosureState, (ClosureState, DetachedAttachRefusal)> {
2311        let original = owed(debt, StoredEdge::ObserverProjection(self));
2312        if !charged_churn_fits(episode_churn_used, delta_cycles, episode_churn_limit) {
2313            return Err((original, DetachedAttachRefusal::EpisodeChurnLimit));
2314        }
2315        Ok(preserve_or_clear(
2316            resulting_debt,
2317            StoredEdge::ObserverProjection(self),
2318        ))
2319    }
2320}
2321
2322impl PhysicalCompaction {
2323    /// Applies ordinary no-marker binding fate by preserving or covering PC.
2324    #[must_use]
2325    #[allow(
2326        dead_code,
2327        reason = "the crate-owned binding-fate replay boundary invokes this sealed PC transition"
2328    )]
2329    pub(crate) const fn apply_ordinary_binding_fate(
2330        self,
2331        resulting_debt: ClosureDebt,
2332        authority: OrdinaryBindingFate,
2333    ) -> RecoveredBindingFateTransition {
2334        if authority.resulting_floor > self.through_seq {
2335            RecoveredBindingFateTransition::DetachedCursorRelease(RecoveredCursorRelease {
2336                debt: resulting_debt,
2337                release: authority.release,
2338            })
2339        } else {
2340            RecoveredBindingFateTransition::PendingStorage(PendingRecoveredCursorRelease {
2341                debt: resulting_debt,
2342                predecessor: RecoveredStorageEdge::PhysicalCompaction(self),
2343                release: authority.release,
2344            })
2345        }
2346    }
2347
2348    /// Applies recovered binding fate by preserving or covering this exact PC.
2349    ///
2350    /// A fate floor at or below `through_seq` preserves PC and returns a latent
2351    /// cursor-release suffix. A greater floor covers PC immediately and selects
2352    /// the exact cursor release derived from the fenced attach.
2353    ///
2354    /// # Errors
2355    ///
2356    /// Returns the unconsumed authority unless it was derived for this exact PC
2357    /// and its exact post-attach debt.
2358    pub fn apply_recovered_binding_fate(
2359        self,
2360        debt: ClosureDebt,
2361        resulting_debt: ClosureDebt,
2362        authority: RecoveredBindingFate,
2363    ) -> Result<RecoveredBindingFateTransition, RecoveredBindingFate> {
2364        if authority.predecessor != RecoveredStorageEdge::PhysicalCompaction(self)
2365            || authority.predecessor_debt != debt
2366        {
2367            return Err(authority);
2368        }
2369        if authority.resulting_floor > self.through_seq {
2370            Ok(RecoveredBindingFateTransition::DetachedCursorRelease(
2371                RecoveredCursorRelease {
2372                    debt: resulting_debt,
2373                    release: authority.release,
2374                },
2375            ))
2376        } else {
2377            Ok(RecoveredBindingFateTransition::PendingStorage(
2378                PendingRecoveredCursorRelease {
2379                    debt: resulting_debt,
2380                    predecessor: RecoveredStorageEdge::PhysicalCompaction(self),
2381                    release: authority.release,
2382                },
2383            ))
2384        }
2385    }
2386
2387    /// Consumes a latent recovered cursor suffix on exact PC completion.
2388    ///
2389    /// # Errors
2390    ///
2391    /// Returns the pending authority intact unless it belongs to this exact PC
2392    /// and the completion event covers its exact stored range.
2393    pub fn complete_after_recovered_binding_fate(
2394        self,
2395        event: Event,
2396        resulting_debt: Option<ClosureDebt>,
2397        pending: PendingRecoveredCursorRelease,
2398    ) -> Result<ClosureState, PendingRecoveredCursorRelease> {
2399        self.complete_after_binding_fate(event, resulting_debt, pending)
2400    }
2401
2402    /// Consumes a latent cursor-release suffix on exact PC completion.
2403    ///
2404    /// # Errors
2405    ///
2406    /// Returns the pending authority intact unless it belongs to this exact PC
2407    /// and the completion event covers its exact stored range.
2408    pub(crate) fn complete_after_binding_fate(
2409        self,
2410        event: Event,
2411        resulting_debt: Option<ClosureDebt>,
2412        pending: PendingRecoveredCursorRelease,
2413    ) -> Result<ClosureState, PendingRecoveredCursorRelease> {
2414        if pending.predecessor != RecoveredStorageEdge::PhysicalCompaction(self)
2415            || physical_completion_floor(self, event).is_none()
2416        {
2417            return Err(pending);
2418        }
2419        Ok(preserve_or_clear(
2420            resulting_debt,
2421            StoredEdge::DetachedCursorRelease(pending.release),
2422        ))
2423    }
2424
2425    /// Validates clear selection after exact PC completion.
2426    #[must_use]
2427    pub const fn clear_after_completion(
2428        &self,
2429        event: &Event,
2430    ) -> Option<ProjectionCompactionSuccessor> {
2431        if physical_completion_floor(*self, *event).is_some() {
2432            Some(ProjectionCompactionSuccessor {
2433                predecessor: StoredEdge::PhysicalCompaction(*self),
2434                event: *event,
2435                use_kind: SuccessorUse::PhysicalCompletion,
2436                state: ClosureState::Clear,
2437            })
2438        } else {
2439            None
2440        }
2441    }
2442
2443    /// Validates a non-DCR strict suffix after exact PC completion.
2444    #[must_use]
2445    pub const fn strict_after_completion(
2446        &self,
2447        event: &Event,
2448        debt: ClosureDebt,
2449        edge: StoredEdge,
2450        successor_boundary: DeliverySeq,
2451    ) -> Option<ProjectionCompactionSuccessor> {
2452        let Some(resulting_floor) = physical_completion_floor(*self, *event) else {
2453            return None;
2454        };
2455        if successor_boundary < resulting_floor
2456            || !strict_edge_matches_boundary(edge, successor_boundary)
2457        {
2458            return None;
2459        }
2460        Some(ProjectionCompactionSuccessor {
2461            predecessor: StoredEdge::PhysicalCompaction(*self),
2462            event: *event,
2463            use_kind: SuccessorUse::PhysicalCompletion,
2464            state: ClosureState::Owed { debt, edge },
2465        })
2466    }
2467
2468    /// Consumes exact PC completion.
2469    ///
2470    /// # Errors
2471    ///
2472    /// Returns the unchanged owed state when the event or successor authority
2473    /// does not belong to this exact compaction range.
2474    pub fn complete(
2475        self,
2476        debt: ClosureDebt,
2477        event: Event,
2478        successor: ProjectionCompactionSuccessor,
2479    ) -> Result<ClosureState, ClosureState> {
2480        let original = owed(debt, StoredEdge::PhysicalCompaction(self));
2481        if successor.predecessor == StoredEdge::PhysicalCompaction(self)
2482            && successor.event == event
2483            && successor.use_kind == SuccessorUse::PhysicalCompletion
2484            && physical_completion_floor(self, event).is_some()
2485        {
2486            Ok(successor.state)
2487        } else {
2488            Err(original)
2489        }
2490    }
2491
2492    /// Records a later marker append while preserving this exact active range.
2493    ///
2494    /// # Errors
2495    ///
2496    /// Returns the unchanged owed state unless the appended marker lies
2497    /// strictly after the physical-compaction range and its projection target
2498    /// covers that marker.
2499    pub const fn marker_appended(
2500        self,
2501        debt: ClosureDebt,
2502        event: Event,
2503    ) -> Result<ClosureState, ClosureState> {
2504        let original = owed(debt, StoredEdge::PhysicalCompaction(self));
2505        let EventKind::MarkerAppended {
2506            marker_delivery_seq,
2507            resulting_projection_through,
2508        } = event.0
2509        else {
2510            return Err(original);
2511        };
2512        if marker_delivery_seq <= self.through_seq
2513            || resulting_projection_through < marker_delivery_seq
2514        {
2515            return Err(original);
2516        }
2517        Ok(original)
2518    }
2519
2520    /// Applies an advancing ack, fate, or Leave whose resulting floor does not
2521    /// cover this PC, preserving the exact range while debt remains.
2522    ///
2523    /// # Errors
2524    ///
2525    /// Returns the unchanged owed state when the event is not a progress class
2526    /// or its resulting floor covers the stored range.
2527    pub const fn preserve_progress(
2528        self,
2529        debt: ClosureDebt,
2530        event: Event,
2531        resulting_debt: ClosureDebt,
2532    ) -> Result<ClosureState, ClosureState> {
2533        let original = owed(debt, StoredEdge::PhysicalCompaction(self));
2534        let Some(resulting_floor) = progress_event_floor(event) else {
2535            return Err(original);
2536        };
2537        if resulting_floor > self.through_seq {
2538            return Err(original);
2539        }
2540        Ok(owed(resulting_debt, StoredEdge::PhysicalCompaction(self)))
2541    }
2542
2543    /// Validates clear selection when an ack, fate, or Leave covers this PC.
2544    #[must_use]
2545    pub const fn clear_after_progress(
2546        &self,
2547        event: &Event,
2548    ) -> Option<ProjectionCompactionSuccessor> {
2549        let Some(resulting_floor) = progress_event_floor(*event) else {
2550            return None;
2551        };
2552        if resulting_floor <= self.through_seq {
2553            return None;
2554        }
2555        Some(ProjectionCompactionSuccessor {
2556            predecessor: StoredEdge::PhysicalCompaction(*self),
2557            event: *event,
2558            use_kind: SuccessorUse::PhysicalCover,
2559            state: ClosureState::Clear,
2560        })
2561    }
2562
2563    /// Validates a strict non-DCR suffix when an ack, fate, or Leave covers PC.
2564    #[must_use]
2565    pub const fn strict_after_progress(
2566        &self,
2567        event: &Event,
2568        debt: ClosureDebt,
2569        edge: StoredEdge,
2570        successor_boundary: DeliverySeq,
2571    ) -> Option<ProjectionCompactionSuccessor> {
2572        let Some(resulting_floor) = progress_event_floor(*event) else {
2573            return None;
2574        };
2575        if resulting_floor <= self.through_seq
2576            || successor_boundary < resulting_floor
2577            || !strict_edge_matches_boundary(edge, successor_boundary)
2578        {
2579            return None;
2580        }
2581        Some(ProjectionCompactionSuccessor {
2582            predecessor: StoredEdge::PhysicalCompaction(*self),
2583            event: *event,
2584            use_kind: SuccessorUse::PhysicalCover,
2585            state: ClosureState::Owed { debt, edge },
2586        })
2587    }
2588
2589    /// Consumes the covering ack/fate/Leave event and its validated suffix.
2590    ///
2591    /// # Errors
2592    ///
2593    /// Returns the unchanged owed state when the successor authority is not
2594    /// bound to this range and event.
2595    pub fn covered_by_progress(
2596        self,
2597        debt: ClosureDebt,
2598        event: Event,
2599        successor: ProjectionCompactionSuccessor,
2600    ) -> Result<ClosureState, ClosureState> {
2601        let original = owed(debt, StoredEdge::PhysicalCompaction(self));
2602        if successor.predecessor == StoredEdge::PhysicalCompaction(self)
2603            && successor.event == event
2604            && successor.use_kind == SuccessorUse::PhysicalCover
2605        {
2606            Ok(successor.state)
2607        } else {
2608            Err(original)
2609        }
2610    }
2611
2612    /// No-op and refused acknowledgements consume no event and preserve exact PC.
2613    #[must_use]
2614    pub const fn unchanged(self, debt: ClosureDebt) -> ClosureState {
2615        owed(debt, StoredEdge::PhysicalCompaction(self))
2616    }
2617
2618    /// Applies a charged binding change that leaves this PC range uncovered.
2619    ///
2620    /// # Errors
2621    ///
2622    /// Returns the unchanged state with the precise churn or stale-selection
2623    /// refusal when charging fails or the measured floor covers the range.
2624    pub const fn charged_binding_change_preserving(
2625        self,
2626        debt: ClosureDebt,
2627        episode_churn_used: u64,
2628        delta_cycles: u64,
2629        episode_churn_limit: u64,
2630        resulting_floor: DeliverySeq,
2631        resulting_debt: ClosureDebt,
2632    ) -> Result<ClosureState, (ClosureState, DetachedAttachRefusal)> {
2633        let original = owed(debt, StoredEdge::PhysicalCompaction(self));
2634        if !charged_churn_fits(episode_churn_used, delta_cycles, episode_churn_limit) {
2635            return Err((original, DetachedAttachRefusal::EpisodeChurnLimit));
2636        }
2637        if resulting_floor > self.through_seq {
2638            return Err((original, DetachedAttachRefusal::StaleAuthority));
2639        }
2640        Ok(owed(resulting_debt, StoredEdge::PhysicalCompaction(self)))
2641    }
2642
2643    /// Applies a charged binding change whose measured floor covers this PC.
2644    ///
2645    /// # Errors
2646    ///
2647    /// Returns the unchanged state with the precise churn or stale-selection
2648    /// refusal when charging fails or the proposed strict suffix is invalid.
2649    #[allow(clippy::too_many_arguments)]
2650    pub const fn charged_binding_change_covering(
2651        self,
2652        debt: ClosureDebt,
2653        episode_churn_used: u64,
2654        delta_cycles: u64,
2655        episode_churn_limit: u64,
2656        resulting_floor: DeliverySeq,
2657        resulting_debt: ClosureDebt,
2658        edge: StoredEdge,
2659        successor_boundary: DeliverySeq,
2660    ) -> Result<ClosureState, (ClosureState, DetachedAttachRefusal)> {
2661        let original = owed(debt, StoredEdge::PhysicalCompaction(self));
2662        if !charged_churn_fits(episode_churn_used, delta_cycles, episode_churn_limit) {
2663            return Err((original, DetachedAttachRefusal::EpisodeChurnLimit));
2664        }
2665        if resulting_floor <= self.through_seq
2666            || successor_boundary < resulting_floor
2667            || !strict_edge_matches_boundary(edge, successor_boundary)
2668        {
2669            return Err((original, DetachedAttachRefusal::StaleAuthority));
2670        }
2671        Ok(owed(resulting_debt, edge))
2672    }
2673}
2674
2675impl MarkerDelivery {
2676    /// Consumes sealed marker-delivery authority and derives its exact cursor
2677    /// progress witness after validating the delivered event.
2678    ///
2679    /// This debt-independent projection is for owners that persist the marker
2680    /// successor separately from later closure-accounting evolution. Callers
2681    /// cannot mint `MarkerDelivery`; only a validated marker drain or restore
2682    /// can supply this authority.
2683    ///
2684    /// # Errors
2685    ///
2686    /// Returns the unchanged sealed delivery unless participant, epoch, and
2687    /// marker sequence exactly match.
2688    pub fn delivered_progress(self, event: Event) -> Result<ParticipantCursorProgress, Self> {
2689        let EventKind::MarkerDelivered {
2690            participant_id,
2691            binding_epoch,
2692            marker_delivery_seq,
2693        } = event.0
2694        else {
2695            return Err(self);
2696        };
2697        if participant_id != self.participant_id
2698            || binding_epoch != self.binding_epoch
2699            || marker_delivery_seq != self.marker_delivery_seq
2700        {
2701            return Err(self);
2702        }
2703        Ok(ParticipantCursorProgress::Marker(CursorProgressMarker {
2704            conversation_id: self.conversation_id,
2705            participant_id,
2706            binding_epoch,
2707            through_seq: marker_delivery_seq,
2708            marker_delivery_seq,
2709        }))
2710    }
2711
2712    /// Consumes exact final-emitter delivery and derives marker-backed PCP.
2713    ///
2714    /// The PCP boundary is the delivered marker itself; callers cannot supply a
2715    /// different cursor witness.
2716    ///
2717    /// # Errors
2718    ///
2719    /// Returns the unchanged owed state unless participant, epoch, and marker
2720    /// exactly match this delivery witness.
2721    pub fn delivered(self, debt: ClosureDebt, event: Event) -> Result<ClosureState, ClosureState> {
2722        let original = owed(debt, StoredEdge::MarkerDelivery(self));
2723        let Ok(progress) = self.delivered_progress(event) else {
2724            return Err(original);
2725        };
2726        Ok(owed(debt, StoredEdge::ParticipantCursorProgress(progress)))
2727    }
2728
2729    /// Applies a lower normal ack, projection, or compaction below the anchor,
2730    /// preserving exact delivery while debt remains or clearing it with debt.
2731    ///
2732    /// # Errors
2733    ///
2734    /// Returns the unchanged owed state when the event is not a permitted lower
2735    /// progress event or reaches the marker anchor.
2736    pub const fn lower_progress(
2737        self,
2738        debt: ClosureDebt,
2739        event: Event,
2740        resulting_debt: Option<ClosureDebt>,
2741    ) -> Result<ClosureState, ClosureState> {
2742        let original = owed(debt, StoredEdge::MarkerDelivery(self));
2743        let is_lower = match event.0 {
2744            EventKind::CursorProgressed {
2745                progress: CursorProgressEvent::Normal { through_seq, .. },
2746                ..
2747            }
2748            | EventKind::ProjectionCompleted { through_seq } => {
2749                through_seq < self.marker_delivery_seq
2750            }
2751            EventKind::CompactionCompleted {
2752                through_seq,
2753                resulting_floor,
2754                ..
2755            } => {
2756                through_seq < self.marker_delivery_seq
2757                    && resulting_floor <= self.marker_delivery_seq
2758            }
2759            _ => false,
2760        };
2761        if !is_lower {
2762            return Err(original);
2763        }
2764        Ok(preserve_or_clear(
2765            resulting_debt,
2766            StoredEdge::MarkerDelivery(self),
2767        ))
2768    }
2769
2770    /// Consumes exact pre-delivery binding fate and derives Leave-only DMR.
2771    ///
2772    /// Takes the durable terminal the ordinary binding fate has always taken,
2773    /// validates it against this delivery's own identity, and drops it. The
2774    /// release it forms is described by a capsule that states a terminal, so a
2775    /// fate driven by an observation alone would form an edge no description
2776    /// could honestly be produced for.
2777    ///
2778    /// # Errors
2779    ///
2780    /// Returns the unchanged owed state unless fate names the exact participant
2781    /// and binding epoch targeted by this undelivered marker, and the terminal
2782    /// names that same participant, epoch and conversation.
2783    pub fn binding_fate(
2784        self,
2785        debt: ClosureDebt,
2786        terminal: super::storage::RestoredBindingFateTerminal,
2787        event: Event,
2788    ) -> Result<ClosureState, ClosureState> {
2789        let original = owed(debt, StoredEdge::MarkerDelivery(self));
2790        let EventKind::BindingFateObserved {
2791            participant_id,
2792            binding_epoch,
2793            ..
2794        } = event.0
2795        else {
2796            return Err(original);
2797        };
2798        if participant_id != self.participant_id || binding_epoch != self.binding_epoch {
2799            return Err(original);
2800        }
2801        if terminal.participant_id() != self.participant_id
2802            || terminal.binding_epoch() != self.binding_epoch
2803            || terminal.conversation_id() != self.conversation_id
2804        {
2805            return Err(original);
2806        }
2807        Ok(owed(
2808            debt,
2809            StoredEdge::DetachedMarkerRelease(DetachedMarkerRelease {
2810                conversation_id: self.conversation_id,
2811                participant_id,
2812                marker_delivery_seq: self.marker_delivery_seq,
2813                last_dead_binding_epoch: binding_epoch,
2814                provenance: DetachedMarkerReleaseProvenance::Fate,
2815            }),
2816        ))
2817    }
2818
2819    /// Retargets undelivered marker delivery after exact charged supersession.
2820    ///
2821    /// # Errors
2822    ///
2823    /// Returns the unchanged delivery with `EpisodeChurnLimit` or
2824    /// `StaleAuthority` when charged churn or the next-generation check fails.
2825    pub const fn retarget(
2826        self,
2827        new_binding_epoch: BindingEpoch,
2828        episode_churn_used: u64,
2829        delta_cycles: u64,
2830        episode_churn_limit: u64,
2831    ) -> Result<Self, (Self, DetachedAttachRefusal)> {
2832        if !charged_churn_fits(episode_churn_used, delta_cycles, episode_churn_limit) {
2833            return Err((self, DetachedAttachRefusal::EpisodeChurnLimit));
2834        }
2835        if !is_next_generation(self.binding_epoch, new_binding_epoch) {
2836            return Err((self, DetachedAttachRefusal::StaleAuthority));
2837        }
2838        Ok(Self {
2839            binding_epoch: new_binding_epoch,
2840            ..self
2841        })
2842    }
2843
2844    /// Consumes exact live Leave and installs only clear/OP/PC.
2845    ///
2846    /// # Errors
2847    ///
2848    /// Returns the unchanged owed state unless Leave names the exact live
2849    /// participant and binding epoch.
2850    pub fn leave(
2851        self,
2852        debt: ClosureDebt,
2853        event: Event,
2854        successor: DebtCompletion,
2855    ) -> Result<ClosureState, ClosureState> {
2856        let original = owed(debt, StoredEdge::MarkerDelivery(self));
2857        let EventKind::LeaveCommitted {
2858            participant_id,
2859            authority: LeaveAuthority::Live(binding_epoch),
2860            ..
2861        } = event.0
2862        else {
2863            return Err(original);
2864        };
2865        if participant_id != self.participant_id || binding_epoch != self.binding_epoch {
2866            return Err(original);
2867        }
2868        Ok(successor.into_state())
2869    }
2870}
2871
2872impl ParticipantCursorProgress {
2873    /// Consumes an equal normal ack or exact marker ack and selects only
2874    /// clear/OP/PC.
2875    ///
2876    /// # Errors
2877    ///
2878    /// Returns the unchanged owed state unless the ack kind, participant,
2879    /// epoch, and boundary exactly satisfy this cursor witness.
2880    pub fn complete_ack(
2881        self,
2882        debt: ClosureDebt,
2883        event: Event,
2884        successor: DebtCompletion,
2885    ) -> Result<ClosureState, ClosureState> {
2886        let original = owed(debt, StoredEdge::ParticipantCursorProgress(self));
2887        let exact = match (self, event.0) {
2888            (
2889                Self::Continuous(value),
2890                EventKind::CursorProgressed {
2891                    participant_id,
2892                    binding_epoch,
2893                    progress: CursorProgressEvent::Normal { through_seq, .. },
2894                    ..
2895                },
2896            ) => {
2897                participant_id == value.participant_id
2898                    && binding_epoch == value.binding_epoch
2899                    && through_seq == value.through_seq
2900            }
2901            (
2902                Self::Marker(value),
2903                EventKind::CursorProgressed {
2904                    participant_id,
2905                    binding_epoch,
2906                    progress: CursorProgressEvent::Normal { through_seq, .. },
2907                    ..
2908                },
2909            ) => {
2910                participant_id == value.participant_id
2911                    && binding_epoch == value.binding_epoch
2912                    && through_seq == value.through_seq
2913            }
2914            (
2915                Self::Marker(value),
2916                EventKind::CursorProgressed {
2917                    participant_id,
2918                    binding_epoch,
2919                    progress:
2920                        CursorProgressEvent::Marker {
2921                            marker_delivery_seq,
2922                        },
2923                    ..
2924                },
2925            ) => {
2926                participant_id == value.participant_id
2927                    && binding_epoch == value.binding_epoch
2928                    && marker_delivery_seq == value.marker_delivery_seq
2929            }
2930            _ => false,
2931        };
2932        if exact {
2933            Ok(successor.into_state())
2934        } else {
2935            Err(original)
2936        }
2937    }
2938
2939    /// Consumes a lesser advancing normal ack and preserves this exact PCP.
2940    ///
2941    /// # Errors
2942    ///
2943    /// Returns the unchanged owed state unless the event is a matching current-
2944    /// epoch normal ack strictly below the stored boundary.
2945    pub fn lesser_ack(
2946        self,
2947        debt: ClosureDebt,
2948        event: Event,
2949        resulting_debt: ClosureDebt,
2950    ) -> Result<ClosureState, ClosureState> {
2951        let original = owed(debt, StoredEdge::ParticipantCursorProgress(self));
2952        let EventKind::CursorProgressed {
2953            participant_id,
2954            binding_epoch,
2955            progress: CursorProgressEvent::Normal { through_seq, .. },
2956            ..
2957        } = event.0
2958        else {
2959            return Err(original);
2960        };
2961        if participant_id != self.participant_id()
2962            || binding_epoch != self.binding_epoch()
2963            || through_seq >= self.through_seq()
2964        {
2965            return Err(original);
2966        }
2967        Ok(owed(
2968            resulting_debt,
2969            StoredEdge::ParticipantCursorProgress(self),
2970        ))
2971    }
2972
2973    /// Validates clear selection for one greater cumulative normal ack.
2974    #[must_use]
2975    pub fn clear_after_greater_ack(&self, event: &Event) -> Option<ProjectionCompactionSuccessor> {
2976        if !greater_ack_matches(*self, *event) {
2977            return None;
2978        }
2979        Some(ProjectionCompactionSuccessor {
2980            predecessor: StoredEdge::ParticipantCursorProgress(*self),
2981            event: *event,
2982            use_kind: SuccessorUse::CursorGreaterAck,
2983            state: ClosureState::Clear,
2984        })
2985    }
2986
2987    /// Validates a strict non-DCR suffix for one greater cumulative normal ack.
2988    #[must_use]
2989    pub fn strict_after_greater_ack(
2990        &self,
2991        event: &Event,
2992        debt: ClosureDebt,
2993        edge: StoredEdge,
2994        successor_boundary: DeliverySeq,
2995    ) -> Option<ProjectionCompactionSuccessor> {
2996        if !greater_ack_matches(*self, *event)
2997            || successor_boundary <= cursor_event_boundary(*event)
2998            || !strict_edge_matches_boundary(edge, successor_boundary)
2999        {
3000            return None;
3001        }
3002        Some(ProjectionCompactionSuccessor {
3003            predecessor: StoredEdge::ParticipantCursorProgress(*self),
3004            event: *event,
3005            use_kind: SuccessorUse::CursorGreaterAck,
3006            state: ClosureState::Owed { debt, edge },
3007        })
3008    }
3009
3010    /// Consumes a greater cumulative normal ack and its predecessor-bound suffix.
3011    ///
3012    /// # Errors
3013    ///
3014    /// Returns the unchanged owed state unless the advancing event and strict
3015    /// successor authority are both bound to this cursor witness.
3016    pub fn greater_ack(
3017        self,
3018        debt: ClosureDebt,
3019        event: Event,
3020        successor: ProjectionCompactionSuccessor,
3021    ) -> Result<ClosureState, ClosureState> {
3022        let original = owed(debt, StoredEdge::ParticipantCursorProgress(self));
3023        if successor.predecessor == StoredEdge::ParticipantCursorProgress(self)
3024            && successor.event == event
3025            && successor.use_kind == SuccessorUse::CursorGreaterAck
3026            && greater_ack_matches(self, event)
3027        {
3028            Ok(successor.state)
3029        } else {
3030            Err(original)
3031        }
3032    }
3033
3034    /// No-op, `AckGap`, and `AckRegression` consume no event and preserve exact PCP.
3035    #[must_use]
3036    pub const fn unchanged(self, debt: ClosureDebt) -> ClosureState {
3037        owed(debt, StoredEdge::ParticipantCursorProgress(self))
3038    }
3039
3040    /// Consumes independent projection/compaction completion and preserves PCP
3041    /// while debt remains, or clears it. Compaction cannot cross an unaccepted
3042    /// marker anchor.
3043    ///
3044    /// # Errors
3045    ///
3046    /// Returns the unchanged owed state for another event class or for a
3047    /// compaction that reaches an unaccepted marker.
3048    pub const fn storage_progress(
3049        self,
3050        debt: ClosureDebt,
3051        event: Event,
3052        resulting_debt: Option<ClosureDebt>,
3053    ) -> Result<ClosureState, ClosureState> {
3054        let original = owed(debt, StoredEdge::ParticipantCursorProgress(self));
3055        let valid = match event.0 {
3056            EventKind::ProjectionCompleted { .. } => true,
3057            EventKind::CompactionCompleted {
3058                through_seq,
3059                resulting_floor,
3060                ..
3061            } => match self.marker_delivery_seq() {
3062                None => true,
3063                Some(marker) => through_seq < marker && resulting_floor <= marker,
3064            },
3065            _ => false,
3066        };
3067        if !valid {
3068            return Err(original);
3069        }
3070        Ok(preserve_or_clear(
3071            resulting_debt,
3072            StoredEdge::ParticipantCursorProgress(self),
3073        ))
3074    }
3075
3076    /// Consumes exact binding fate and derives DCR only from marker-backed PCP.
3077    ///
3078    /// Continuous PCP never accepts this raw-event transition. Ordinary
3079    /// no-marker fate requires [`OrdinaryBindingAuthority`], while the fate of
3080    /// an epoch committed by fenced attach requires
3081    /// `FencedAttachCommit::recovered_binding_fate`.
3082    ///
3083    /// # Errors
3084    ///
3085    /// Returns the unchanged owed state unless fate names the exact participant
3086    /// and binding epoch carried by this cursor witness.
3087    pub fn binding_fate(
3088        self,
3089        debt: ClosureDebt,
3090        terminal: super::storage::RestoredBindingFateTerminal,
3091        event: Event,
3092    ) -> Result<CursorFateSuccessor, ClosureState> {
3093        let original = owed(debt, StoredEdge::ParticipantCursorProgress(self));
3094        let Self::Marker(value) = self else {
3095            return Err(original);
3096        };
3097        let EventKind::BindingFateObserved {
3098            participant_id,
3099            binding_epoch,
3100            resulting_floor,
3101            ..
3102        } = event.0
3103        else {
3104            return Err(original);
3105        };
3106        if participant_id != value.participant_id || binding_epoch != value.binding_epoch {
3107            return Err(original);
3108        }
3109        if terminal.participant_id() != value.participant_id
3110            || terminal.binding_epoch() != value.binding_epoch
3111            || terminal.conversation_id() != value.conversation_id
3112        {
3113            return Err(original);
3114        }
3115        Ok(CursorFateSuccessor::DetachedCredentialRecovery(
3116            DetachedCredentialRecovery {
3117                conversation_id: value.conversation_id,
3118                participant_id: value.participant_id,
3119                marker_delivery_seq: value.marker_delivery_seq,
3120                prior_binding_epoch: value.binding_epoch,
3121                resulting_floor,
3122            },
3123        ))
3124    }
3125
3126    /// Retargets only continuous PCP after exact charged supersession.
3127    ///
3128    /// # Errors
3129    ///
3130    /// Returns the unchanged cursor and the exact delivered-marker, churn, or
3131    /// stale-authority refusal when retargeting is forbidden.
3132    pub const fn retarget(
3133        self,
3134        new_binding_epoch: BindingEpoch,
3135        episode_churn_used: u64,
3136        delta_cycles: u64,
3137        episode_churn_limit: u64,
3138    ) -> Result<Self, (Self, DetachedAttachRefusal)> {
3139        let Self::Continuous(value) = self else {
3140            return Err((self, DetachedAttachRefusal::DeliveredMarkerAwaitingAck));
3141        };
3142        if !charged_churn_fits(episode_churn_used, delta_cycles, episode_churn_limit) {
3143            return Err((self, DetachedAttachRefusal::EpisodeChurnLimit));
3144        }
3145        if !is_next_generation(value.binding_epoch, new_binding_epoch) {
3146            return Err((self, DetachedAttachRefusal::StaleAuthority));
3147        }
3148        Ok(Self::Continuous(CursorProgressContinuous {
3149            binding_epoch: new_binding_epoch,
3150            ..value
3151        }))
3152    }
3153
3154    /// Consumes exact live Leave and installs its measured clear/OP/PC result.
3155    ///
3156    /// # Errors
3157    ///
3158    /// Returns the unchanged owed state unless Leave names the exact live
3159    /// participant and binding epoch.
3160    pub fn leave(
3161        self,
3162        debt: ClosureDebt,
3163        event: Event,
3164        successor: DebtCompletion,
3165    ) -> Result<ClosureState, ClosureState> {
3166        let original = owed(debt, StoredEdge::ParticipantCursorProgress(self));
3167        let EventKind::LeaveCommitted {
3168            participant_id,
3169            authority: LeaveAuthority::Live(binding_epoch),
3170            ..
3171        } = event.0
3172        else {
3173            return Err(original);
3174        };
3175        if participant_id != self.participant_id() || binding_epoch != self.binding_epoch() {
3176            return Err(original);
3177        }
3178        Ok(successor.into_state())
3179    }
3180}
3181
3182/// Crate-private failed proof mint carrying the same one-use marker record back
3183/// to its move-owned frontier.
3184pub(super) struct FencedAttachRecordRefusal {
3185    record: ValidatedMarkerRecord,
3186}
3187
3188impl core::fmt::Debug for FencedAttachRecordRefusal {
3189    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
3190        formatter.write_str("FencedAttachRecordRefusal { record: <linear> }")
3191    }
3192}
3193
3194impl FencedAttachRecordRefusal {
3195    /// Returns the exact record authority for serial reinstall and retry.
3196    pub(super) const fn into_record(self) -> ValidatedMarkerRecord {
3197        self.record
3198    }
3199}
3200
3201impl DetachedCredentialRecovery {
3202    /// Validates exact-current K and exit claims for detached Leave.
3203    #[must_use]
3204    pub const fn validate_leave_claim(
3205        &self,
3206        participant_id: ParticipantId,
3207        actual_charge: ResourceVector,
3208        remaining_k: ResourceVector,
3209        exit_claims: u64,
3210    ) -> Option<KClaimBackedDetachedLeave> {
3211        validate_detached_claim(
3212            self.participant_id,
3213            DetachedClaimTarget::CredentialRecovery {
3214                marker_delivery_seq: self.marker_delivery_seq,
3215                binding_epoch: self.prior_binding_epoch,
3216            },
3217            participant_id,
3218            actual_charge,
3219            remaining_k,
3220            exit_claims,
3221        )
3222    }
3223
3224    /// Consumes the sole fully validated marker occurrence and mints one fenced
3225    /// attach proof.
3226    ///
3227    /// This is deliberately visible only inside the lifecycle module. Public
3228    /// descriptive recovery values cannot invoke it without the private,
3229    /// non-cloneable record removed from a move-owned frontier.
3230    pub(super) fn fenced_attach(
3231        self,
3232        record_authority: ValidatedMarkerRecord,
3233        debt: ClosureDebt,
3234        event: Event,
3235        successor: DebtCompletion,
3236    ) -> Result<FencedAttachCommit, Box<FencedAttachRecordRefusal>> {
3237        let Some(proof) =
3238            FencedAttachCommit::restore_validated(self, &record_authority, debt, event, successor)
3239        else {
3240            return Err(Box::new(FencedAttachRecordRefusal {
3241                record: record_authority,
3242            }));
3243        };
3244        record_authority.consume();
3245        Ok(proof)
3246    }
3247
3248    /// Consumes exact K-backed detached Leave and installs only clear/OP/PC.
3249    ///
3250    /// # Errors
3251    ///
3252    /// Returns the unchanged owed state unless the Leave and private claim
3253    /// authority are bound to this exact recovery edge.
3254    pub fn detached_leave(
3255        self,
3256        debt: ClosureDebt,
3257        event: Event,
3258        evidence: KClaimBackedDetachedLeave,
3259        successor: DebtCompletion,
3260    ) -> Result<ClosureState, ClosureState> {
3261        let original = owed(debt, StoredEdge::DetachedCredentialRecovery(self));
3262        let EventKind::LeaveCommitted {
3263            participant_id,
3264            authority: LeaveAuthority::Detached,
3265            ..
3266        } = event.0
3267        else {
3268            return Err(original);
3269        };
3270        let target = DetachedClaimTarget::CredentialRecovery {
3271            marker_delivery_seq: self.marker_delivery_seq,
3272            binding_epoch: self.prior_binding_epoch,
3273        };
3274        if participant_id != self.participant_id
3275            || evidence.participant_id != self.participant_id
3276            || evidence.target != target
3277        {
3278            return Err(original);
3279        }
3280        Ok(successor.into_state())
3281    }
3282
3283    /// Ordinary non-fenced attach is refused without mutation.
3284    #[must_use]
3285    pub const fn ordinary_attach_refusal(self) -> DetachedAttachRefusal {
3286        let _ = self;
3287        DetachedAttachRefusal::RecoveryFence
3288    }
3289
3290    /// An explicit marker is eligible only when it is the exact recovery marker.
3291    #[must_use]
3292    pub const fn marker_attach_refusal(
3293        self,
3294        presented_marker: DeliverySeq,
3295    ) -> Option<DetachedAttachRefusal> {
3296        if presented_marker == self.marker_delivery_seq {
3297            None
3298        } else {
3299            Some(DetachedAttachRefusal::MarkerMismatch)
3300        }
3301    }
3302
3303    /// Authority supersession before commit preserves DCR and is stale.
3304    #[must_use]
3305    pub const fn authority_superseded(self) -> (Self, DetachedAttachRefusal) {
3306        (self, DetachedAttachRefusal::StaleAuthority)
3307    }
3308
3309    /// Applies only an unrelated participant event, preserving this edge while
3310    /// debt remains or clearing it with debt.
3311    ///
3312    /// # Errors
3313    ///
3314    /// Returns the unchanged owed state for a non-participant event or an event
3315    /// owned by this detached participant.
3316    pub const fn unrelated_event(
3317        self,
3318        debt: ClosureDebt,
3319        event: Event,
3320        resulting_debt: Option<ClosureDebt>,
3321    ) -> Result<ClosureState, ClosureState> {
3322        unrelated_detached_event(
3323            StoredEdge::DetachedCredentialRecovery(self),
3324            self.participant_id,
3325            debt,
3326            event,
3327            resulting_debt,
3328        )
3329    }
3330}
3331
3332mod sealed {
3333    pub trait Sealed {}
3334}
3335
3336/// Sealed intended-dead-end contract for DMR and `DCursor`.
3337pub trait LeaveOnlyEdge: sealed::Sealed + Sized + Copy {
3338    /// Returns the exact owner of this Leave-only edge.
3339    fn participant_id(self) -> ParticipantId;
3340
3341    /// Validates participant, exact target, actual charge, remaining K, and the
3342    /// available exit claim before creating private Leave authority.
3343    fn validate_leave_claim(
3344        &self,
3345        participant_id: ParticipantId,
3346        actual_charge: ResourceVector,
3347        remaining_k: ResourceVector,
3348        exit_claims: u64,
3349    ) -> Option<KClaimBackedDetachedLeave>;
3350
3351    /// Sole successful owner transition: exact-current K-backed detached Leave.
3352    ///
3353    /// # Errors
3354    ///
3355    /// Returns the unchanged owed state unless the Leave and private claim
3356    /// authority both name this exact Leave-only edge.
3357    fn leave(
3358        self,
3359        debt: ClosureDebt,
3360        event: Event,
3361        evidence: KClaimBackedDetachedLeave,
3362        successor: DebtCompletion,
3363    ) -> Result<ClosureState, ClosureState>;
3364
3365    /// Repeat exact fate is an event-consuming no-op.
3366    ///
3367    /// # Errors
3368    ///
3369    /// Returns the unchanged edge when the event is not fate for its exact owner
3370    /// and last dead binding epoch.
3371    fn repeat_fate(self, event: Event) -> Result<Self, Self>;
3372
3373    /// Supersession is stale and preserves the exact edge.
3374    fn authority_superseded(self) -> (Self, DetachedAttachRefusal) {
3375        (self, DetachedAttachRefusal::StaleAuthority)
3376    }
3377
3378    /// Normal/marker ack and ordinary admission have no binding authority.
3379    fn binding_required_refusal(self) -> DetachedAttachRefusal {
3380        let _ = self;
3381        DetachedAttachRefusal::NoBinding
3382    }
3383
3384    /// Applies an unrelated participant event, preserving this edge while debt
3385    /// remains or clearing it with debt.
3386    ///
3387    /// # Errors
3388    ///
3389    /// Returns the unchanged owed state for a non-participant event or an event
3390    /// owned by this detached participant.
3391    fn unrelated_event(
3392        self,
3393        debt: ClosureDebt,
3394        event: Event,
3395        resulting_debt: Option<ClosureDebt>,
3396    ) -> Result<ClosureState, ClosureState>;
3397}
3398
3399impl sealed::Sealed for DetachedMarkerRelease {}
3400impl sealed::Sealed for DetachedCursorRelease {}
3401
3402impl LeaveOnlyEdge for DetachedMarkerRelease {
3403    fn participant_id(self) -> ParticipantId {
3404        self.participant_id
3405    }
3406
3407    fn validate_leave_claim(
3408        &self,
3409        participant_id: ParticipantId,
3410        actual_charge: ResourceVector,
3411        remaining_k: ResourceVector,
3412        exit_claims: u64,
3413    ) -> Option<KClaimBackedDetachedLeave> {
3414        validate_detached_claim(
3415            self.participant_id,
3416            DetachedClaimTarget::MarkerRelease {
3417                marker_delivery_seq: self.marker_delivery_seq,
3418                binding_epoch: self.last_dead_binding_epoch,
3419            },
3420            participant_id,
3421            actual_charge,
3422            remaining_k,
3423            exit_claims,
3424        )
3425    }
3426
3427    fn leave(
3428        self,
3429        debt: ClosureDebt,
3430        event: Event,
3431        evidence: KClaimBackedDetachedLeave,
3432        successor: DebtCompletion,
3433    ) -> Result<ClosureState, ClosureState> {
3434        let original = owed(debt, StoredEdge::DetachedMarkerRelease(self));
3435        let EventKind::LeaveCommitted {
3436            participant_id,
3437            authority: LeaveAuthority::Detached,
3438            ..
3439        } = event.0
3440        else {
3441            return Err(original);
3442        };
3443        let target = DetachedClaimTarget::MarkerRelease {
3444            marker_delivery_seq: self.marker_delivery_seq,
3445            binding_epoch: self.last_dead_binding_epoch,
3446        };
3447        if participant_id != self.participant_id
3448            || evidence.participant_id != self.participant_id
3449            || evidence.target != target
3450        {
3451            return Err(original);
3452        }
3453        Ok(successor.into_state())
3454    }
3455
3456    fn repeat_fate(self, event: Event) -> Result<Self, Self> {
3457        let EventKind::BindingFateObserved {
3458            participant_id,
3459            binding_epoch,
3460            ..
3461        } = event.0
3462        else {
3463            return Err(self);
3464        };
3465        if participant_id == self.participant_id && binding_epoch == self.last_dead_binding_epoch {
3466            Ok(self)
3467        } else {
3468            Err(self)
3469        }
3470    }
3471
3472    fn unrelated_event(
3473        self,
3474        debt: ClosureDebt,
3475        event: Event,
3476        resulting_debt: Option<ClosureDebt>,
3477    ) -> Result<ClosureState, ClosureState> {
3478        unrelated_detached_event(
3479            StoredEdge::DetachedMarkerRelease(self),
3480            self.participant_id,
3481            debt,
3482            event,
3483            resulting_debt,
3484        )
3485    }
3486}
3487
3488impl DetachedMarkerRelease {
3489    /// Ordinary attach cannot cross this Leave-only edge.
3490    #[must_use]
3491    pub const fn ordinary_attach_refusal(self) -> DetachedAttachRefusal {
3492        let _ = self;
3493        DetachedAttachRefusal::RecoveryFence
3494    }
3495
3496    /// The exact undelivered marker selects `MarkerNotDelivered`; another marker
3497    /// selects `MarkerMismatch` without fabricating an expected delivery fact.
3498    #[must_use]
3499    pub const fn marker_attach_refusal(
3500        self,
3501        presented_marker: DeliverySeq,
3502    ) -> DetachedAttachRefusal {
3503        if presented_marker == self.marker_delivery_seq {
3504            DetachedAttachRefusal::MarkerNotDelivered
3505        } else {
3506            DetachedAttachRefusal::MarkerMismatch
3507        }
3508    }
3509}
3510
3511impl LeaveOnlyEdge for DetachedCursorRelease {
3512    fn participant_id(self) -> ParticipantId {
3513        self.participant_id
3514    }
3515
3516    fn validate_leave_claim(
3517        &self,
3518        participant_id: ParticipantId,
3519        actual_charge: ResourceVector,
3520        remaining_k: ResourceVector,
3521        exit_claims: u64,
3522    ) -> Option<KClaimBackedDetachedLeave> {
3523        validate_detached_claim(
3524            self.participant_id,
3525            DetachedClaimTarget::CursorRelease {
3526                binding_epoch: self.last_dead_binding_epoch,
3527            },
3528            participant_id,
3529            actual_charge,
3530            remaining_k,
3531            exit_claims,
3532        )
3533    }
3534
3535    fn leave(
3536        self,
3537        debt: ClosureDebt,
3538        event: Event,
3539        evidence: KClaimBackedDetachedLeave,
3540        successor: DebtCompletion,
3541    ) -> Result<ClosureState, ClosureState> {
3542        let original = owed(debt, StoredEdge::DetachedCursorRelease(self));
3543        let EventKind::LeaveCommitted {
3544            participant_id,
3545            authority: LeaveAuthority::Detached,
3546            ..
3547        } = event.0
3548        else {
3549            return Err(original);
3550        };
3551        let target = DetachedClaimTarget::CursorRelease {
3552            binding_epoch: self.last_dead_binding_epoch,
3553        };
3554        if participant_id != self.participant_id
3555            || evidence.participant_id != self.participant_id
3556            || evidence.target != target
3557        {
3558            return Err(original);
3559        }
3560        Ok(successor.into_state())
3561    }
3562
3563    fn repeat_fate(self, event: Event) -> Result<Self, Self> {
3564        let EventKind::BindingFateObserved {
3565            participant_id,
3566            binding_epoch,
3567            ..
3568        } = event.0
3569        else {
3570            return Err(self);
3571        };
3572        if participant_id == self.participant_id && binding_epoch == self.last_dead_binding_epoch {
3573            Ok(self)
3574        } else {
3575            Err(self)
3576        }
3577    }
3578
3579    fn unrelated_event(
3580        self,
3581        debt: ClosureDebt,
3582        event: Event,
3583        resulting_debt: Option<ClosureDebt>,
3584    ) -> Result<ClosureState, ClosureState> {
3585        unrelated_detached_event(
3586            StoredEdge::DetachedCursorRelease(self),
3587            self.participant_id,
3588            debt,
3589            event,
3590            resulting_debt,
3591        )
3592    }
3593}
3594
3595impl DetachedCursorRelease {
3596    /// Attach without a marker cannot cross this Leave-only edge.
3597    #[must_use]
3598    pub const fn ordinary_attach_refusal(self) -> DetachedAttachRefusal {
3599        let _ = self;
3600        DetachedAttachRefusal::RecoveryFence
3601    }
3602
3603    /// Presenting any marker mismatches a cursor-only edge.
3604    #[must_use]
3605    pub const fn marker_attach_refusal(self) -> DetachedAttachRefusal {
3606        let _ = self;
3607        DetachedAttachRefusal::MarkerMismatch
3608    }
3609}
3610
3611const fn owed(debt: ClosureDebt, edge: StoredEdge) -> ClosureState {
3612    ClosureState::Owed { debt, edge }
3613}
3614
3615const fn preserve_or_clear(resulting_debt: Option<ClosureDebt>, edge: StoredEdge) -> ClosureState {
3616    match resulting_debt {
3617        Some(debt) => owed(debt, edge),
3618        None => ClosureState::Clear,
3619    }
3620}
3621
3622const fn projection_completion_boundary(
3623    edge: ObserverProjection,
3624    event: Event,
3625) -> Option<DeliverySeq> {
3626    let EventKind::ProjectionCompleted { through_seq } = event.0 else {
3627        return None;
3628    };
3629    if through_seq == edge.through_seq {
3630        Some(through_seq)
3631    } else {
3632        None
3633    }
3634}
3635
3636const fn physical_completion_floor(edge: PhysicalCompaction, event: Event) -> Option<DeliverySeq> {
3637    match event.0 {
3638        EventKind::CompactionCompleted {
3639            from_floor,
3640            through_seq,
3641            resulting_floor,
3642        } if from_floor == edge.from_floor
3643            && through_seq == edge.through_seq
3644            && resulting_floor > edge.through_seq =>
3645        {
3646            Some(resulting_floor)
3647        }
3648        _ => None,
3649    }
3650}
3651
3652const fn progress_event_floor(event: Event) -> Option<DeliverySeq> {
3653    match event.0 {
3654        EventKind::CursorProgressed {
3655            resulting_floor, ..
3656        }
3657        | EventKind::BindingFateObserved {
3658            resulting_floor, ..
3659        }
3660        | EventKind::LeaveCommitted {
3661            resulting_floor, ..
3662        } => Some(resulting_floor),
3663        _ => None,
3664    }
3665}
3666
3667const fn cursor_event_boundary(event: Event) -> DeliverySeq {
3668    match event.0 {
3669        EventKind::CursorProgressed {
3670            progress: CursorProgressEvent::Normal { through_seq, .. },
3671            ..
3672        } => through_seq,
3673        _ => 0,
3674    }
3675}
3676
3677fn greater_ack_matches(edge: ParticipantCursorProgress, event: Event) -> bool {
3678    let EventKind::CursorProgressed {
3679        participant_id,
3680        binding_epoch,
3681        progress:
3682            CursorProgressEvent::Normal {
3683                previous_cursor,
3684                through_seq,
3685            },
3686        ..
3687    } = event.0
3688    else {
3689        return false;
3690    };
3691    participant_id == edge.participant_id()
3692        && binding_epoch == edge.binding_epoch()
3693        && previous_cursor < edge.through_seq()
3694        && through_seq > edge.through_seq()
3695}
3696
3697const fn strict_edge_matches_boundary(edge: StoredEdge, boundary: DeliverySeq) -> bool {
3698    match edge {
3699        StoredEdge::ObserverProjection(value) => value.through_seq == boundary,
3700        StoredEdge::PhysicalCompaction(value) => value.through_seq == boundary,
3701        StoredEdge::MarkerDelivery(value) => value.marker_delivery_seq == boundary,
3702        StoredEdge::ParticipantCursorProgress(value) => value.through_seq() == boundary,
3703        StoredEdge::DetachedCredentialRecovery(_) => false,
3704        StoredEdge::DetachedMarkerRelease(value) => value.marker_delivery_seq == boundary,
3705        // DCursor has no sequence field in the frozen tag. The explicit boundary
3706        // supplied to the predecessor-bound successor is its typed causal-order
3707        // witness under LP-EXTRACTION-GOAL.md Fix 2.
3708        StoredEdge::DetachedCursorRelease(_) => true,
3709    }
3710}
3711
3712const fn charged_churn_fits(used: u64, delta: u64, limit: u64) -> bool {
3713    delta > 0 && widen_u64(used) + widen_u64(delta) <= widen_u64(limit)
3714}
3715
3716#[allow(clippy::cast_lossless)]
3717const fn widen_u64(value: u64) -> u128 {
3718    value as u128
3719}
3720
3721const fn is_next_generation(old: BindingEpoch, new: BindingEpoch) -> bool {
3722    match old.capability_generation.get().checked_add(1) {
3723        Some(expected) => new.capability_generation.get() == expected,
3724        None => false,
3725    }
3726}
3727
3728const fn validate_detached_claim(
3729    owner: ParticipantId,
3730    target: DetachedClaimTarget,
3731    participant_id: ParticipantId,
3732    actual_charge: ResourceVector,
3733    remaining_k: ResourceVector,
3734    exit_claims: u64,
3735) -> Option<KClaimBackedDetachedLeave> {
3736    if participant_id != owner
3737        || actual_charge.entries == 0
3738        || actual_charge.bytes == 0
3739        || actual_charge.entries > remaining_k.entries
3740        || actual_charge.bytes > remaining_k.bytes
3741        || exit_claims == 0
3742    {
3743        return None;
3744    }
3745    Some(KClaimBackedDetachedLeave {
3746        participant_id,
3747        target,
3748        actual_charge,
3749    })
3750}
3751
3752const fn event_participant(event: Event) -> Option<ParticipantId> {
3753    match event.0 {
3754        EventKind::MarkerDelivered { participant_id, .. }
3755        | EventKind::CursorProgressed { participant_id, .. }
3756        | EventKind::BindingFateObserved { participant_id, .. }
3757        | EventKind::LeaveCommitted { participant_id, .. }
3758        | EventKind::FencedRecoveryCommitted { participant_id, .. } => Some(participant_id),
3759        EventKind::ProjectionCompleted { .. }
3760        | EventKind::CompactionCompleted { .. }
3761        | EventKind::MarkerAppended { .. } => None,
3762    }
3763}
3764
3765const fn unrelated_detached_event(
3766    edge: StoredEdge,
3767    owner: ParticipantId,
3768    debt: ClosureDebt,
3769    event: Event,
3770    resulting_debt: Option<ClosureDebt>,
3771) -> Result<ClosureState, ClosureState> {
3772    let original = owed(debt, edge);
3773    let Some(participant_id) = event_participant(event) else {
3774        return Err(original);
3775    };
3776    if participant_id == owner {
3777        return Err(original);
3778    }
3779    Ok(preserve_or_clear(resulting_debt, edge))
3780}