Skip to main content

liminal_protocol/lifecycle/
edge.rs

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