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