Skip to main content

liminal_protocol/lifecycle/
claim_frontier.rs

1//! Exact reserved sequence and transaction-order claim frontiers.
2//!
3//! `docs/design/LP-EXTRACTION-GOAL.md` Fix 2 deliberately excludes the frozen
4//! occurrence-array layout. This module keeps participant-keyed facts and
5//! compact product range descriptors instead: exact direct/candidate/recovery
6//! positions use O(I) bounded storage, while product handles are derived lazily
7//! from active-identity ranks and are never expanded into an `I x I` array.
8
9use alloc::{boxed::Box, vec::Vec};
10
11mod binding_fate_transition;
12pub(in crate::lifecycle) use binding_fate_transition::BindingFateFrontierPlan;
13
14use crate::{
15    algebra::ResourceVector,
16    outcome::{CandidatePhase, ClaimCounter, ParticipantStateCorruptReason},
17    wire::{
18        BindingEpoch, ClosureCheckedEnvelope, ConnectionIncarnation, ConversationId, DeliverySeq,
19        Generation, ParticipantId, TransactionOrder,
20    },
21};
22
23use super::{
24    AttachedLifecycleRecord, BindingOrigin, BindingState, ClosureAccounting, ClosureState,
25    CommittedBindingTerminal, Event, InitialEnrollmentClosureProjection,
26    InitialEnrollmentOperationCommit, LeaveCommitError, LiveMember, MarkerDelivery,
27    ObserverCheckedOperation, ObserverFloorDecision, ObserverProjection, OrderClaims, OrderHigh,
28    OrderLedger, ParticipantCursorProgress, PendingFinalization, PreparedLeaveAuthority,
29    RecoveryQuartetStatus, RecoverySequenceReserve, RemainingClosureDecision, SequenceClaims,
30    SequenceLedger, StoredEdge, check_observer_floor, check_remaining_closure,
31    operations::ordinary_record_projection::{
32        OrdinaryFixedPointPlan, OrdinaryProjectionError, OrdinaryProjectionFacts,
33        OrdinaryProjectionKernelDecision, OrdinaryRecordDrainFirst,
34        OrdinaryRecordProjectionDecision, OrdinaryRecordProjectionFailure,
35        OrdinaryRecordProjectionInput, ProjectedOrdinaryRecord, project_ordinary_fixed_point,
36    },
37};
38
39/// Counter whose frontier failed restoration.
40#[derive(Clone, Copy, Debug, PartialEq, Eq)]
41pub enum ClaimFrontierCounter {
42    /// Conversation delivery sequence.
43    DeliverySequence,
44    /// Conversation transaction-order major.
45    TransactionOrder,
46}
47
48/// Structural reason for a claim-frontier failure.
49#[derive(Clone, Copy, Debug, PartialEq, Eq)]
50pub enum ClaimFrontierInvalidReason {
51    /// Numeric positions contain a gap, duplicate, collision, or invalid bound.
52    NumericPosition,
53    /// One immutable candidate key is duplicated or malformed.
54    CandidateKey,
55    /// One logical owner is unknown, duplicated, missing, or in the wrong class.
56    LogicalOwner,
57    /// A product range is misordered or has the wrong active-rank extent.
58    ProductRange,
59    /// A recovery block is torn, non-adjacent, or inconsistent across counters.
60    RecoveryBlock,
61    /// Exact frontier owner counts disagree with the aggregate ledger.
62    AggregateLedger,
63}
64
65/// Deterministic claim-frontier restoration failure.
66#[derive(Clone, Copy, Debug, PartialEq, Eq)]
67pub struct ClaimFrontierError {
68    /// Counter whose union failed.
69    pub counter: ClaimFrontierCounter,
70    /// Lowest checked-u128 index selected by the frozen scan.
71    pub first_bad_position: u128,
72    /// Structural class of the first failure.
73    pub reason: ClaimFrontierInvalidReason,
74}
75
76/// Exact live participant binding state used by marker planning.
77#[derive(Clone, Copy, Debug, PartialEq, Eq)]
78pub enum FrontierBinding {
79    /// Participant is bound to this exact current epoch.
80    Bound(BindingEpoch),
81    /// Participant is detached after this exact last authoritative epoch.
82    Detached(BindingEpoch),
83}
84
85/// Participant-indexed membership fact used by claim validation and planning.
86#[derive(Clone, Copy, Debug, PartialEq, Eq)]
87pub struct FrontierParticipant {
88    participant_index: ParticipantId,
89    cursor: DeliverySeq,
90    binding: FrontierBinding,
91}
92
93impl FrontierParticipant {
94    /// Creates one exact live-membership frontier fact.
95    #[must_use]
96    pub const fn new(
97        participant_index: ParticipantId,
98        cursor: DeliverySeq,
99        binding: FrontierBinding,
100    ) -> Self {
101        Self {
102            participant_index,
103            cursor,
104            binding,
105        }
106    }
107
108    /// Returns the permanent participant index.
109    #[must_use]
110    pub const fn participant_index(self) -> ParticipantId {
111        self.participant_index
112    }
113
114    /// Returns the durable cumulative cursor.
115    #[must_use]
116    pub const fn cursor(self) -> DeliverySeq {
117        self.cursor
118    }
119
120    /// Returns the exact current or last authoritative binding state.
121    #[must_use]
122    pub const fn binding(self) -> FrontierBinding {
123        self.binding
124    }
125}
126
127/// Sorted unique permanent indexes of current live members.
128#[derive(Clone, Debug, PartialEq, Eq)]
129pub struct ActiveIdentityRanks {
130    participants: Vec<FrontierParticipant>,
131}
132
133impl ActiveIdentityRanks {
134    /// Validates the signed identity-slot bound, ascending unique indexes, and
135    /// cursors at or below `H`.
136    ///
137    /// # Errors
138    ///
139    /// Returns a delivery-sequence [`ClaimFrontierError`] at the first malformed
140    /// rank. The rank itself is the deterministic bad-position index.
141    pub fn try_new(
142        participants: Vec<FrontierParticipant>,
143        high_watermark: DeliverySeq,
144        identity_slot_limit: u64,
145    ) -> Result<Self, ClaimFrontierError> {
146        if usize_to_u128(participants.len()) > u128::from(identity_slot_limit) {
147            return Err(sequence_error(
148                u128::from(identity_slot_limit),
149                ClaimFrontierInvalidReason::LogicalOwner,
150            ));
151        }
152        let mut previous = None;
153        for (rank, participant) in participants.iter().enumerate() {
154            if previous.is_some_and(|value| value >= participant.participant_index)
155                || participant.participant_index >= identity_slot_limit
156                || participant.cursor > high_watermark
157            {
158                return Err(sequence_error(
159                    rank_index(rank),
160                    ClaimFrontierInvalidReason::LogicalOwner,
161                ));
162            }
163            previous = Some(participant.participant_index);
164        }
165        Ok(Self { participants })
166    }
167
168    /// Borrows the ascending live participant facts.
169    #[must_use]
170    pub fn participants(&self) -> &[FrontierParticipant] {
171        &self.participants
172    }
173
174    /// Returns the number of active identity ranks.
175    #[must_use]
176    pub fn len(&self) -> u64 {
177        usize_to_u64(self.participants.len())
178    }
179
180    /// Returns whether no live identity rank exists.
181    #[must_use]
182    pub fn is_empty(&self) -> bool {
183        self.participants.is_empty()
184    }
185
186    fn contains(&self, participant_index: ParticipantId) -> bool {
187        self.participants
188            .binary_search_by_key(&participant_index, |participant| {
189                participant.participant_index
190            })
191            .is_ok()
192    }
193}
194
195/// Exact active binding-terminal claim authority.
196#[derive(Clone, Copy, Debug, PartialEq, Eq)]
197pub struct BindingTerminalOwner {
198    /// Permanent participant index.
199    pub participant_index: ParticipantId,
200    /// Binding epoch whose future fate owns this claim.
201    pub binding_epoch: BindingEpoch,
202}
203
204/// Direct sequence-claim owner stored in an identity slot.
205#[derive(Clone, Copy, Debug, PartialEq, Eq)]
206pub enum SequenceDirectOwner {
207    /// Eventual tokenized `Left` record.
208    MembershipExit {
209        /// Permanent participant index.
210        participant_index: ParticipantId,
211    },
212    /// Future terminal for one exact active binding.
213    BindingTerminal(BindingTerminalOwner),
214}
215
216/// One exact movable direct sequence claim.
217#[derive(Clone, Copy, Debug, PartialEq, Eq)]
218pub struct MovableSequenceClaim {
219    /// Owned delivery-sequence position.
220    pub delivery_seq: DeliverySeq,
221    /// Exact identity-slot owner.
222    pub owner: SequenceDirectOwner,
223}
224
225/// Terminal source retained by immutable marker provenance.
226#[derive(Clone, Copy, Debug, PartialEq, Eq)]
227pub enum TerminalProductSource {
228    /// Ordinary active-binding terminal claim.
229    Binding(BindingTerminalOwner),
230    /// Edge-owned replacement terminal for a recovered binding.
231    RecoveryReplacement {
232        /// Permanent participant index.
233        participant_index: ParticipantId,
234        /// Exact prospective replacement epoch.
235        binding_epoch: BindingEpoch,
236    },
237}
238
239impl TerminalProductSource {
240    /// Names the prospective replacement terminal produced by recovery.
241    #[must_use]
242    pub const fn recovery_replacement(
243        participant_index: ParticipantId,
244        binding_epoch: BindingEpoch,
245    ) -> Self {
246        Self::RecoveryReplacement {
247            participant_index,
248            binding_epoch,
249        }
250    }
251}
252
253/// Immutable origin of one planned or appended marker value.
254#[derive(Clone, Copy, Debug, PartialEq, Eq)]
255pub enum MarkerProvenance {
256    /// Marker was planned directly by an optional floor transition.
257    NonProductM,
258    /// Marker was conditionally reserved by a terminal product.
259    TerminalProduct {
260        /// Exact causal terminal source.
261        terminal: TerminalProductSource,
262        /// Permanent affected participant index.
263        affected_participant: ParticipantId,
264    },
265    /// Marker was conditionally reserved by a membership-exit product.
266    ExitProduct {
267        /// Exiting participant whose E claim caused the product.
268        exit_participant: ParticipantId,
269        /// Remaining participant the possible marker protects.
270        remaining_participant: ParticipantId,
271    },
272}
273
274impl MarkerProvenance {
275    /// Names one conditional terminal-product marker provenance.
276    #[must_use]
277    pub const fn terminal_product(
278        terminal: TerminalProductSource,
279        affected_participant: ParticipantId,
280    ) -> Self {
281        Self::TerminalProduct {
282            terminal,
283            affected_participant,
284        }
285    }
286
287    /// Names one conditional membership-exit-product marker provenance.
288    #[must_use]
289    pub const fn exit_product(
290        exit_participant: ParticipantId,
291        remaining_participant: ParticipantId,
292    ) -> Self {
293        Self::ExitProduct {
294            exit_participant,
295            remaining_participant,
296        }
297    }
298}
299
300/// Exact typed body class for one retained causal record.
301#[derive(Clone, Copy, Debug, PartialEq, Eq)]
302pub enum RetainedCausalRecordKind {
303    /// Retained binding terminal with exact epoch ownership.
304    BindingTerminal(BindingTerminalOwner),
305    /// Retained tokenized membership exit.
306    MembershipExit {
307        /// Permanent exiting participant.
308        participant_index: ParticipantId,
309    },
310    /// Retained attached lifecycle record.
311    AttachLifecycle {
312        /// Permanent affected participant.
313        participant_index: ParticipantId,
314        /// Exact attached binding epoch.
315        binding_epoch: BindingEpoch,
316    },
317    /// Retained ordinary application record.
318    OrdinaryRecord {
319        /// Permanent verified sender.
320        participant_index: ParticipantId,
321    },
322    /// Retained compaction marker with immutable provenance.
323    CompactionMarker {
324        /// Permanent marker owner.
325        participant_index: ParticipantId,
326        /// Immutable marker origin.
327        provenance: MarkerProvenance,
328    },
329}
330
331/// One typed retained record fact used for candidate-key and provenance checks.
332#[derive(Clone, Copy, Debug, PartialEq, Eq)]
333pub struct RetainedCausalRecord {
334    /// Durable appended sequence.
335    pub delivery_seq: DeliverySeq,
336    /// Complete immutable candidate/direct-record key.
337    pub admission_order: super::AdmissionOrder,
338    /// Typed retained record body facts.
339    pub kind: RetainedCausalRecordKind,
340}
341
342#[derive(Clone, Copy, Debug, PartialEq, Eq)]
343enum HistoricalCausalKind {
344    BindingTerminal(BindingTerminalOwner),
345    MembershipExit(ParticipantId),
346}
347
348/// Raw durable facts for one compacted causal lifecycle record.
349///
350/// This is storage input, not executable provenance. Only complete
351/// [`super::ParticipantConversationRestore`] validation can pair these facts
352/// with owned membership or tombstone history and turn them into crate-private
353/// marker provenance. In particular, neither a raw binding-terminal position
354/// nor a raw `Left` major can be converted into sealed authority directly.
355///
356/// ```compile_fail
357/// use liminal_protocol::lifecycle::HistoricalCausalAuthority;
358/// ```
359#[derive(Clone, Copy, Debug, PartialEq, Eq)]
360pub enum HistoricalCausalFactRestore {
361    /// A compacted binding terminal retained by participant history.
362    BindingTerminal {
363        /// Owning conversation.
364        conversation_id: ConversationId,
365        /// Permanent participant index.
366        participant_index: ParticipantId,
367        /// Exact ended binding epoch.
368        binding_epoch: BindingEpoch,
369        /// Immutable binding-terminal tuple.
370        admission_order: super::AdmissionOrder,
371    },
372    /// A compacted `Left` record retained by the permanent tombstone.
373    MembershipExit {
374        /// Owning conversation.
375        conversation_id: ConversationId,
376        /// Permanent retired participant index.
377        participant_index: ParticipantId,
378        /// Immutable membership-exit tuple.
379        admission_order: super::AdmissionOrder,
380    },
381}
382
383/// Raw immutable fact that one retained marker was durably delivered to an
384/// exact historical binding epoch.
385///
386/// This fact is distinct from current marker-anchor ownership and current
387/// membership. It may therefore outlive fenced attach and multiple later
388/// binding cycles, and several retained markers may name the same participant.
389/// Only joint conversation restoration can turn it into executable recovery
390/// provenance.
391#[derive(Clone, Copy, Debug, PartialEq, Eq)]
392pub struct HistoricalMarkerDeliveryFactRestore {
393    /// Owning conversation.
394    pub conversation_id: ConversationId,
395    /// Permanent marker owner.
396    pub participant_index: ParticipantId,
397    /// Exact retained marker record.
398    pub marker_delivery_seq: DeliverySeq,
399    /// Binding epoch to which that marker was durably delivered.
400    pub delivered_binding_epoch: BindingEpoch,
401}
402
403/// Crate-sealed compacted-history authority derived during total restoration.
404#[derive(Clone, Copy, Debug, PartialEq, Eq)]
405pub(super) struct HistoricalCausalAuthority {
406    conversation_id: ConversationId,
407    admission_order: super::AdmissionOrder,
408    kind: HistoricalCausalKind,
409}
410
411#[derive(Clone, Copy, Debug, PartialEq, Eq)]
412pub(super) struct HistoricalMarkerDeliveryAuthority {
413    participant_index: ParticipantId,
414    marker_delivery_seq: DeliverySeq,
415    delivered_binding_epoch: BindingEpoch,
416}
417
418impl HistoricalMarkerDeliveryAuthority {
419    /// Returns the permanent owner of the delivered marker.
420    pub(super) const fn participant_index(self) -> ParticipantId {
421        self.participant_index
422    }
423
424    /// Returns the exact retained marker record.
425    pub(super) const fn marker_delivery_seq(self) -> DeliverySeq {
426        self.marker_delivery_seq
427    }
428
429    /// Returns the binding epoch that durably received the marker.
430    pub(super) const fn delivered_binding_epoch(self) -> BindingEpoch {
431        self.delivered_binding_epoch
432    }
433}
434
435impl HistoricalCausalAuthority {
436    pub(super) const fn from_committed_terminal(terminal: super::CommittedBindingTerminal) -> Self {
437        Self {
438            conversation_id: terminal.conversation_id(),
439            admission_order: terminal.admission_order(),
440            kind: HistoricalCausalKind::BindingTerminal(BindingTerminalOwner {
441                participant_index: terminal.participant_id(),
442                binding_epoch: terminal.binding_epoch(),
443            }),
444        }
445    }
446
447    pub(super) const fn from_retired<EF, V, LF>(
448        retired: &super::RetiredIdentity<EF, V, LF>,
449    ) -> Self {
450        Self {
451            conversation_id: retired.conversation_id(),
452            admission_order: retired.left_admission_order(),
453            kind: HistoricalCausalKind::MembershipExit(retired.participant_id()),
454        }
455    }
456
457    const fn from_restore(fact: HistoricalCausalFactRestore) -> Self {
458        match fact {
459            HistoricalCausalFactRestore::BindingTerminal {
460                conversation_id,
461                participant_index,
462                binding_epoch,
463                admission_order,
464            } => Self {
465                conversation_id,
466                admission_order,
467                kind: HistoricalCausalKind::BindingTerminal(BindingTerminalOwner {
468                    participant_index,
469                    binding_epoch,
470                }),
471            },
472            HistoricalCausalFactRestore::MembershipExit {
473                conversation_id,
474                participant_index,
475                admission_order,
476            } => Self {
477                conversation_id,
478                admission_order,
479                kind: HistoricalCausalKind::MembershipExit(participant_index),
480            },
481        }
482    }
483
484    /// Describes this sealed authority back into the raw durable fact it was
485    /// validated from; the exact inverse of [`Self::from_restore`].
486    const fn describe(self) -> HistoricalCausalFactRestore {
487        match self.kind {
488            HistoricalCausalKind::BindingTerminal(owner) => {
489                HistoricalCausalFactRestore::BindingTerminal {
490                    conversation_id: self.conversation_id,
491                    participant_index: owner.participant_index,
492                    binding_epoch: owner.binding_epoch,
493                    admission_order: self.admission_order,
494                }
495            }
496            HistoricalCausalKind::MembershipExit(participant_index) => {
497                HistoricalCausalFactRestore::MembershipExit {
498                    conversation_id: self.conversation_id,
499                    participant_index,
500                    admission_order: self.admission_order,
501                }
502            }
503        }
504    }
505}
506
507#[derive(Debug)]
508pub(super) struct ValidatedConversationHistory {
509    causal_authorities: Vec<HistoricalCausalAuthority>,
510    binding_origins: Vec<BindingOrigin>,
511    total: bool,
512}
513
514impl ValidatedConversationHistory {
515    pub(super) const fn empty() -> Self {
516        Self {
517            causal_authorities: Vec::new(),
518            binding_origins: Vec::new(),
519            total: false,
520        }
521    }
522
523    pub(super) const fn new(
524        causal_authorities: Vec<HistoricalCausalAuthority>,
525        binding_origins: Vec<BindingOrigin>,
526    ) -> Self {
527        Self {
528            causal_authorities,
529            binding_origins,
530            total: true,
531        }
532    }
533
534    pub(super) fn ordinary_origin(
535        &self,
536        conversation_id: ConversationId,
537        participant_index: ParticipantId,
538        binding_epoch: BindingEpoch,
539    ) -> Option<&BindingOrigin> {
540        self.binding_origins.iter().find(|origin| {
541            origin.conversation_id() == conversation_id
542                && origin.participant_id() == participant_index
543                && origin.binding_epoch() == binding_epoch
544                && origin.is_unfenced()
545        })
546    }
547}
548
549/// Product class that may own a value before its causal transaction fires.
550#[derive(Clone, Copy, Debug, PartialEq, Eq)]
551pub enum SequenceProductClass {
552    /// `L x T`.
553    LiveTimesTerminal,
554    /// `L x RT`.
555    LiveTimesReplacementTerminal,
556    /// `L_other x E`.
557    OtherLiveTimesExit,
558}
559
560/// Current logical owner of one marker-provenance value.
561#[derive(Clone, Copy, Debug, PartialEq, Eq)]
562pub enum MarkerSequenceOwner {
563    /// Required-but-unwritten marker claim `M`.
564    Marker,
565    /// An unfired conditional product range.
566    ConditionalProduct(SequenceProductClass),
567}
568
569/// Complete immutable marker candidate authority.
570#[derive(Clone, Copy, Debug, PartialEq, Eq)]
571pub struct MarkerCandidateAuthority {
572    /// Exact assigned marker sequence.
573    pub delivery_seq: DeliverySeq,
574    /// Exact phase-4 causal candidate key.
575    pub admission_order: super::AdmissionOrder,
576    /// Current or last authoritative delivery target.
577    pub target_binding: FrontierBinding,
578    /// Immutable marker provenance.
579    pub provenance: MarkerProvenance,
580    /// Last sequence acknowledged before this participant was overtaken.
581    pub abandoned_after: DeliverySeq,
582    /// Last pre-marker sequence abandoned by this marker decision.
583    pub abandoned_through: DeliverySeq,
584    /// Physical retained floor selected by this marker decision.
585    pub physical_floor_at_decision: DeliverySeq,
586    /// Current sequence owner; an immutable candidate must own `M`.
587    pub current_owner: MarkerSequenceOwner,
588}
589
590/// Immutable assigned candidate above the current sequence high watermark.
591#[derive(Clone, Copy, Debug, PartialEq, Eq)]
592pub enum ImmutableSequenceCandidate {
593    /// Pending exact binding terminal.
594    BindingTerminal {
595        /// Assigned delivery sequence.
596        delivery_seq: DeliverySeq,
597        /// Exact causal candidate key.
598        admission_order: super::AdmissionOrder,
599        /// Binding-terminal authority consumed into the candidate.
600        owner: BindingTerminalOwner,
601    },
602    /// Planned phase-4 compaction marker.
603    Marker(MarkerCandidateAuthority),
604}
605
606impl ImmutableSequenceCandidate {
607    /// Returns the immutable assigned delivery sequence.
608    #[must_use]
609    pub const fn delivery_seq(self) -> DeliverySeq {
610        match self {
611            Self::BindingTerminal { delivery_seq, .. } => delivery_seq,
612            Self::Marker(candidate) => candidate.delivery_seq,
613        }
614    }
615
616    /// Returns the complete causal candidate key.
617    #[must_use]
618    pub const fn admission_order(self) -> super::AdmissionOrder {
619        match self {
620            Self::BindingTerminal {
621                admission_order, ..
622            } => admission_order,
623            Self::Marker(candidate) => candidate.admission_order,
624        }
625    }
626}
627
628/// Persisted `L x T` product-range descriptor supplied during restoration.
629#[derive(Clone, Copy, Debug, PartialEq, Eq)]
630pub struct TerminalProductRangeRestore {
631    /// First owned sequence value.
632    pub start: DeliverySeq,
633    /// Persisted range length, which must equal the active-rank count.
634    pub length: u64,
635    /// Exact terminal claim that owns the product row.
636    pub terminal: BindingTerminalOwner,
637}
638
639/// Persisted `L x RT` product-range descriptor supplied during restoration.
640#[derive(Clone, Copy, Debug, PartialEq, Eq)]
641pub struct ReplacementTerminalProductRangeRestore {
642    /// First owned sequence value.
643    pub start: DeliverySeq,
644    /// Persisted range length, which must equal the active-rank count.
645    pub length: u64,
646}
647
648/// Persisted `L_other x E` product-range descriptor supplied during restoration.
649#[derive(Clone, Copy, Debug, PartialEq, Eq)]
650pub struct ExitProductRangeRestore {
651    /// First owned sequence value.
652    pub start: DeliverySeq,
653    /// Persisted range length, which must equal `L - 1`.
654    pub length: u64,
655    /// Permanent participant whose exit claim owns the product row.
656    pub exit_participant: ParticipantId,
657}
658
659/// Compact persisted product descriptors supplied during restoration.
660#[derive(Clone, Debug, Default, PartialEq, Eq)]
661pub struct SequenceProductRangesRestore {
662    /// One full active-rank range for every current terminal claim.
663    pub live_times_terminal: Vec<TerminalProductRangeRestore>,
664    /// The sole replacement-terminal range when the DCR pair exists.
665    pub live_times_replacement_terminal: Option<ReplacementTerminalProductRangeRestore>,
666    /// One all-other-active-ranks range for every current exit claim.
667    pub other_live_times_exit: Vec<ExitProductRangeRestore>,
668}
669
670/// Validated compact `L x T` product range.
671#[derive(Clone, Copy, Debug, PartialEq, Eq)]
672pub struct TerminalProductRange {
673    start: DeliverySeq,
674    length: u64,
675    terminal: BindingTerminalOwner,
676}
677
678impl TerminalProductRange {
679    /// Returns the first owned sequence value.
680    #[must_use]
681    pub const fn start(self) -> DeliverySeq {
682        self.start
683    }
684
685    /// Returns the terminal claim that owns this product row.
686    #[must_use]
687    pub const fn terminal(self) -> BindingTerminalOwner {
688        self.terminal
689    }
690
691    /// Returns the validated active-rank extent.
692    #[must_use]
693    pub const fn length(self) -> u64 {
694        self.length
695    }
696
697    /// Derives the owned value for one active-rank index without expanding the row.
698    #[must_use]
699    pub fn value_at_rank(self, active_rank: usize) -> Option<DeliverySeq> {
700        if usize_to_u128(active_rank) >= u128::from(self.length) {
701            return None;
702        }
703        checked_rank_value(self.start, active_rank)
704    }
705}
706
707/// Validated compact `L x RT` product range.
708#[derive(Clone, Copy, Debug, PartialEq, Eq)]
709pub struct ReplacementTerminalProductRange {
710    start: DeliverySeq,
711    length: u64,
712    participant_index: ParticipantId,
713    marker_delivery_seq: DeliverySeq,
714    prior_binding_epoch: BindingEpoch,
715}
716
717impl ReplacementTerminalProductRange {
718    /// Returns the first owned sequence value.
719    #[must_use]
720    pub const fn start(self) -> DeliverySeq {
721        self.start
722    }
723
724    /// Returns the validated active-rank extent.
725    #[must_use]
726    pub const fn length(self) -> u64 {
727        self.length
728    }
729
730    /// Returns the participant whose prospective replacement owns `RT`.
731    #[must_use]
732    pub const fn participant_index(self) -> ParticipantId {
733        self.participant_index
734    }
735
736    /// Returns the delivered marker whose recovery owns `RT`.
737    #[must_use]
738    pub const fn marker_delivery_seq(self) -> DeliverySeq {
739        self.marker_delivery_seq
740    }
741
742    /// Returns the prior authoritative epoch fenced by recovery.
743    #[must_use]
744    pub const fn prior_binding_epoch(self) -> BindingEpoch {
745        self.prior_binding_epoch
746    }
747
748    /// Derives the owned value for one active-rank index without expanding the row.
749    #[must_use]
750    pub fn value_at_rank(self, active_rank: usize) -> Option<DeliverySeq> {
751        if usize_to_u128(active_rank) >= u128::from(self.length) {
752            return None;
753        }
754        checked_rank_value(self.start, active_rank)
755    }
756}
757
758/// Validated compact `L_other x E` product range.
759#[derive(Clone, Copy, Debug, PartialEq, Eq)]
760pub struct ExitProductRange {
761    start: DeliverySeq,
762    length: u64,
763    exit_participant: ParticipantId,
764}
765
766impl ExitProductRange {
767    /// Returns the first owned sequence value.
768    #[must_use]
769    pub const fn start(self) -> DeliverySeq {
770        self.start
771    }
772
773    /// Returns the exit-claim owner.
774    #[must_use]
775    pub const fn exit_participant(self) -> ParticipantId {
776        self.exit_participant
777    }
778
779    /// Returns the validated all-other-rank extent.
780    #[must_use]
781    pub const fn length(self) -> u64 {
782        self.length
783    }
784
785    /// Derives the value for an affected active rank.
786    ///
787    /// The exiting identity is skipped, so no `I x I` expansion is formed.
788    #[must_use]
789    pub fn value_for_affected_rank(
790        self,
791        active_identities: &ActiveIdentityRanks,
792        affected_rank: usize,
793    ) -> Option<DeliverySeq> {
794        let affected = active_identities.participants().get(affected_rank)?;
795        if usize_to_u128(affected_rank) >= usize_to_u128(active_identities.participants.len()) {
796            return None;
797        }
798        if affected.participant_index == self.exit_participant {
799            return None;
800        }
801        let exit_rank = active_identities
802            .participants()
803            .binary_search_by_key(&self.exit_participant, |participant| {
804                participant.participant_index
805            })
806            .ok()?;
807        let compact_rank = if affected_rank < exit_rank {
808            affected_rank
809        } else {
810            affected_rank.checked_sub(1)?
811        };
812        if usize_to_u128(compact_rank) >= u128::from(self.length) {
813            return None;
814        }
815        checked_rank_value(self.start, compact_rank)
816    }
817}
818
819/// Validated O(I) product descriptors for the sequence frontier.
820#[derive(Clone, Debug, Default, PartialEq, Eq)]
821pub struct SequenceProductRanges {
822    live_times_terminal: Vec<TerminalProductRange>,
823    live_times_replacement_terminal: Option<ReplacementTerminalProductRange>,
824    other_live_times_exit: Vec<ExitProductRange>,
825}
826
827impl SequenceProductRanges {
828    /// Borrows the `L x T` rows in terminal-owner order.
829    #[must_use]
830    pub fn live_times_terminal(&self) -> &[TerminalProductRange] {
831        &self.live_times_terminal
832    }
833
834    /// Returns the optional `L x RT` row.
835    #[must_use]
836    pub const fn live_times_replacement_terminal(&self) -> Option<ReplacementTerminalProductRange> {
837        self.live_times_replacement_terminal
838    }
839
840    /// Borrows the `L_other x E` rows in exit-owner order.
841    #[must_use]
842    pub fn other_live_times_exit(&self) -> &[ExitProductRange] {
843        &self.other_live_times_exit
844    }
845}
846
847/// Optional leading `T` member of a persisted DCR sequence interval.
848#[derive(Clone, Copy, Debug, PartialEq, Eq)]
849pub struct RecoverySequenceTerminalRestore {
850    /// Owned sequence value immediately before `RS`.
851    pub delivery_seq: DeliverySeq,
852    /// Exact active binding-terminal owner.
853    pub owner: BindingTerminalOwner,
854}
855
856/// Public persisted shape of the sole DCR sequence interval.
857///
858/// `terminal` is present before its `T` claim materializes. Restoration accepts
859/// individual persisted positions so a torn interval can be diagnosed, then
860/// stores the validated interval as one indivisible value.
861#[derive(Clone, Copy, Debug, PartialEq, Eq)]
862pub struct RecoverySequenceBlockRestore {
863    /// Optional leading active-terminal claim.
864    pub terminal: Option<RecoverySequenceTerminalRestore>,
865    /// Exact `RS` recovery-attach position.
866    pub recovery_attach_seq: DeliverySeq,
867    /// Exact adjacent `RT` replacement-terminal position.
868    pub replacement_terminal_seq: DeliverySeq,
869}
870
871/// Validated indivisible DCR sequence interval.
872#[derive(Clone, Copy, Debug, PartialEq, Eq)]
873pub struct RecoverySequenceBlock {
874    terminal: Option<RecoverySequenceTerminalRestore>,
875    recovery_attach_seq: DeliverySeq,
876    replacement_terminal_seq: DeliverySeq,
877    participant_index: ParticipantId,
878    marker_delivery_seq: DeliverySeq,
879    recovered_binding_epoch: BindingEpoch,
880}
881
882impl RecoverySequenceBlock {
883    /// Returns the optional leading active-terminal claim.
884    #[must_use]
885    pub const fn terminal(self) -> Option<RecoverySequenceTerminalRestore> {
886        self.terminal
887    }
888
889    /// Returns the exact `RS` position.
890    #[must_use]
891    pub const fn recovery_attach_seq(self) -> DeliverySeq {
892        self.recovery_attach_seq
893    }
894
895    /// Returns the exact adjacent `RT` position.
896    #[must_use]
897    pub const fn replacement_terminal_seq(self) -> DeliverySeq {
898        self.replacement_terminal_seq
899    }
900
901    /// Returns the participant recovered by the block.
902    #[must_use]
903    pub const fn participant_index(self) -> ParticipantId {
904        self.participant_index
905    }
906
907    /// Returns the exact delivered marker fenced by recovery.
908    #[must_use]
909    pub const fn marker_delivery_seq(self) -> DeliverySeq {
910        self.marker_delivery_seq
911    }
912
913    /// Returns the old binding epoch fenced by recovery.
914    #[must_use]
915    pub const fn recovered_binding_epoch(self) -> BindingEpoch {
916        self.recovered_binding_epoch
917    }
918}
919
920/// Public persisted input for sequence-frontier restoration.
921#[derive(Clone, Debug, Default, PartialEq, Eq)]
922pub struct SequenceClaimFrontierRestore {
923    /// Exact movable identity-slot sequence claims.
924    pub movable_claims: Vec<MovableSequenceClaim>,
925    /// Immutable pending terminal and marker candidates.
926    pub immutable_candidates: Vec<ImmutableSequenceCandidate>,
927    /// Compact conditional product ranges.
928    pub products: SequenceProductRangesRestore,
929    /// The sole optional DCR interval.
930    pub recovery: Option<RecoverySequenceBlockRestore>,
931}
932
933/// Validated exact sequence claim frontier.
934#[derive(Clone, Debug, PartialEq, Eq)]
935pub struct SequenceClaimFrontier {
936    ledger: SequenceLedger,
937    movable_claims: Vec<MovableSequenceClaim>,
938    immutable_candidates: Vec<ImmutableSequenceCandidate>,
939    products: SequenceProductRanges,
940    recovery: Option<RecoverySequenceBlock>,
941}
942
943impl SequenceClaimFrontier {
944    /// Returns the aggregate ledger validated against these exact owners.
945    #[must_use]
946    pub const fn ledger(&self) -> SequenceLedger {
947        self.ledger
948    }
949
950    /// Borrows exact movable identity-slot claims.
951    #[must_use]
952    pub fn movable_claims(&self) -> &[MovableSequenceClaim] {
953        &self.movable_claims
954    }
955
956    /// Borrows immutable candidates in delivery-sequence order.
957    #[must_use]
958    pub fn immutable_candidates(&self) -> &[ImmutableSequenceCandidate] {
959        &self.immutable_candidates
960    }
961
962    /// Borrows compact conditional product ranges.
963    #[must_use]
964    pub const fn products(&self) -> &SequenceProductRanges {
965        &self.products
966    }
967
968    /// Returns the sole validated DCR sequence interval.
969    #[must_use]
970    pub const fn recovery(&self) -> Option<RecoverySequenceBlock> {
971        self.recovery
972    }
973}
974
975/// Direct movable transaction-order owner stored in a bounded identity slot.
976#[derive(Clone, Copy, Debug, PartialEq, Eq)]
977pub enum OrderDirectOwner {
978    /// Future terminal for one exact active binding (`A`).
979    ActiveBindingTerminal(BindingTerminalOwner),
980    /// Future tokenized `Left` for one live member (`X`).
981    MembershipExit {
982        /// Permanent participant index.
983        participant_index: ParticipantId,
984    },
985}
986
987/// One exact movable transaction-order claim.
988#[derive(Clone, Copy, Debug, PartialEq, Eq)]
989pub struct MovableOrderClaim {
990    /// Owned major.
991    pub transaction_order: TransactionOrder,
992    /// Exact bounded-slot owner.
993    pub owner: OrderDirectOwner,
994}
995
996/// Public persisted candidate-major group supplied during restoration.
997///
998/// Several candidate keys caused by one transaction contribute one numeric
999/// major to the frontier union.
1000#[derive(Clone, Debug, PartialEq, Eq)]
1001pub struct ImmutableOrderCandidateMajorRestore {
1002    /// Immutable assigned major.
1003    pub transaction_order: TransactionOrder,
1004    /// Complete candidate keys sharing that major.
1005    pub candidate_keys: Vec<super::AdmissionOrder>,
1006}
1007
1008/// Validated immutable candidate-major group.
1009#[derive(Clone, Debug, PartialEq, Eq)]
1010pub struct ImmutableOrderCandidateMajor {
1011    transaction_order: TransactionOrder,
1012    candidate_keys: Vec<super::AdmissionOrder>,
1013}
1014
1015impl ImmutableOrderCandidateMajor {
1016    /// Returns the immutable assigned major.
1017    #[must_use]
1018    pub const fn transaction_order(&self) -> TransactionOrder {
1019        self.transaction_order
1020    }
1021
1022    /// Borrows the complete candidate keys in canonical tuple order.
1023    #[must_use]
1024    pub fn candidate_keys(&self) -> &[super::AdmissionOrder] {
1025        &self.candidate_keys
1026    }
1027}
1028
1029/// Optional leading `A` member of a persisted DCR order interval.
1030#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1031pub struct RecoveryOrderActiveBindingRestore {
1032    /// Owned major immediately before `RO`.
1033    pub transaction_order: TransactionOrder,
1034    /// Exact active binding-terminal owner.
1035    pub owner: BindingTerminalOwner,
1036}
1037
1038/// Public persisted shape of the sole DCR order interval.
1039#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1040pub struct RecoveryOrderBlockRestore {
1041    /// Optional leading active-binding claim before its terminal materializes.
1042    pub active_binding: Option<RecoveryOrderActiveBindingRestore>,
1043    /// Exact `RO` recovery-operation major.
1044    pub recovery_operation_order: TransactionOrder,
1045    /// Exact adjacent `RA` replacement-terminal major.
1046    pub replacement_terminal_order: TransactionOrder,
1047}
1048
1049/// Validated indivisible DCR order interval.
1050#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1051pub struct RecoveryOrderBlock {
1052    active_binding: Option<RecoveryOrderActiveBindingRestore>,
1053    recovery_operation_order: TransactionOrder,
1054    replacement_terminal_order: TransactionOrder,
1055    participant_index: ParticipantId,
1056    marker_delivery_seq: DeliverySeq,
1057    recovered_binding_epoch: BindingEpoch,
1058}
1059
1060impl RecoveryOrderBlock {
1061    /// Returns the optional leading active-binding claim.
1062    #[must_use]
1063    pub const fn active_binding(self) -> Option<RecoveryOrderActiveBindingRestore> {
1064        self.active_binding
1065    }
1066
1067    /// Returns the exact `RO` major.
1068    #[must_use]
1069    pub const fn recovery_operation_order(self) -> TransactionOrder {
1070        self.recovery_operation_order
1071    }
1072
1073    /// Returns the exact adjacent `RA` major.
1074    #[must_use]
1075    pub const fn replacement_terminal_order(self) -> TransactionOrder {
1076        self.replacement_terminal_order
1077    }
1078
1079    /// Returns the participant recovered by the block.
1080    #[must_use]
1081    pub const fn participant_index(self) -> ParticipantId {
1082        self.participant_index
1083    }
1084
1085    /// Returns the exact delivered marker fenced by recovery.
1086    #[must_use]
1087    pub const fn marker_delivery_seq(self) -> DeliverySeq {
1088        self.marker_delivery_seq
1089    }
1090
1091    /// Returns the old binding epoch fenced by recovery.
1092    #[must_use]
1093    pub const fn recovered_binding_epoch(self) -> BindingEpoch {
1094        self.recovered_binding_epoch
1095    }
1096}
1097
1098/// Public persisted input for transaction-order-frontier restoration.
1099#[derive(Clone, Debug, Default, PartialEq, Eq)]
1100pub struct OrderClaimFrontierRestore {
1101    /// Exact movable `A` and `X` claims.
1102    pub movable_claims: Vec<MovableOrderClaim>,
1103    /// Immutable candidate-major prefix above the caller-major high watermark.
1104    pub immutable_candidates: Vec<ImmutableOrderCandidateMajorRestore>,
1105    /// The sole optional DCR order interval.
1106    pub recovery: Option<RecoveryOrderBlockRestore>,
1107}
1108
1109/// Validated exact transaction-order claim frontier.
1110#[derive(Clone, Debug, PartialEq, Eq)]
1111pub struct OrderClaimFrontier {
1112    ledger: OrderLedger,
1113    movable_claims: Vec<MovableOrderClaim>,
1114    immutable_candidates: Vec<ImmutableOrderCandidateMajor>,
1115    recovery: Option<RecoveryOrderBlock>,
1116}
1117
1118impl OrderClaimFrontier {
1119    /// Returns the aggregate ledger validated against these exact owners.
1120    #[must_use]
1121    pub const fn ledger(&self) -> OrderLedger {
1122        self.ledger
1123    }
1124
1125    /// Borrows exact movable `A` and `X` claims.
1126    #[must_use]
1127    pub fn movable_claims(&self) -> &[MovableOrderClaim] {
1128        &self.movable_claims
1129    }
1130
1131    /// Borrows immutable candidate-major groups in numeric order.
1132    #[must_use]
1133    pub fn immutable_candidates(&self) -> &[ImmutableOrderCandidateMajor] {
1134        &self.immutable_candidates
1135    }
1136
1137    /// Returns the sole validated DCR order interval.
1138    #[must_use]
1139    pub const fn recovery(&self) -> Option<RecoveryOrderBlock> {
1140        self.recovery
1141    }
1142}
1143
1144#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1145enum RecoveryClaimPhase {
1146    PreFate,
1147    PostFate,
1148    RecoveredBound,
1149}
1150
1151/// Sealed recovery-claim provenance derived from one exact stored edge.
1152///
1153/// Before fate, only a marker-backed cursor witness can prove the full
1154/// `[T,RS,RT]` / `[A,RO,RA]` blocks. After fate, only the resulting
1155/// [`super::DetachedCredentialRecovery`] can prove the remaining pairs. The
1156/// prospective replacement epoch is deliberately absent: it does not exist
1157/// until fenced attach transfers `RT`/`RA` into `T`/`A`.
1158#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1159pub struct RecoveryClaimProvenance {
1160    participant_index: ParticipantId,
1161    marker_delivery_seq: DeliverySeq,
1162    prior_binding_epoch: BindingEpoch,
1163    current_binding_epoch: BindingEpoch,
1164    phase: RecoveryClaimPhase,
1165}
1166
1167impl RecoveryClaimProvenance {
1168    /// Derives recovery authority only from a marker-backed PCP or the exact DCR
1169    /// edge produced by its binding fate.
1170    #[must_use]
1171    pub const fn from_stored_edge(edge: super::StoredEdge) -> Option<Self> {
1172        match edge {
1173            super::StoredEdge::MarkerDelivery(delivery) => Some(Self {
1174                participant_index: delivery.participant_id(),
1175                marker_delivery_seq: delivery.marker_delivery_seq(),
1176                prior_binding_epoch: delivery.binding_epoch(),
1177                current_binding_epoch: delivery.binding_epoch(),
1178                phase: RecoveryClaimPhase::PreFate,
1179            }),
1180            super::StoredEdge::ParticipantCursorProgress(progress) => {
1181                let Some(marker_delivery_seq) = progress.marker_delivery_seq() else {
1182                    return None;
1183                };
1184                Some(Self {
1185                    participant_index: progress.participant_id(),
1186                    marker_delivery_seq,
1187                    prior_binding_epoch: progress.binding_epoch(),
1188                    current_binding_epoch: progress.binding_epoch(),
1189                    phase: RecoveryClaimPhase::PreFate,
1190                })
1191            }
1192            super::StoredEdge::DetachedCredentialRecovery(recovery) => Some(Self {
1193                participant_index: recovery.participant_id(),
1194                marker_delivery_seq: recovery.marker_delivery_seq(),
1195                prior_binding_epoch: recovery.prior_binding_epoch(),
1196                current_binding_epoch: recovery.prior_binding_epoch(),
1197                phase: RecoveryClaimPhase::PostFate,
1198            }),
1199            _ => None,
1200        }
1201    }
1202
1203    /// Returns the recovery participant.
1204    #[must_use]
1205    pub const fn participant_index(self) -> ParticipantId {
1206        self.participant_index
1207    }
1208
1209    /// Returns the delivered marker that makes fenced recovery possible.
1210    #[must_use]
1211    pub const fn marker_delivery_seq(self) -> DeliverySeq {
1212        self.marker_delivery_seq
1213    }
1214
1215    /// Returns the exact prior binding epoch.
1216    #[must_use]
1217    pub const fn prior_binding_epoch(self) -> BindingEpoch {
1218        self.prior_binding_epoch
1219    }
1220}
1221
1222/// Public persisted input for restoring both coupled claim frontiers.
1223#[derive(Clone, Debug, PartialEq, Eq)]
1224pub struct ClaimFrontiersRestore {
1225    /// Owning conversation for every typed identity/history authority.
1226    pub conversation_id: ConversationId,
1227    /// Raw current live identities; validation occurs after numeric frontiers.
1228    pub active_identities: Vec<FrontierParticipant>,
1229    /// Signed permanent identity-slot cap `I`.
1230    pub identity_slot_limit: u64,
1231    /// Current physical retained suffix floor.
1232    pub retained_floor: u128,
1233    /// Signed cap on retained record facts supplied by storage.
1234    pub retained_record_limit: u64,
1235    /// Typed retained direct-record facts used by provenance validation.
1236    pub retained_records: Vec<RetainedCausalRecord>,
1237    /// Retained marker sequences that still own current credit/anchor state.
1238    ///
1239    /// Released historical marker records remain in `retained_records` but are
1240    /// deliberately absent here. This bounded subset has at most one current
1241    /// owner per permanent participant and at most `I` entries.
1242    pub active_marker_anchors: Vec<DeliverySeq>,
1243    /// Immutable exact-epoch delivery facts for retained marker history.
1244    ///
1245    /// Unlike `active_marker_anchors`, this list is bounded by retained history
1246    /// rather than identity count and may contain multiple facts for one
1247    /// participant.
1248    pub historical_marker_deliveries: Vec<HistoricalMarkerDeliveryFactRestore>,
1249    /// O(I) factual compacted terminal/exit rows retained by identities and tombstones.
1250    pub historical_causal_facts: Vec<HistoricalCausalFactRestore>,
1251    /// Sequence-side persisted ownership.
1252    pub sequence: SequenceClaimFrontierRestore,
1253    /// Order-side persisted ownership.
1254    pub order: OrderClaimFrontierRestore,
1255    /// Exact planned or retained marker selected by the sole recovery quartet.
1256    ///
1257    /// This raw selector carries no participant or epoch authority. Restoration
1258    /// derives both solely from one fully validated marker candidate/record and,
1259    /// after binding fate, the exact typed DCR edge.
1260    pub recovery_marker_delivery_seq: Option<DeliverySeq>,
1261}
1262
1263/// Crate-private description of one live [`ClaimFrontiers`], produced by
1264/// [`ClaimFrontiers::describe`] and taken back by
1265/// [`ClaimFrontiers::prevalidate_description`].
1266///
1267/// It carries the public restore input, the two aggregate ledgers that
1268/// [`ClaimFrontiers::restore`] takes beside it, and the one piece of live
1269/// state the public input deliberately cannot name: the fenced-marker
1270/// issuance latch. That latch stays here, not on [`ClaimFrontiersRestore`],
1271/// so the public restore input cannot name it; capsule bytes can still carry
1272/// any value, so every restore that installs it checks it first.
1273#[derive(Clone, Debug, PartialEq, Eq)]
1274pub(super) struct ClaimFrontiersDescription {
1275    /// The persisted restore input for both coupled frontiers.
1276    pub(super) restore: ClaimFrontiersRestore,
1277    /// The aggregate sequence ledger validated against `restore.sequence`.
1278    pub(super) sequence_ledger: SequenceLedger,
1279    /// The aggregate order ledger validated against `restore.order`.
1280    pub(super) order_ledger: OrderLedger,
1281    /// The fenced-marker issuance latch: which retained marker a fenced
1282    /// attach has already taken its one-use authority from, if any.
1283    pub(super) fenced_marker_issued: Option<(ParticipantId, DeliverySeq, BindingEpoch)>,
1284}
1285
1286/// Numerically and causally prevalidated claim-frontier snapshot.
1287///
1288/// This crate-private phase breaks the cold-restore cycle without exposing a
1289/// forgeable marker token: retained history is validated first, storage uses at
1290/// most one sealed marker record to rebuild its typed edge, and only then may
1291/// recovery blocks be finalized against that edge.
1292#[derive(Debug)]
1293pub(super) struct ClaimFrontiersPrevalidated {
1294    conversation_id: ConversationId,
1295    active_identities: ActiveIdentityRanks,
1296    identity_slot_limit: u64,
1297    retained_floor: u128,
1298    retained_records: Vec<RetainedCausalRecord>,
1299    marker_records: Vec<RetainedCausalRecord>,
1300    historical_marker_deliveries: Vec<HistoricalMarkerDeliveryAuthority>,
1301    historical_causal_authorities: Vec<HistoricalCausalAuthority>,
1302    binding_origins: Vec<BindingOrigin>,
1303    sequence_restore: SequenceClaimFrontierRestore,
1304    order_restore: OrderClaimFrontierRestore,
1305    recovery_marker_delivery_seq: Option<DeliverySeq>,
1306    sequence_ledger: SequenceLedger,
1307    order_ledger: OrderLedger,
1308    issued_marker_record: Option<MarkerRecordRequest>,
1309    /// The fenced-marker issuance latch that `finish` installs.
1310    ///
1311    /// Always `None` on the public restore route; only a
1312    /// [`ClaimFrontiersDescription`] supplies a described value, which the
1313    /// joint restore checks with [`ClaimFrontiers::fenced_latch_matches`].
1314    fenced_marker_issued: Option<(ParticipantId, DeliverySeq, BindingEpoch)>,
1315}
1316
1317#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1318enum MarkerRecordUse {
1319    Planned(FrontierBinding),
1320    Delivered(FrontierBinding),
1321    Recovered {
1322        prior_binding_epoch: BindingEpoch,
1323        recovered_binding_epoch: BindingEpoch,
1324    },
1325}
1326
1327/// Exact closure-derived context requesting one retained-marker restore token.
1328#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1329pub(super) struct MarkerRecordRequest {
1330    participant_index: ParticipantId,
1331    marker_delivery_seq: DeliverySeq,
1332    use_kind: MarkerRecordUse,
1333}
1334
1335impl MarkerRecordRequest {
1336    /// Requests an undelivered planned marker in its exact current target state.
1337    pub(super) const fn planned(
1338        participant_index: ParticipantId,
1339        marker_delivery_seq: DeliverySeq,
1340        target: FrontierBinding,
1341    ) -> Self {
1342        Self {
1343            participant_index,
1344            marker_delivery_seq,
1345            use_kind: MarkerRecordUse::Planned(target),
1346        }
1347    }
1348
1349    /// Requests a durably delivered marker in its exact current target state.
1350    pub(super) const fn delivered(
1351        participant_index: ParticipantId,
1352        marker_delivery_seq: DeliverySeq,
1353        target: FrontierBinding,
1354    ) -> Self {
1355        Self {
1356            participant_index,
1357            marker_delivery_seq,
1358            use_kind: MarkerRecordUse::Delivered(target),
1359        }
1360    }
1361
1362    /// Requests the detached old-epoch predecessor of fenced recovery.
1363    pub(super) const fn recovered(
1364        participant_index: ParticipantId,
1365        marker_delivery_seq: DeliverySeq,
1366        prior_binding_epoch: BindingEpoch,
1367        recovered_binding_epoch: BindingEpoch,
1368    ) -> Self {
1369        Self {
1370            participant_index,
1371            marker_delivery_seq,
1372            use_kind: MarkerRecordUse::Recovered {
1373                prior_binding_epoch,
1374                recovered_binding_epoch,
1375            },
1376        }
1377    }
1378}
1379
1380/// Crate-internal result of consuming the exact next marker candidate.
1381#[derive(Debug)]
1382pub(super) struct MarkerDrainCore {
1383    frontiers: ClaimFrontiers,
1384    candidate: ValidatedMarkerCandidate,
1385    record: ValidatedMarkerRecord,
1386}
1387
1388impl MarkerDrainCore {
1389    /// Splits the indivisible frontier update from its fresh-edge and retained
1390    /// record authorities for the public marker-drain operation wrapper.
1391    pub(super) fn into_parts(
1392        self,
1393    ) -> (
1394        ClaimFrontiers,
1395        ValidatedMarkerCandidate,
1396        ValidatedMarkerRecord,
1397    ) {
1398        (self.frontiers, self.candidate, self.record)
1399    }
1400}
1401
1402/// Invalid or non-marker mandatory prefix encountered by marker drain.
1403#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1404pub(super) enum MarkerDrainCoreError {
1405    /// No immutable candidate is currently owed.
1406    NoCandidate,
1407    /// A binding terminal has global precedence over marker work.
1408    BindingTerminalFirst,
1409    /// The first marker does not own exactly `H+1`.
1410    SequenceNotNext,
1411    /// A marker may share an already allocated causal major but cannot allocate one.
1412    CausalMajorNotAllocated,
1413    /// Cross-counter validation promised an order key that is now absent.
1414    MissingOrderCandidate,
1415    /// Consuming `M` did not yield a valid post-append sequence ledger.
1416    ResultingLedger,
1417}
1418
1419/// Validated participant-keyed sequence and order claim authority.
1420#[derive(Debug, Eq)]
1421pub struct ClaimFrontiers {
1422    ordinary_cursor_release: Option<Box<super::storage::OrdinaryBindingFateRestore>>,
1423    conversation_id: ConversationId,
1424    active_identities: ActiveIdentityRanks,
1425    identity_slot_limit: u64,
1426    retained_floor: u128,
1427    retained_records: Vec<RetainedCausalRecord>,
1428    marker_records: Vec<RetainedCausalRecord>,
1429    fenced_marker_issued: Option<(ParticipantId, DeliverySeq, BindingEpoch)>,
1430    sequence: SequenceClaimFrontier,
1431    order: OrderClaimFrontier,
1432    /// The marker deliveries this conversation has already made, as the
1433    /// restore validated them.
1434    ///
1435    /// Predecessor provenance: the restore validated these, used them to check
1436    /// its consumers and to resolve recovery provenance, and then dropped them
1437    /// — the live frontier kept no record that the deliveries had ever been
1438    /// validated. A frontier that cannot say which deliveries it has made
1439    /// cannot be described back into a capsule, which is what this row exists
1440    /// to make possible.
1441    historical_marker_deliveries: Vec<HistoricalMarkerDeliveryAuthority>,
1442    /// The compacted terminal and exit facts this conversation's history
1443    /// sealed, as the restore validated them.
1444    ///
1445    /// Predecessor provenance: the restore validated these against owned
1446    /// history, checked the immutable candidates with them, and then dropped
1447    /// them. Live transitions never add or remove one; they carry the list
1448    /// forward unchanged so the frontier can describe it back.
1449    historical_causal_authorities: Vec<HistoricalCausalAuthority>,
1450}
1451
1452impl PartialEq for ClaimFrontiers {
1453    fn eq(&self, other: &Self) -> bool {
1454        // Capture provenance does not alter operational frontier equality.
1455        (
1456            &self.conversation_id,
1457            &self.active_identities,
1458            &self.identity_slot_limit,
1459            &self.retained_floor,
1460            &self.retained_records,
1461            &self.marker_records,
1462            &self.fenced_marker_issued,
1463            &self.sequence,
1464            &self.order,
1465            &self.historical_marker_deliveries,
1466            &self.historical_causal_authorities,
1467        ) == (
1468            &other.conversation_id,
1469            &other.active_identities,
1470            &other.identity_slot_limit,
1471            &other.retained_floor,
1472            &other.retained_records,
1473            &other.marker_records,
1474            &other.fenced_marker_issued,
1475            &other.sequence,
1476            &other.order,
1477            &other.historical_marker_deliveries,
1478            &other.historical_causal_authorities,
1479        )
1480    }
1481}
1482
1483/// Atomic protocol-owned initial-enrollment frontier result.
1484///
1485/// The wrapper owns the typed operation commit together with the exact frontier,
1486/// closure accounting, and retained `Attached` charge derived from it. It is not
1487/// cloneable and exposes no raw restore components or caller-selected positions.
1488#[derive(Debug, PartialEq, Eq)]
1489pub struct InitialEnrollmentFrontierCommit<F> {
1490    operation: InitialEnrollmentOperationCommit<F>,
1491    frontiers: ClaimFrontiers,
1492    closure_accounting: ClosureAccounting,
1493    attached_charge: ResourceVector,
1494}
1495
1496impl<F> InitialEnrollmentFrontierCommit<F> {
1497    /// Borrows the complete admitted enrollment operation.
1498    #[must_use]
1499    pub const fn operation(&self) -> &InitialEnrollmentOperationCommit<F> {
1500        &self.operation
1501    }
1502
1503    /// Borrows the directly constructed coupled claim frontiers.
1504    #[must_use]
1505    pub const fn frontiers(&self) -> &ClaimFrontiers {
1506        &self.frontiers
1507    }
1508
1509    /// Returns the exact closure accounting committed with the frontier.
1510    #[must_use]
1511    pub const fn closure_accounting(&self) -> ClosureAccounting {
1512        self.closure_accounting
1513    }
1514
1515    /// Returns the exact encoded charge of the retained `Attached` row.
1516    #[must_use]
1517    pub const fn attached_charge(&self) -> ResourceVector {
1518        self.attached_charge
1519    }
1520
1521    /// Consumes the atomic result for the crate-owned conversation event layer.
1522    #[allow(
1523        dead_code,
1524        reason = "the next conversation event body consumes this sealed operation/frontier unit"
1525    )]
1526    pub(in crate::lifecycle) fn into_conversation_parts(
1527        self,
1528    ) -> (
1529        InitialEnrollmentOperationCommit<F>,
1530        ClaimFrontiers,
1531        ClosureAccounting,
1532        ResourceVector,
1533    ) {
1534        (
1535            self.operation,
1536            self.frontiers,
1537            self.closure_accounting,
1538            self.attached_charge,
1539        )
1540    }
1541}
1542
1543/// An admitted initial enrollment disagreed with its typed frontier projection.
1544#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1545pub enum InitialEnrollmentFrontierError {
1546    /// Supplied encoded `Attached` charge differs from the admitted projection.
1547    AttachedChargeMismatch {
1548        /// Charge fixed by the admitted closure projection.
1549        expected: ResourceVector,
1550        /// Charge supplied for the exact encoded `Attached` row.
1551        actual: ResourceVector,
1552    },
1553    /// Membership, binding, and `Attached` facts do not describe participant zero.
1554    EnrollmentShape,
1555    /// Operation record positions and aggregate ledgers disagree.
1556    LedgerShape,
1557    /// Floor, observer, marker, recovery, or closure facts disagree.
1558    ClosureProjection,
1559    /// Deriving exact direct/product positions overflowed their fixed-width domain.
1560    PositionOverflow,
1561    /// The directly constructed sequence and order owners failed cross-validation.
1562    FrontierInvariant,
1563}
1564
1565/// Failed initial-frontier derivation retaining the speculative operation.
1566#[derive(Debug, PartialEq, Eq)]
1567pub struct InitialEnrollmentFrontierFailure<F> {
1568    operation: InitialEnrollmentOperationCommit<F>,
1569    error: InitialEnrollmentFrontierError,
1570}
1571
1572impl<F> InitialEnrollmentFrontierFailure<F> {
1573    /// Returns the exact derivation fault.
1574    #[must_use]
1575    pub const fn error(&self) -> InitialEnrollmentFrontierError {
1576        self.error
1577    }
1578
1579    /// Recovers the speculative operation for the crate-owned conversation layer.
1580    #[allow(
1581        dead_code,
1582        reason = "the conversation decision layer recovers or terminalizes the speculative enrollment"
1583    )]
1584    pub(in crate::lifecycle) fn into_operation(self) -> InitialEnrollmentOperationCommit<F> {
1585        self.operation
1586    }
1587}
1588
1589/// Failure to consume the exact order authority for a Leave transaction.
1590#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1591pub enum PrepareLeaveAuthorityError {
1592    /// Member and frontier name different conversations.
1593    Conversation,
1594    /// Member identity/cursor is absent or disagrees with the frontier.
1595    Identity,
1596    /// Binding state disagrees with the validated identity frontier.
1597    Binding,
1598    /// A globally earlier immutable candidate must drain first.
1599    ImmutablePrefix,
1600    /// The exact pending binding-terminal candidate is absent or not sole.
1601    PendingCandidate,
1602    /// The participant's unique `X` order handle is absent.
1603    MembershipExitClaim,
1604    /// Bound Leave lacks its exact active-binding `A` handle.
1605    ActiveBindingClaim,
1606    /// The selected later handle cannot relay every survivor into the suffix.
1607    OrderCapacity,
1608    /// Consuming the selected handles could not produce a valid order ledger.
1609    ResultingOrderLedger,
1610}
1611
1612/// Sealed marker candidate that passed complete frontier restoration.
1613///
1614/// This is the only authority from which the lifecycle module may construct a
1615/// new executable marker-delivery edge. Its private field prevents a storage
1616/// binding from turning raw participant/epoch/sequence values into that edge.
1617#[derive(Debug, PartialEq, Eq)]
1618pub struct ValidatedMarkerCandidate {
1619    conversation_id: ConversationId,
1620    candidate: MarkerCandidateAuthority,
1621    seal: MarkerAuthoritySeal,
1622}
1623
1624/// Descriptive retained-marker facts checked before a durable source read may
1625/// proceed to the one-use authority mint.
1626#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1627pub(super) struct FencedMarkerSourceRecord {
1628    pub(super) conversation_id: ConversationId,
1629    pub(super) delivery_seq: DeliverySeq,
1630    pub(super) admission_order: super::AdmissionOrder,
1631    pub(super) participant_id: ParticipantId,
1632    pub(super) provenance: MarkerProvenance,
1633    pub(super) target_binding: FrontierBinding,
1634}
1635
1636/// Sealed retained marker record paired with its exact current authority.
1637///
1638/// Cold restoration of marker-derived edges consumes this token so raw storage
1639/// fields cannot fabricate durable marker delivery.
1640#[derive(Debug, PartialEq, Eq)]
1641pub struct ValidatedMarkerRecord {
1642    conversation_id: ConversationId,
1643    record: RetainedCausalRecord,
1644    provenance: MarkerProvenance,
1645    target_binding: FrontierBinding,
1646    occurrence: MarkerRecordOccurrence,
1647    seal: MarkerAuthoritySeal,
1648}
1649
1650/// Delivery-occurrence state proven together with one retained marker record.
1651///
1652/// This discriminator is deliberately private. A retained append record does
1653/// not by itself prove that the marker reached its target binding; only joint
1654/// frontier restoration may upgrade an undelivered record to `Delivered` from
1655/// the exact historical delivery fact.
1656#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1657pub(super) enum MarkerRecordOccurrence {
1658    /// The marker was appended but has not been delivered.
1659    Undelivered,
1660    /// The marker was durably delivered to the exact target binding.
1661    Delivered,
1662}
1663
1664#[derive(Debug, PartialEq, Eq)]
1665enum MarkerAuthoritySeal {
1666    Validated,
1667}
1668
1669/// Supplies the private record type to a compile-time closure without ever
1670/// constructing or exposing record authority at runtime.
1671///
1672/// This narrow type probe lets downstream workspace UI tests verify move-only
1673/// behavior while the record itself remains absent from lifecycle re-exports.
1674#[doc(hidden)]
1675pub fn with_validated_marker_record_type<F>(probe: F)
1676where
1677    F: FnOnce(ValidatedMarkerRecord),
1678{
1679    drop(probe);
1680}
1681
1682impl ValidatedMarkerRecord {
1683    /// Consumes the one-shot retained-record token after a restore attempt.
1684    pub(super) const fn consume(self) {
1685        match self.seal {
1686            MarkerAuthoritySeal::Validated => {}
1687        }
1688    }
1689
1690    /// Returns the owning conversation selected by complete frontier restore.
1691    #[must_use]
1692    pub const fn conversation_id(&self) -> ConversationId {
1693        self.conversation_id
1694    }
1695
1696    /// Returns the permanent marker owner.
1697    #[must_use]
1698    pub const fn participant_id(&self) -> ParticipantId {
1699        self.record.admission_order.participant_index()
1700    }
1701
1702    /// Returns the durable appended marker sequence.
1703    #[must_use]
1704    pub const fn delivery_seq(&self) -> DeliverySeq {
1705        self.record.delivery_seq
1706    }
1707
1708    /// Returns the immutable retained-row causal key.
1709    #[must_use]
1710    pub const fn admission_order(&self) -> super::AdmissionOrder {
1711        self.record.admission_order
1712    }
1713
1714    /// Returns immutable marker provenance.
1715    #[must_use]
1716    pub const fn provenance(&self) -> MarkerProvenance {
1717        self.provenance
1718    }
1719
1720    /// Returns the current or last authoritative target binding.
1721    #[must_use]
1722    pub const fn target_binding(&self) -> FrontierBinding {
1723        self.target_binding
1724    }
1725
1726    /// Returns the exact current or last authoritative epoch.
1727    #[must_use]
1728    pub const fn binding_epoch(&self) -> BindingEpoch {
1729        binding_epoch(self.target_binding)
1730    }
1731
1732    /// Reports whether joint restoration proved the delivery occurrence.
1733    #[must_use]
1734    pub(super) const fn occurrence(&self) -> MarkerRecordOccurrence {
1735        self.occurrence
1736    }
1737
1738    /// Marks a synthetic crate-test token as delivered.
1739    #[cfg(any(test, feature = "test-support"))]
1740    pub(super) const fn delivered_for_test(mut self) -> Self {
1741        self.occurrence = MarkerRecordOccurrence::Delivered;
1742        self
1743    }
1744}
1745
1746impl ValidatedMarkerCandidate {
1747    /// Consumes the one-shot fresh-candidate token after delivery materializes.
1748    pub(super) const fn consume(self) {
1749        match self.seal {
1750            MarkerAuthoritySeal::Validated => {}
1751        }
1752    }
1753
1754    /// Returns the owning conversation selected by complete frontier restore.
1755    #[must_use]
1756    pub(super) const fn conversation_id(&self) -> ConversationId {
1757        self.conversation_id
1758    }
1759
1760    /// Returns the permanent marker owner.
1761    #[must_use]
1762    pub(super) const fn participant_index(&self) -> ParticipantId {
1763        self.candidate.admission_order.participant_index()
1764    }
1765
1766    /// Returns the permanent marker owner.
1767    #[must_use]
1768    pub(super) const fn participant_id(&self) -> ParticipantId {
1769        self.participant_index()
1770    }
1771
1772    /// Returns the exact assigned marker sequence.
1773    #[must_use]
1774    pub(super) const fn delivery_seq(&self) -> DeliverySeq {
1775        self.candidate.delivery_seq
1776    }
1777
1778    /// Returns the exact current or last authoritative target binding.
1779    #[must_use]
1780    pub(super) const fn target_binding(&self) -> FrontierBinding {
1781        self.candidate.target_binding
1782    }
1783
1784    /// Returns immutable marker provenance.
1785    #[must_use]
1786    pub(super) const fn provenance(&self) -> MarkerProvenance {
1787        self.candidate.provenance
1788    }
1789
1790    pub(super) const fn abandoned_after(&self) -> DeliverySeq {
1791        self.candidate.abandoned_after
1792    }
1793
1794    pub(super) const fn abandoned_through(&self) -> DeliverySeq {
1795        self.candidate.abandoned_through
1796    }
1797
1798    pub(super) const fn physical_floor_at_decision(&self) -> DeliverySeq {
1799        self.candidate.physical_floor_at_decision
1800    }
1801}
1802
1803#[derive(Clone, Copy)]
1804struct InitialEnrollmentFrontierShape {
1805    conversation_id: ConversationId,
1806    binding_epoch: BindingEpoch,
1807    identity_slot_limit: u64,
1808    retained_floor: u128,
1809    attached: AttachedLifecycleRecord,
1810    order_ledger: OrderLedger,
1811    sequence_ledger: SequenceLedger,
1812    closure_accounting: ClosureAccounting,
1813}
1814
1815#[derive(Clone, Copy)]
1816struct InitialFrontierPositions {
1817    terminal_sequence: DeliverySeq,
1818    exit_sequence: DeliverySeq,
1819    product_sequence: DeliverySeq,
1820    active_terminal_order: TransactionOrder,
1821    exit_order: TransactionOrder,
1822}
1823
1824fn initial_enrollment_frontier_shape<F>(
1825    operation: &InitialEnrollmentOperationCommit<F>,
1826    attached_charge: ResourceVector,
1827) -> Result<InitialEnrollmentFrontierShape, InitialEnrollmentFrontierError> {
1828    let projection = operation.closure_projection();
1829    let expected_charge = projection.resulting_retained_charge();
1830    if attached_charge != expected_charge {
1831        return Err(InitialEnrollmentFrontierError::AttachedChargeMismatch {
1832            expected: expected_charge,
1833            actual: attached_charge,
1834        });
1835    }
1836    let enrollment = operation.enrollment();
1837    let attached = enrollment.attached;
1838    let BindingState::Bound(binding) = enrollment.binding_state else {
1839        return Err(InitialEnrollmentFrontierError::EnrollmentShape);
1840    };
1841    if enrollment.member.participant_id() != 0
1842        || enrollment.member.cursor() != 0
1843        || binding.participant_id != 0
1844        || attached.participant_id() != 0
1845        || binding.conversation_id != enrollment.member.conversation_id()
1846        || attached.conversation_id() != binding.conversation_id
1847        || attached.binding_epoch() != binding.binding_epoch
1848        || projection.participant_index() != 0
1849        || projection.binding_epoch() != binding.binding_epoch
1850        || projection.identity_slots() == 0
1851    {
1852        return Err(InitialEnrollmentFrontierError::EnrollmentShape);
1853    }
1854    let admission_order = attached.admission_order();
1855    let order_ledger = operation.order().resulting();
1856    let sequence_ledger = operation.sequence().resulting();
1857    let order_claims = order_ledger.claims();
1858    let sequence_claims = sequence_ledger.claims();
1859    if operation.order().major() != admission_order.transaction_order()
1860        || !matches!(order_ledger.high(), OrderHigh::Allocated(value) if value == admission_order.transaction_order())
1861        || order_claims.active_binding_terminals() != 1
1862        || order_claims.membership_exits() != 1
1863        || order_claims.recovery_operation()
1864        || order_claims.recovery_replacement_terminal()
1865        || sequence_ledger.high_watermark() != attached.delivery_seq()
1866        || sequence_claims.live_members() != 1
1867        || sequence_claims.binding_terminals() != 1
1868        || sequence_claims.markers() != 0
1869        || sequence_claims.recovery() != RecoverySequenceReserve::None
1870        || admission_order.candidate_phase() != CandidatePhase::AttachLifecycle
1871    {
1872        return Err(InitialEnrollmentFrontierError::LedgerShape);
1873    }
1874    Ok(InitialEnrollmentFrontierShape {
1875        conversation_id: binding.conversation_id,
1876        binding_epoch: binding.binding_epoch,
1877        identity_slot_limit: projection.identity_slots(),
1878        retained_floor: projection.resulting_floor(),
1879        attached,
1880        order_ledger,
1881        sequence_ledger,
1882        closure_accounting: validate_initial_enrollment_closure(operation, projection, attached)?,
1883    })
1884}
1885
1886fn validate_initial_enrollment_closure<F>(
1887    operation: &InitialEnrollmentOperationCommit<F>,
1888    projection: &InitialEnrollmentClosureProjection,
1889    attached: AttachedLifecycleRecord,
1890) -> Result<ClosureAccounting, InitialEnrollmentFrontierError> {
1891    let accounting = projection.resulting_closure_accounting();
1892    let state_matches = match accounting.state() {
1893        ClosureState::Clear => {
1894            projection.debt().is_zero()
1895                && projection.remaining_recovery_claim() == ResourceVector::default()
1896        }
1897        ClosureState::Owed {
1898            debt,
1899            edge: StoredEdge::ObserverProjection(observer),
1900        } => {
1901            debt.value() == projection.debt()
1902                && observer == ObserverProjection::new(attached.delivery_seq())
1903                && projection.remaining_recovery_claim() == accounting.edge_k_remaining()
1904        }
1905        ClosureState::Owed { .. } => false,
1906    };
1907    if projection.resulting_floor() != 1
1908        || projection.resulting_floor() != operation.observer_floor().cap_floor()
1909        || operation.observer_floor().observer_progress() != 0
1910        || projection.recovery_quartet() != RecoveryQuartetStatus::None
1911        || !projection.new_marker_candidates().is_empty()
1912        || accounting.marker_capacity_credits() != 0
1913        || accounting.marker_anchors() != 0
1914        || accounting.edge_sequence_claims() != 0
1915        || accounting.edge_order_position_claims() != 0
1916        || accounting.baseline() != projection.resulting_baseline()
1917        || !state_matches
1918    {
1919        Err(InitialEnrollmentFrontierError::ClosureProjection)
1920    } else {
1921        Ok(accounting)
1922    }
1923}
1924
1925fn initial_frontier_positions(
1926    attached: AttachedLifecycleRecord,
1927) -> Result<InitialFrontierPositions, InitialEnrollmentFrontierError> {
1928    let terminal_sequence = attached
1929        .delivery_seq()
1930        .checked_add(1)
1931        .ok_or(InitialEnrollmentFrontierError::PositionOverflow)?;
1932    let exit_sequence = terminal_sequence
1933        .checked_add(1)
1934        .ok_or(InitialEnrollmentFrontierError::PositionOverflow)?;
1935    let product_sequence = exit_sequence
1936        .checked_add(1)
1937        .ok_or(InitialEnrollmentFrontierError::PositionOverflow)?;
1938    let active_terminal_order = attached
1939        .admission_order()
1940        .transaction_order()
1941        .checked_add(1)
1942        .ok_or(InitialEnrollmentFrontierError::PositionOverflow)?;
1943    let exit_order = active_terminal_order
1944        .checked_add(1)
1945        .ok_or(InitialEnrollmentFrontierError::PositionOverflow)?;
1946    Ok(InitialFrontierPositions {
1947        terminal_sequence,
1948        exit_sequence,
1949        product_sequence,
1950        active_terminal_order,
1951        exit_order,
1952    })
1953}
1954
1955fn initial_sequence_frontier(
1956    shape: &InitialEnrollmentFrontierShape,
1957    positions: InitialFrontierPositions,
1958    terminal: BindingTerminalOwner,
1959) -> SequenceClaimFrontier {
1960    SequenceClaimFrontier {
1961        ledger: shape.sequence_ledger,
1962        movable_claims: alloc::vec![
1963            MovableSequenceClaim {
1964                delivery_seq: positions.terminal_sequence,
1965                owner: SequenceDirectOwner::BindingTerminal(terminal),
1966            },
1967            MovableSequenceClaim {
1968                delivery_seq: positions.exit_sequence,
1969                owner: SequenceDirectOwner::MembershipExit {
1970                    participant_index: 0,
1971                },
1972            },
1973        ],
1974        immutable_candidates: Vec::new(),
1975        products: SequenceProductRanges {
1976            live_times_terminal: alloc::vec![TerminalProductRange {
1977                start: positions.product_sequence,
1978                length: 1,
1979                terminal,
1980            }],
1981            live_times_replacement_terminal: None,
1982            other_live_times_exit: Vec::new(),
1983        },
1984        recovery: None,
1985    }
1986}
1987
1988fn initial_order_frontier(
1989    shape: &InitialEnrollmentFrontierShape,
1990    positions: InitialFrontierPositions,
1991    terminal: BindingTerminalOwner,
1992) -> OrderClaimFrontier {
1993    OrderClaimFrontier {
1994        ledger: shape.order_ledger,
1995        movable_claims: alloc::vec![
1996            MovableOrderClaim {
1997                transaction_order: positions.active_terminal_order,
1998                owner: OrderDirectOwner::ActiveBindingTerminal(terminal),
1999            },
2000            MovableOrderClaim {
2001                transaction_order: positions.exit_order,
2002                owner: OrderDirectOwner::MembershipExit {
2003                    participant_index: 0,
2004                },
2005            },
2006        ],
2007        immutable_candidates: Vec::new(),
2008        recovery: None,
2009    }
2010}
2011
2012fn build_initial_enrollment_frontiers(
2013    shape: &InitialEnrollmentFrontierShape,
2014) -> Result<ClaimFrontiers, InitialEnrollmentFrontierError> {
2015    let positions = initial_frontier_positions(shape.attached)?;
2016    let terminal = BindingTerminalOwner {
2017        participant_index: 0,
2018        binding_epoch: shape.binding_epoch,
2019    };
2020    let sequence = initial_sequence_frontier(shape, positions, terminal);
2021    let order = initial_order_frontier(shape, positions, terminal);
2022    validate_cross_counter(&sequence, &order)
2023        .map_err(|_| InitialEnrollmentFrontierError::FrontierInvariant)?;
2024    Ok(ClaimFrontiers {
2025        ordinary_cursor_release: None,
2026        // An enrollment has delivered no markers yet.
2027        historical_marker_deliveries: alloc::vec::Vec::new(),
2028        // Nor has it compacted any terminal or exit.
2029        historical_causal_authorities: alloc::vec::Vec::new(),
2030        conversation_id: shape.conversation_id,
2031        active_identities: ActiveIdentityRanks {
2032            participants: alloc::vec![FrontierParticipant::new(
2033                0,
2034                0,
2035                FrontierBinding::Bound(shape.binding_epoch),
2036            )],
2037        },
2038        identity_slot_limit: shape.identity_slot_limit,
2039        retained_floor: shape.retained_floor,
2040        retained_records: alloc::vec![RetainedCausalRecord {
2041            delivery_seq: shape.attached.delivery_seq(),
2042            admission_order: shape.attached.admission_order(),
2043            kind: RetainedCausalRecordKind::AttachLifecycle {
2044                participant_index: 0,
2045                binding_epoch: shape.binding_epoch,
2046            },
2047        }],
2048        marker_records: Vec::new(),
2049        fenced_marker_issued: None,
2050        sequence,
2051        order,
2052    })
2053}
2054
2055#[derive(Clone, Copy)]
2056enum LeaveSequenceUnit {
2057    Direct(MovableSequenceClaim),
2058    TerminalProduct(TerminalProductRange),
2059    ReplacementProduct(ReplacementTerminalProductRange),
2060    ExitProduct(ExitProductRange),
2061    Recovery(RecoverySequenceBlock),
2062}
2063
2064impl LeaveSequenceUnit {
2065    fn original_start(self) -> DeliverySeq {
2066        match self {
2067            Self::Direct(claim) => claim.delivery_seq,
2068            Self::TerminalProduct(range) => range.start,
2069            Self::ReplacementProduct(range) => range.start,
2070            Self::ExitProduct(range) => range.start,
2071            Self::Recovery(block) => block_start_validated_sequence(block),
2072        }
2073    }
2074}
2075
2076fn allocate_leave_sequence_range(
2077    cursor: &mut Option<DeliverySeq>,
2078    length: u64,
2079) -> Result<DeliverySeq, LeaveCommitError> {
2080    let Some(start) = *cursor else {
2081        return Err(LeaveCommitError::ResultingFrontier);
2082    };
2083    let end = u128::from(start)
2084        .checked_add(u128::from(length))
2085        .and_then(|value| value.checked_sub(1))
2086        .ok_or(LeaveCommitError::ResultingFrontier)?;
2087    if end > u128::from(u64::MAX) {
2088        return Err(LeaveCommitError::ResultingFrontier);
2089    }
2090    *cursor = u64::try_from(end + 1).ok();
2091    Ok(start)
2092}
2093
2094/// Which of amendment A5's clearing conditions raised a `Precedence` refusal.
2095///
2096/// Participant contract §0.16 rules ONE seam (`apply_live_transition`) into
2097/// THREE clearing conditions with no shared retry story, and rules a different
2098/// lawful answer for each. The bare `Precedence` variant destroyed that
2099/// discriminant, which is why all three wrappers flattened it into the same
2100/// bare connection close. The condition is therefore computed AT the seam,
2101/// from the exact state the seam's own guard reads, and travels out with the
2102/// refusal — no caller re-derives it and no caller enumerates call sites.
2103///
2104/// [`Self::Unclassified`] exists so the type can never lie: a `Precedence`
2105/// raised somewhere that is NOT one of the amendment's three conditions says
2106/// so, and the server keeps the pre-amendment bare close for it.
2107#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2108pub enum PrecedenceCondition {
2109    /// Condition 1 — a pending binding terminal holds the immutable lane. The
2110    /// blocked resource is hard-observer progress and the wake is the already
2111    /// pushed `0x0200 ObserverProgressed`.
2112    BindingTerminal,
2113    /// Condition 2 — a compaction-marker candidate is awaiting its drain.
2114    MarkerDrain {
2115        /// Delivery sequence of the marker candidate that must drain NEXT.
2116        ///
2117        /// This is the settlement epoch the refusal waits on. It is the head of
2118        /// the immutable sequence lane, which is exactly the candidate
2119        /// `drain_next_marker_core` consumes (it takes `first()` and refuses a
2120        /// binding terminal there), so the epoch a refusal carries is the epoch
2121        /// the clearing write announces.
2122        settlement_epoch: DeliverySeq,
2123    },
2124    /// Condition 3 — an armed fenced-recovery block. Excluded by the board #13
2125    /// constructor census: no production constructor of
2126    /// `StoredAttachModeV3::Fenced` exists, so this arm is unreachable in
2127    /// production and gets no wire row. The first production constructor VOIDS
2128    /// that exclusion.
2129    FencedRecovery,
2130    /// Not one of §0.16's three conditions: a `Precedence` raised over a
2131    /// retained marker below a measured floor, or with an order-lane candidate
2132    /// the sequence lane cannot name. Never presented as a settlement.
2133    Unclassified,
2134}
2135
2136/// Protocol-internal failure while deriving a live frontier from a typed lifecycle commit.
2137#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2138pub(in crate::lifecycle) enum LiveFrontierTransitionError {
2139    /// The typed commit names another conversation or participant history.
2140    Authority,
2141    /// An immutable candidate or recovery interval must be handled by its dedicated transition.
2142    ///
2143    /// Carries WHICH of amendment A5's clearing conditions blocked, computed at
2144    /// the raise site from the state the guard itself read.
2145    Precedence(PrecedenceCondition),
2146    /// The commit's retained rows do not immediately follow the current durable high watermark.
2147    RecordPosition,
2148    /// Checked claim relocation exceeded the fixed-width sequence or order domain.
2149    Exhausted,
2150    /// Derived exact owners disagree with the protocol-produced aggregate ledgers.
2151    ResultingFrontier,
2152}
2153
2154/// Boxes one classified `Precedence` refusal with its unchanged frontiers.
2155fn precedence(
2156    frontiers: ClaimFrontiers,
2157    condition: PrecedenceCondition,
2158) -> Box<(ClaimFrontiers, LiveFrontierTransitionError)> {
2159    Box::new((
2160        frontiers,
2161        LiveFrontierTransitionError::Precedence(condition),
2162    ))
2163}
2164
2165fn select_retained_marker_records(records: &[RetainedCausalRecord]) -> Vec<RetainedCausalRecord> {
2166    records
2167        .iter()
2168        .copied()
2169        .filter(|record| {
2170            matches!(
2171                record.kind,
2172                RetainedCausalRecordKind::CompactionMarker { .. }
2173            )
2174        })
2175        .collect()
2176}
2177
2178impl ClaimFrontiers {
2179    pub(super) fn retain_ordinary_cursor_release(
2180        &mut self,
2181        source: Box<super::storage::OrdinaryBindingFateRestore>,
2182    ) {
2183        self.ordinary_cursor_release = Some(source);
2184    }
2185
2186    pub(super) fn ordinary_cursor_release(
2187        &self,
2188    ) -> Option<&super::storage::OrdinaryBindingFateRestore> {
2189        self.ordinary_cursor_release.as_deref()
2190    }
2191
2192    /// Returns the marker deliveries this conversation has durably made.
2193    ///
2194    /// A frontier that cannot say which deliveries it has already made cannot
2195    /// be described back into the capsule that carries them, so the restore's
2196    /// validated authorities are kept rather than consumed and dropped.
2197    pub(super) fn historical_marker_deliveries(&self) -> &[HistoricalMarkerDeliveryAuthority] {
2198        &self.historical_marker_deliveries
2199    }
2200
2201    /// Describes this live frontier as the exact restore input, ledgers, and
2202    /// issuance latch that restoring reproduces it from, field by field.
2203    ///
2204    /// `retained_record_limit` is supplied by the caller because the frontier
2205    /// does not hold it: the live owner keeps the signed cap and the restore
2206    /// only checks the retained corpus against it.
2207    ///
2208    /// Every derived value is recovered from the field restore derived it
2209    /// from: active marker anchors from the retained marker records, the
2210    /// recovery selector from the sequence recovery block, and the sealed
2211    /// histories from their raw facts. Recovery provenance itself is not
2212    /// described; restore re-derives it from the stored edge.
2213    #[must_use]
2214    pub(in crate::lifecycle) fn describe(
2215        &self,
2216        retained_record_limit: u64,
2217    ) -> ClaimFrontiersDescription {
2218        let conversation_id = self.conversation_id;
2219        ClaimFrontiersDescription {
2220            restore: ClaimFrontiersRestore {
2221                conversation_id,
2222                active_identities: self.active_identities.participants().to_vec(),
2223                identity_slot_limit: self.identity_slot_limit,
2224                retained_floor: self.retained_floor,
2225                retained_record_limit,
2226                retained_records: self.retained_records.clone(),
2227                active_marker_anchors: self
2228                    .marker_records
2229                    .iter()
2230                    .map(|record| record.delivery_seq)
2231                    .collect(),
2232                historical_marker_deliveries: self
2233                    .historical_marker_deliveries()
2234                    .iter()
2235                    .map(|authority| HistoricalMarkerDeliveryFactRestore {
2236                        conversation_id,
2237                        participant_index: authority.participant_index(),
2238                        marker_delivery_seq: authority.marker_delivery_seq(),
2239                        delivered_binding_epoch: authority.delivered_binding_epoch(),
2240                    })
2241                    .collect(),
2242                historical_causal_facts: self
2243                    .historical_causal_authorities
2244                    .iter()
2245                    .copied()
2246                    .map(HistoricalCausalAuthority::describe)
2247                    .collect(),
2248                sequence: describe_sequence_frontier(&self.sequence),
2249                order: describe_order_frontier(&self.order),
2250                recovery_marker_delivery_seq: self
2251                    .sequence
2252                    .recovery
2253                    .map(RecoverySequenceBlock::marker_delivery_seq),
2254            },
2255            sequence_ledger: self.sequence.ledger,
2256            order_ledger: self.order.ledger,
2257            fenced_marker_issued: self.fenced_marker_issued,
2258        }
2259    }
2260
2261    /// Takes a [`ClaimFrontiersDescription`] back through the same validating
2262    /// route every restore uses.
2263    ///
2264    /// The description's restore input and ledgers are prevalidated against
2265    /// `history` exactly as [`Self::prevalidate_with_history`] does; the caller
2266    /// then takes at most one retained-marker token, rebuilds its typed edge,
2267    /// and calls `finish`, as the joint conversation restore does. Restoring
2268    /// claim frontiers alone cannot validate compacted causal history or a
2269    /// marker-backed edge, so this entry deliberately stops before `finish`.
2270    ///
2271    /// The issuance latch is carried here and installed by `finish`, but it is
2272    /// not trusted: the description may come from capsule bytes decoded by the
2273    /// public [`super::LifecycleCapsule::decode_canonical`]. The joint restore
2274    /// checks the finished frontier's latch with
2275    /// [`ClaimFrontiers::fenced_latch_matches`] against the restored marker
2276    /// records, participant binding and closure edge, and refuses a latch no
2277    /// live take could have set before any state is returned.
2278    ///
2279    /// # Errors
2280    ///
2281    /// Returns the first fault [`Self::prevalidate_with_history`] reports.
2282    pub(in crate::lifecycle) fn prevalidate_description(
2283        description: ClaimFrontiersDescription,
2284        history: &ValidatedConversationHistory,
2285    ) -> Result<ClaimFrontiersPrevalidated, ParticipantStateCorruptReason> {
2286        let ClaimFrontiersDescription {
2287            restore,
2288            sequence_ledger,
2289            order_ledger,
2290            fenced_marker_issued,
2291        } = description;
2292        let mut prevalidated =
2293            Self::prevalidate_with_history(restore, sequence_ledger, order_ledger, history)?;
2294        prevalidated.fenced_marker_issued = fenced_marker_issued;
2295        Ok(prevalidated)
2296    }
2297
2298    /// Constructs the complete initial frontier directly from one admitted
2299    /// enrollment operation and its exact encoded `Attached` charge.
2300    ///
2301    /// No restore representation, row list, claim list, or numeric position is
2302    /// accepted from the caller. Participant zero, the retained lifecycle row,
2303    /// `A`/`X`, `T`/`E`, and `L x T` owners are derived solely from the opaque
2304    /// operation commit after its closure projection and aggregate ledgers are
2305    /// cross-checked.
2306    ///
2307    /// # Errors
2308    ///
2309    /// Returns [`InitialEnrollmentFrontierError`] when the supplied charge or
2310    /// any typed operation/projection invariant disagrees with the canonical
2311    /// initial frontier.
2312    pub fn from_initial_enrollment<F>(
2313        operation: InitialEnrollmentOperationCommit<F>,
2314        attached_charge: ResourceVector,
2315    ) -> Result<InitialEnrollmentFrontierCommit<F>, Box<InitialEnrollmentFrontierFailure<F>>> {
2316        let shape = match initial_enrollment_frontier_shape(&operation, attached_charge) {
2317            Ok(shape) => shape,
2318            Err(error) => {
2319                return Err(Box::new(InitialEnrollmentFrontierFailure {
2320                    operation,
2321                    error,
2322                }));
2323            }
2324        };
2325        let closure_accounting = shape.closure_accounting;
2326        let frontiers = match build_initial_enrollment_frontiers(&shape) {
2327            Ok(frontiers) => frontiers,
2328            Err(error) => {
2329                return Err(Box::new(InitialEnrollmentFrontierFailure {
2330                    operation,
2331                    error,
2332                }));
2333            }
2334        };
2335        Ok(InitialEnrollmentFrontierCommit {
2336            operation,
2337            frontiers,
2338            closure_accounting,
2339            attached_charge,
2340        })
2341    }
2342
2343    /// Restores exact frontiers only when their numeric unions, logical owners,
2344    /// product descriptors, DCR intervals, candidate keys, and aggregate ledgers
2345    /// all agree.
2346    ///
2347    /// This standalone form accepts no compacted causal-history rows or binding
2348    /// origins. Snapshots containing either must use the protocol-owned event
2349    /// replay path so participant-owned history is restored first.
2350    ///
2351    /// # Errors
2352    ///
2353    /// Returns the deterministic first delivery-sequence fault before checking
2354    /// transaction order, then checks cross-counter candidate and DCR identity.
2355    pub fn restore(
2356        restore: ClaimFrontiersRestore,
2357        sequence_ledger: SequenceLedger,
2358        order_ledger: OrderLedger,
2359    ) -> Result<Self, ParticipantStateCorruptReason> {
2360        let history = ValidatedConversationHistory::empty();
2361        Self::prevalidate_with_history(restore, sequence_ledger, order_ledger, &history)?
2362            .finish(None)
2363    }
2364
2365    /// Validates numeric frontiers and durable marker history before a stored
2366    /// edge is reconstructed from the resulting sealed authority.
2367    #[cfg(any(test, feature = "test-support"))]
2368    pub(super) fn prevalidate(
2369        restore: ClaimFrontiersRestore,
2370        sequence_ledger: SequenceLedger,
2371        order_ledger: OrderLedger,
2372    ) -> Result<ClaimFrontiersPrevalidated, ParticipantStateCorruptReason> {
2373        let history = ValidatedConversationHistory::empty();
2374        Self::prevalidate_with_history(restore, sequence_ledger, order_ledger, &history)
2375    }
2376
2377    pub(super) fn prevalidate_with_history(
2378        restore: ClaimFrontiersRestore,
2379        sequence_ledger: SequenceLedger,
2380        order_ledger: OrderLedger,
2381        history: &ValidatedConversationHistory,
2382    ) -> Result<ClaimFrontiersPrevalidated, ParticipantStateCorruptReason> {
2383        validate_sequence_numeric(&restore.sequence, sequence_ledger).map_err(corrupt_frontier)?;
2384        validate_order_numeric(&restore.order, order_ledger).map_err(corrupt_frontier)?;
2385        validate_unique_candidate_keys(
2386            &restore.sequence.immutable_candidates,
2387            &restore.retained_records,
2388        )?;
2389        validate_bounded_shape(&restore, sequence_ledger).map_err(corrupt_frontier)?;
2390        let active_identities = ActiveIdentityRanks::try_new(
2391            restore.active_identities,
2392            sequence_ledger.high_watermark(),
2393            restore.identity_slot_limit,
2394        )
2395        .map_err(corrupt_frontier)?;
2396        let retained_records = validated_retained_records(
2397            restore.retained_records,
2398            restore.retained_floor,
2399            restore.retained_record_limit,
2400            restore.identity_slot_limit,
2401            sequence_ledger,
2402        )
2403        .map_err(corrupt_frontier)?;
2404        let historical_causal_authorities = validated_historical_authorities(
2405            restore.historical_causal_facts,
2406            restore.conversation_id,
2407            restore.identity_slot_limit,
2408            sequence_ledger,
2409            history,
2410        )
2411        .map_err(corrupt_frontier)?;
2412        let retained_marker_records = select_retained_marker_records(&retained_records);
2413        let marker_records = validated_active_marker_records(
2414            &retained_marker_records,
2415            restore.active_marker_anchors,
2416            restore.identity_slot_limit,
2417            sequence_ledger,
2418        )
2419        .map_err(corrupt_frontier)?;
2420        let historical_marker_deliveries = validated_historical_marker_deliveries(
2421            restore.historical_marker_deliveries,
2422            restore.conversation_id,
2423            &active_identities,
2424            &MarkerDeliverySources {
2425                retained: &retained_records,
2426                historical: &historical_causal_authorities,
2427                candidates: &restore.sequence.immutable_candidates,
2428            },
2429            restore.retained_record_limit,
2430            sequence_ledger,
2431        )
2432        .map_err(corrupt_frontier)?;
2433        BindingOriginValidation {
2434            conversation_id: restore.conversation_id,
2435            active: &active_identities,
2436            origins: &history.binding_origins,
2437            retained_records: &retained_records,
2438            causal_authorities: &history.causal_authorities,
2439            historical_marker_deliveries: &historical_marker_deliveries,
2440            total: history.total,
2441            ledger: sequence_ledger,
2442        }
2443        .validate()
2444        .map_err(corrupt_frontier)?;
2445        validate_sequence_candidates(
2446            &active_identities,
2447            &restore.sequence.immutable_candidates,
2448            restore.retained_floor,
2449            &retained_records,
2450            &historical_causal_authorities,
2451            sequence_ledger,
2452        )
2453        .map_err(corrupt_frontier)?;
2454        validate_marker_credit_owners(
2455            &restore.sequence.immutable_candidates,
2456            &marker_records,
2457            restore.identity_slot_limit,
2458            sequence_ledger,
2459        )
2460        .map_err(corrupt_frontier)?;
2461        Ok(ClaimFrontiersPrevalidated {
2462            conversation_id: restore.conversation_id,
2463            active_identities,
2464            identity_slot_limit: restore.identity_slot_limit,
2465            retained_floor: restore.retained_floor,
2466            retained_records,
2467            marker_records,
2468            historical_marker_deliveries,
2469            historical_causal_authorities,
2470            binding_origins: history.binding_origins.clone(),
2471            sequence_restore: restore.sequence,
2472            order_restore: restore.order,
2473            recovery_marker_delivery_seq: restore.recovery_marker_delivery_seq,
2474            sequence_ledger,
2475            order_ledger,
2476            issued_marker_record: None,
2477            fenced_marker_issued: None,
2478        })
2479    }
2480
2481    /// Borrows sorted current live identities.
2482    #[must_use]
2483    pub const fn active_identities(&self) -> &ActiveIdentityRanks {
2484        &self.active_identities
2485    }
2486
2487    /// Returns the signed permanent identity-slot capacity validated at restore.
2488    #[must_use]
2489    pub const fn identity_slot_limit(&self) -> u64 {
2490        self.identity_slot_limit
2491    }
2492
2493    /// Returns the owning conversation.
2494    #[must_use]
2495    pub const fn conversation_id(&self) -> ConversationId {
2496        self.conversation_id
2497    }
2498
2499    /// Returns the current physical retained suffix floor.
2500    #[must_use]
2501    pub const fn retained_floor(&self) -> u128 {
2502        self.retained_floor
2503    }
2504
2505    /// Borrows every validated physical row in the retained sequence suffix.
2506    #[must_use]
2507    pub fn retained_records(&self) -> &[RetainedCausalRecord] {
2508        &self.retained_records
2509    }
2510
2511    pub(super) fn checkpoint_recovered_marker_record(
2512        &self,
2513        origin: BindingOrigin,
2514    ) -> Option<ValidatedMarkerRecord> {
2515        let (marker_delivery_seq, prior_binding_epoch) = origin.recovered_marker()?;
2516        if origin.conversation_id() != self.conversation_id
2517            || !self.historical_marker_deliveries.iter().any(|delivery| {
2518                delivery.participant_index == origin.participant_id()
2519                    && delivery.marker_delivery_seq == marker_delivery_seq
2520                    && delivery.delivered_binding_epoch == prior_binding_epoch
2521            })
2522        {
2523            return None;
2524        }
2525        let record = self
2526            .retained_records
2527            .iter()
2528            .find(|record| record.delivery_seq == marker_delivery_seq)
2529            .copied()?;
2530        let RetainedCausalRecordKind::CompactionMarker {
2531            participant_index,
2532            provenance,
2533        } = record.kind
2534        else {
2535            return None;
2536        };
2537        if participant_index != origin.participant_id() {
2538            return None;
2539        }
2540        Some(ValidatedMarkerRecord {
2541            conversation_id: self.conversation_id,
2542            record,
2543            provenance,
2544            target_binding: FrontierBinding::Detached(prior_binding_epoch),
2545            occurrence: MarkerRecordOccurrence::Delivered,
2546            seal: MarkerAuthoritySeal::Validated,
2547        })
2548    }
2549
2550    /// Borrows only the O(I) retained marker anchors needed by executable edges.
2551    #[must_use]
2552    pub fn retained_marker_records(&self) -> &[RetainedCausalRecord] {
2553        &self.marker_records
2554    }
2555
2556    /// Counts the marker anchors the ordinary-ack projection derives as
2557    /// unaccepted — the same census the admission cross-check compares against
2558    /// stored closure accounting. Exposed so load-time reconciliation can
2559    /// measure the derived side of that ledger without re-running a projection.
2560    #[must_use]
2561    pub(in crate::lifecycle) fn unaccepted_marker_anchor_count(&self) -> u64 {
2562        ordinary_unaccepted_marker_anchors(self).len() as u64
2563    }
2564
2565    /// Counts planned compaction markers still pending as immutable sequence
2566    /// candidates. A planned marker already holds its stored anchor while its
2567    /// retained record exists only after the drain, so the record census alone
2568    /// under-derives exactly while a drain is pending — the `DrainFirst`
2569    /// discipline keeps the admission projection out of that window, and
2570    /// load-time reconciliation must stay out of it the same way.
2571    #[must_use]
2572    pub(in crate::lifecycle) fn pending_marker_candidate_count(&self) -> u64 {
2573        self.sequence
2574            .immutable_candidates
2575            .iter()
2576            .filter(|candidate| matches!(candidate, ImmutableSequenceCandidate::Marker(_)))
2577            .count() as u64
2578    }
2579
2580    /// Projects exact offered-marker cursor progress from one validated retained
2581    /// marker and its current bound identity.
2582    ///
2583    /// Raw participant, epoch, and sequence inputs grant no authority: all must
2584    /// match the coupled frontier's retained marker anchor and active binding.
2585    /// The returned progress is derived only through sealed [`MarkerDelivery`]
2586    /// authority and the exact delivered event.
2587    #[must_use]
2588    pub fn project_offered_marker_progress(
2589        &self,
2590        participant_id: ParticipantId,
2591        binding_epoch: BindingEpoch,
2592        marker_delivery_seq: DeliverySeq,
2593        event: Event,
2594    ) -> Option<ParticipantCursorProgress> {
2595        let record = self
2596            .marker_records
2597            .iter()
2598            .find(|record| record.delivery_seq == marker_delivery_seq)
2599            .copied()?;
2600        let RetainedCausalRecordKind::CompactionMarker {
2601            participant_index,
2602            provenance,
2603        } = record.kind
2604        else {
2605            return None;
2606        };
2607        if participant_index != participant_id {
2608            return None;
2609        }
2610        let participant = active_participant(&self.active_identities, participant_id)?;
2611        let target_binding = FrontierBinding::Bound(binding_epoch);
2612        if participant.binding != target_binding {
2613            return None;
2614        }
2615        let authority = ValidatedMarkerRecord {
2616            conversation_id: self.conversation_id,
2617            record,
2618            provenance,
2619            target_binding,
2620            occurrence: MarkerRecordOccurrence::Undelivered,
2621            seal: MarkerAuthoritySeal::Validated,
2622        };
2623        MarkerDelivery::from_validated_record(&authority)
2624            .delivered_progress(event)
2625            .ok()
2626    }
2627
2628    /// Recomputes the exact delivered marker record that a fenced recovery may
2629    /// name without constructing its one-use authority.
2630    pub(super) fn fenced_marker_source(
2631        &self,
2632        recovery: super::DetachedCredentialRecovery,
2633    ) -> Option<FencedMarkerSourceRecord> {
2634        if self.fenced_marker_issued.is_some() {
2635            return None;
2636        }
2637        self.unlatched_fenced_marker_source(recovery)
2638    }
2639
2640    /// Reports whether the fenced-marker issuance latch is one this restored
2641    /// state can hold: `None`, or exactly the take the current state would
2642    /// grant were the latch absent.
2643    ///
2644    /// A `Some((participant, marker, epoch))` is consistent only when the
2645    /// current closure edge is the detached credential recovery of that
2646    /// participant, marker and prior epoch, and that recovery's retained marker
2647    /// occurrence is still active for that participant while it stands
2648    /// detached on that epoch — the state `take_fenced_marker_record` requires
2649    /// and leaves unchanged. Anything else is a latch no live take produced: a
2650    /// foreign one would refuse a legitimate first take.
2651    ///
2652    /// A `None` passes unchecked because nothing can check it: a take writes
2653    /// only the latch, so a latched state and the same state unlatched differ
2654    /// in nothing else, and a committed fenced attach clears the latch it
2655    /// spent.
2656    #[must_use]
2657    pub(in crate::lifecycle) fn fenced_latch_matches(
2658        &self,
2659        edge: Option<super::StoredEdge>,
2660    ) -> bool {
2661        let Some((participant_id, marker_delivery_seq, prior_binding_epoch)) =
2662            self.fenced_marker_issued
2663        else {
2664            return true;
2665        };
2666        let Some(super::StoredEdge::DetachedCredentialRecovery(recovery)) = edge else {
2667            return false;
2668        };
2669        recovery.participant_id() == participant_id
2670            && recovery.marker_delivery_seq() == marker_delivery_seq
2671            && recovery.prior_binding_epoch() == prior_binding_epoch
2672            && self.unlatched_fenced_marker_source(recovery).is_some()
2673    }
2674
2675    /// Recomputes the delivered marker record a fenced recovery names, as
2676    /// [`Self::fenced_marker_source`] does, without consulting the latch.
2677    fn unlatched_fenced_marker_source(
2678        &self,
2679        recovery: super::DetachedCredentialRecovery,
2680    ) -> Option<FencedMarkerSourceRecord> {
2681        if recovery.conversation_id() != self.conversation_id {
2682            return None;
2683        }
2684        let marker_delivery_seq = recovery.marker_delivery_seq();
2685        let participant_id = recovery.participant_id();
2686        let prior_binding_epoch = recovery.prior_binding_epoch();
2687        let record = self
2688            .marker_records
2689            .iter()
2690            .find(|record| record.delivery_seq == marker_delivery_seq)
2691            .copied()?;
2692        let RetainedCausalRecordKind::CompactionMarker {
2693            participant_index,
2694            provenance,
2695        } = record.kind
2696        else {
2697            return None;
2698        };
2699        let participant = active_participant(&self.active_identities, participant_id)?;
2700        let target_binding = FrontierBinding::Detached(prior_binding_epoch);
2701        if participant_index != participant_id || participant.binding != target_binding {
2702            return None;
2703        }
2704        Some(FencedMarkerSourceRecord {
2705            conversation_id: self.conversation_id,
2706            delivery_seq: record.delivery_seq,
2707            admission_order: record.admission_order,
2708            participant_id,
2709            provenance,
2710            target_binding,
2711        })
2712    }
2713
2714    /// Removes the sole retained-marker occurrence authority for one fenced
2715    /// attach proof mint.
2716    ///
2717    /// The frontiers have already passed complete numeric, causal, participant,
2718    /// and retained-row validation. This final gate binds that validation to the
2719    /// exact detached recovery description and records issuance before returning
2720    /// the non-cloneable token. A second take is refused until the exact token is
2721    /// reinstalled after a failed private mint, or until the fenced attach that
2722    /// spends it (or the participant's Leave) commits and clears the latch.
2723    /// Issuance writes only the latch; no other frontier field changes.
2724    pub(in crate::lifecycle) fn take_fenced_marker_record(
2725        &mut self,
2726        recovery: super::DetachedCredentialRecovery,
2727    ) -> Option<ValidatedMarkerRecord> {
2728        let source = self.fenced_marker_source(recovery)?;
2729        self.fenced_marker_issued = Some((
2730            source.participant_id,
2731            source.delivery_seq,
2732            recovery.prior_binding_epoch(),
2733        ));
2734        Some(ValidatedMarkerRecord {
2735            conversation_id: source.conversation_id,
2736            record: RetainedCausalRecord {
2737                delivery_seq: source.delivery_seq,
2738                admission_order: source.admission_order,
2739                kind: RetainedCausalRecordKind::CompactionMarker {
2740                    participant_index: source.participant_id,
2741                    provenance: source.provenance,
2742                },
2743            },
2744            provenance: source.provenance,
2745            target_binding: source.target_binding,
2746            occurrence: MarkerRecordOccurrence::Delivered,
2747            seal: MarkerAuthoritySeal::Validated,
2748        })
2749    }
2750
2751    /// Reinstalls the exact occurrence authority after the private proof mint
2752    /// refused without producing a proof.
2753    pub(in crate::lifecycle) const fn reinstall_fenced_marker_record(
2754        &mut self,
2755        record: ValidatedMarkerRecord,
2756    ) {
2757        self.fenced_marker_issued = None;
2758        record.consume();
2759    }
2760
2761    /// Borrows the validated sequence frontier.
2762    #[must_use]
2763    pub const fn sequence(&self) -> &SequenceClaimFrontier {
2764        &self.sequence
2765    }
2766
2767    /// Borrows the validated transaction-order frontier.
2768    #[must_use]
2769    pub const fn order(&self) -> &OrderClaimFrontier {
2770        &self.order
2771    }
2772
2773    #[cfg(test)]
2774    pub(in crate::lifecycle) fn cross_counter_valid_for_test(&self) -> bool {
2775        validate_cross_counter(&self.sequence, &self.order).is_ok()
2776    }
2777
2778    /// Reports whether an immutable candidate or armed recovery currently has
2779    /// precedence over an ordinary live transition.
2780    ///
2781    /// This IS the guard `apply_live_transition` and
2782    /// `apply_pending_binding_terminal` run — they call it rather than
2783    /// restating the predicate, so the classifier below can never disagree with
2784    /// the condition that fired.
2785    #[must_use]
2786    pub fn live_transition_blocked(&self) -> bool {
2787        !self.sequence.immutable_candidates.is_empty()
2788            || self.sequence.recovery.is_some()
2789            || !self.order.immutable_candidates.is_empty()
2790            || self.order.recovery.is_some()
2791    }
2792
2793    /// Classifies the state that blocks a live transition into one of amendment
2794    /// A5's clearing conditions (participant contract §0.16).
2795    ///
2796    /// Ordering is a ruling, not an accident. An armed recovery outranks
2797    /// everything: it is the census-excluded arm and must never be dressed as a
2798    /// settlement. Otherwise the answer is the HEAD of the immutable sequence
2799    /// lane — the next thing that must clear — because that is the exact
2800    /// candidate the drain consumes and therefore the only one whose clearing
2801    /// the server can honestly promise to announce. A head binding terminal
2802    /// standing in front of a marker candidate is answered as condition 1, and
2803    /// the retry that follows its `ObserverProgressed` then hears condition 2:
2804    /// each refusal names the live blocker rather than a later one.
2805    ///
2806    /// An order-lane candidate with an empty sequence lane cannot be named (the
2807    /// order lane stores candidate KEYS, not delivery sequences) and is
2808    /// therefore [`PrecedenceCondition::Unclassified`] rather than guessed.
2809    #[must_use]
2810    pub fn precedence_condition(&self) -> PrecedenceCondition {
2811        if self.sequence.recovery.is_some() || self.order.recovery.is_some() {
2812            return PrecedenceCondition::FencedRecovery;
2813        }
2814        match self.sequence.immutable_candidates.first() {
2815            Some(ImmutableSequenceCandidate::Marker(marker)) => PrecedenceCondition::MarkerDrain {
2816                settlement_epoch: marker.delivery_seq,
2817            },
2818            Some(ImmutableSequenceCandidate::BindingTerminal { .. }) => {
2819                PrecedenceCondition::BindingTerminal
2820            }
2821            None => PrecedenceCondition::Unclassified,
2822        }
2823    }
2824
2825    /// Applies one protocol-normalized live lifecycle transition.
2826    ///
2827    /// Only sibling lifecycle operations can call this seam. They derive the
2828    /// identities, rows, and aggregate ledgers from sealed typed commits; no
2829    /// storage or server caller can provide raw frontier components.
2830    pub(in crate::lifecycle) fn apply_live_transition(
2831        self,
2832        active_identities: Vec<FrontierParticipant>,
2833        appended_records: &[RetainedCausalRecord],
2834        sequence_ledger: SequenceLedger,
2835        order_ledger: OrderLedger,
2836    ) -> Result<Self, Box<(Self, LiveFrontierTransitionError)>> {
2837        if self.live_transition_blocked() {
2838            let condition = self.precedence_condition();
2839            return Err(Box::new((
2840                self,
2841                LiveFrontierTransitionError::Precedence(condition),
2842            )));
2843        }
2844        let Ok(active) = ActiveIdentityRanks::try_new(
2845            active_identities,
2846            sequence_ledger.high_watermark(),
2847            self.identity_slot_limit,
2848        ) else {
2849            return Err(Box::new((self, LiveFrontierTransitionError::Authority)));
2850        };
2851        let first_sequence = self.sequence.ledger.high_watermark().checked_add(1);
2852        if first_sequence.is_none_or(|first| {
2853            appended_records.iter().enumerate().any(|(index, record)| {
2854                u64::try_from(index)
2855                    .ok()
2856                    .and_then(|offset| first.checked_add(offset))
2857                    != Some(record.delivery_seq)
2858            })
2859        }) || appended_records
2860            .last()
2861            .is_some_and(|record| record.delivery_seq != sequence_ledger.high_watermark())
2862        {
2863            return Err(Box::new((
2864                self,
2865                LiveFrontierTransitionError::RecordPosition,
2866            )));
2867        }
2868        let (sequence, order) =
2869            match rebuild_unreserved_frontiers(&active, sequence_ledger, order_ledger) {
2870                Ok(frontiers) => frontiers,
2871                Err(error) => return Err(Box::new((self, error))),
2872            };
2873        let Self {
2874            ordinary_cursor_release,
2875            conversation_id,
2876            identity_slot_limit,
2877            retained_floor,
2878            mut retained_records,
2879            marker_records,
2880            fenced_marker_issued,
2881            historical_marker_deliveries,
2882            historical_causal_authorities,
2883            ..
2884        } = self;
2885        retained_records.extend_from_slice(appended_records);
2886        Ok(Self {
2887            ordinary_cursor_release,
2888            conversation_id,
2889            active_identities: active,
2890            identity_slot_limit,
2891            retained_floor,
2892            retained_records,
2893            marker_records,
2894            fenced_marker_issued,
2895            sequence,
2896            order,
2897            historical_marker_deliveries,
2898            historical_causal_authorities,
2899        })
2900    }
2901
2902    /// Ends one exact active binding while retaining its terminal as the first
2903    /// immutable sequence candidate. The transaction-order major is consumed,
2904    /// but the candidate delivery sequence remains unconsumed.
2905    pub(in crate::lifecycle) fn apply_pending_binding_terminal(
2906        mut self,
2907        participant_id: ParticipantId,
2908        binding_epoch: BindingEpoch,
2909        delivery_seq: DeliverySeq,
2910        admission_order: super::AdmissionOrder,
2911        order_ledger: OrderLedger,
2912    ) -> Result<Self, Box<(Self, LiveFrontierTransitionError)>> {
2913        if self.live_transition_blocked() {
2914            let condition = self.precedence_condition();
2915            return Err(Box::new((
2916                self,
2917                LiveFrontierTransitionError::Precedence(condition),
2918            )));
2919        }
2920        let mut participants = self.active_identities.participants().to_vec();
2921        let Some(participant) = participants
2922            .iter_mut()
2923            .find(|participant| participant.participant_index() == participant_id)
2924        else {
2925            return Err(Box::new((self, LiveFrontierTransitionError::Authority)));
2926        };
2927        if participant.binding() != FrontierBinding::Bound(binding_epoch) {
2928            return Err(Box::new((self, LiveFrontierTransitionError::Authority)));
2929        }
2930        *participant = FrontierParticipant::new(
2931            participant_id,
2932            participant.cursor(),
2933            FrontierBinding::Detached(binding_epoch),
2934        );
2935        let Ok(active) = ActiveIdentityRanks::try_new(
2936            participants,
2937            self.sequence.ledger.high_watermark(),
2938            self.identity_slot_limit,
2939        ) else {
2940            return Err(Box::new((self, LiveFrontierTransitionError::Authority)));
2941        };
2942        let pending_owner = BindingTerminalOwner {
2943            participant_index: participant_id,
2944            binding_epoch,
2945        };
2946        let (sequence, order) = match rebuild_pending_terminal_frontiers(
2947            &active,
2948            pending_owner,
2949            delivery_seq,
2950            admission_order,
2951            self.sequence.ledger,
2952            order_ledger,
2953        ) {
2954            Ok(frontiers) => frontiers,
2955            Err(error) => return Err(Box::new((self, error))),
2956        };
2957        self.active_identities = active;
2958        self.sequence = sequence;
2959        self.order = order;
2960        Ok(self)
2961    }
2962
2963    /// Consumes the exact coupled DCR blocks into one fenced attach.
2964    ///
2965    /// This seam is lifecycle-private: its participant and rows are derived from
2966    /// the sealed attach commit, never supplied by storage. Both recovery blocks,
2967    /// the delivered marker, the detached epoch, and every reserved position are
2968    /// checked before `RS`/`RO` are consumed and `RT`/`RA` become the recovered
2969    /// binding's ordinary `T`/`A` claims.
2970    pub(in crate::lifecycle) fn apply_live_fenced_attach(
2971        self,
2972        participant: FrontierParticipant,
2973        prior_binding_epoch: BindingEpoch,
2974        appended_records: &[RetainedCausalRecord],
2975    ) -> Result<Self, Box<(Self, LiveFrontierTransitionError)>> {
2976        let Some(current) = self
2977            .active_identities
2978            .participants()
2979            .iter()
2980            .find(|current| current.participant_index() == participant.participant_index())
2981            .copied()
2982        else {
2983            return Err(Box::new((self, LiveFrontierTransitionError::Authority)));
2984        };
2985        let Some(sequence_recovery) = self.sequence.recovery else {
2986            return Err(precedence(self, PrecedenceCondition::FencedRecovery));
2987        };
2988        let Some(order_recovery) = self.order.recovery else {
2989            return Err(precedence(self, PrecedenceCondition::FencedRecovery));
2990        };
2991        if !Self::fenced_recovery_authority_matches(
2992            current,
2993            participant,
2994            prior_binding_epoch,
2995            sequence_recovery,
2996            order_recovery,
2997        ) {
2998            return Err(Box::new((self, LiveFrontierTransitionError::Authority)));
2999        }
3000        let finalizes_pending = appended_records.len() == 2;
3001        let pending_terminal_matches = self.pending_fenced_terminal_matches(appended_records);
3002        let candidates_empty = self.sequence.immutable_candidates.is_empty()
3003            && self.order.immutable_candidates.is_empty();
3004        if !(candidates_empty || finalizes_pending && pending_terminal_matches) {
3005            let condition = self.precedence_condition();
3006            return Err(precedence(self, condition));
3007        }
3008        if !Self::fenced_records_match(
3009            participant,
3010            sequence_recovery,
3011            order_recovery,
3012            appended_records,
3013            pending_terminal_matches,
3014        ) {
3015            return Err(Box::new((
3016                self,
3017                LiveFrontierTransitionError::RecordPosition,
3018            )));
3019        }
3020        if !self.has_recovery_marker(participant) {
3021            return Err(Box::new((self, LiveFrontierTransitionError::Authority)));
3022        }
3023        if self.has_duplicate_appended_record(appended_records) {
3024            return Err(Box::new((
3025                self,
3026                LiveFrontierTransitionError::RecordPosition,
3027            )));
3028        }
3029        let Some((sequence_ledger, order_ledger)) =
3030            apply_fenced_ledgers(self.sequence.ledger, self.order.ledger, finalizes_pending)
3031        else {
3032            return Err(Box::new((
3033                self,
3034                LiveFrontierTransitionError::ResultingFrontier,
3035            )));
3036        };
3037        let mut active = self.active_identities.participants().to_vec();
3038        let Some(current) = active
3039            .iter_mut()
3040            .find(|current| current.participant_index() == participant.participant_index())
3041        else {
3042            return Err(Box::new((self, LiveFrontierTransitionError::Authority)));
3043        };
3044        *current = participant;
3045        let Ok(active) = ActiveIdentityRanks::try_new(
3046            active,
3047            sequence_ledger.high_watermark(),
3048            self.identity_slot_limit,
3049        ) else {
3050            return Err(Box::new((self, LiveFrontierTransitionError::Authority)));
3051        };
3052        let Ok((sequence, order)) =
3053            rebuild_unreserved_frontiers(&active, sequence_ledger, order_ledger)
3054        else {
3055            return Err(Box::new((
3056                self,
3057                LiveFrontierTransitionError::ResultingFrontier,
3058            )));
3059        };
3060        let mut resulting = self;
3061        resulting.active_identities = active;
3062        resulting
3063            .retained_records
3064            .extend_from_slice(appended_records);
3065        resulting
3066            .retained_records
3067            .sort_unstable_by_key(|record| record.delivery_seq);
3068        resulting
3069            .marker_records
3070            .retain(|record| record.delivery_seq != participant.cursor());
3071        resulting.spend_fenced_latch(participant, prior_binding_epoch);
3072        resulting.sequence = sequence;
3073        resulting.order = order;
3074        Ok(resulting)
3075    }
3076
3077    /// Clears the fenced latch when it names the take a committed fenced
3078    /// attach just spent: its marker occurrence is gone and the participant is
3079    /// bound, so the latch has nothing left to refuse. Clearing it keeps every
3080    /// latch a restore can be handed checkable against the marker it names.
3081    fn spend_fenced_latch(
3082        &mut self,
3083        participant: FrontierParticipant,
3084        prior_binding_epoch: BindingEpoch,
3085    ) {
3086        if self.fenced_marker_issued
3087            == Some((
3088                participant.participant_index(),
3089                participant.cursor(),
3090                prior_binding_epoch,
3091            ))
3092        {
3093            self.fenced_marker_issued = None;
3094        }
3095    }
3096
3097    fn fenced_recovery_authority_matches(
3098        current: FrontierParticipant,
3099        participant: FrontierParticipant,
3100        prior_binding_epoch: BindingEpoch,
3101        sequence_recovery: RecoverySequenceBlock,
3102        order_recovery: RecoveryOrderBlock,
3103    ) -> bool {
3104        let participant_matches = sequence_recovery.participant_index()
3105            == participant.participant_index()
3106            && order_recovery.participant_index() == participant.participant_index();
3107        let marker_matches = sequence_recovery.marker_delivery_seq() == participant.cursor()
3108            && order_recovery.marker_delivery_seq() == participant.cursor();
3109        let prior_epoch_matches = sequence_recovery.recovered_binding_epoch()
3110            == prior_binding_epoch
3111            && order_recovery.recovered_binding_epoch() == prior_binding_epoch;
3112        participant_matches
3113            && marker_matches
3114            && prior_epoch_matches
3115            && current.binding() == FrontierBinding::Detached(prior_binding_epoch)
3116            && current.cursor() <= participant.cursor()
3117            && matches!(participant.binding(), FrontierBinding::Bound(_))
3118    }
3119
3120    fn fenced_records_match(
3121        participant: FrontierParticipant,
3122        sequence_recovery: RecoverySequenceBlock,
3123        order_recovery: RecoveryOrderBlock,
3124        appended_records: &[RetainedCausalRecord],
3125        pending_terminal_matches: bool,
3126    ) -> bool {
3127        let Some(attached) = appended_records.last().copied() else {
3128            return false;
3129        };
3130        let FrontierBinding::Bound(recovered_binding_epoch) = participant.binding() else {
3131            return false;
3132        };
3133        let attached_matches = attached.delivery_seq == sequence_recovery.recovery_attach_seq()
3134            && attached.admission_order.transaction_order()
3135                == order_recovery.recovery_operation_order()
3136            && attached.kind
3137                == (RetainedCausalRecordKind::AttachLifecycle {
3138                    participant_index: participant.participant_index(),
3139                    binding_epoch: recovered_binding_epoch,
3140                });
3141        if !attached_matches {
3142            return false;
3143        }
3144        let prefix = &appended_records[..appended_records.len() - 1];
3145        match (
3146            sequence_recovery.terminal(),
3147            order_recovery.active_binding(),
3148            prefix,
3149        ) {
3150            (None, None, []) => true,
3151            (None, None, [_]) => pending_terminal_matches,
3152            (Some(sequence_terminal), Some(order_terminal), [terminal]) => {
3153                sequence_terminal.owner == order_terminal.owner
3154                    && terminal.delivery_seq == sequence_terminal.delivery_seq
3155                    && terminal.admission_order.transaction_order()
3156                        == order_terminal.transaction_order
3157                    && terminal.kind
3158                        == RetainedCausalRecordKind::BindingTerminal(sequence_terminal.owner)
3159            }
3160            _ => false,
3161        }
3162    }
3163
3164    fn pending_fenced_terminal_matches(&self, appended_records: &[RetainedCausalRecord]) -> bool {
3165        let [terminal, _] = appended_records else {
3166            return false;
3167        };
3168        let RetainedCausalRecordKind::BindingTerminal(owner) = terminal.kind else {
3169            return false;
3170        };
3171        let sequence_matches = self.sequence.immutable_candidates.iter().any(|candidate| {
3172            matches!(
3173                candidate,
3174                ImmutableSequenceCandidate::BindingTerminal {
3175                    delivery_seq,
3176                    admission_order,
3177                    owner: candidate_owner,
3178                } if *delivery_seq == terminal.delivery_seq
3179                    && *admission_order == terminal.admission_order
3180                    && *candidate_owner == owner
3181            )
3182        });
3183        let order_matches = self.order.immutable_candidates.iter().any(|candidate| {
3184            candidate.transaction_order == terminal.admission_order.transaction_order()
3185                && candidate.candidate_keys.as_slice() == [terminal.admission_order]
3186        });
3187        sequence_matches && order_matches
3188    }
3189
3190    fn has_recovery_marker(&self, participant: FrontierParticipant) -> bool {
3191        self.retained_records.iter().any(|record| {
3192            record.delivery_seq == participant.cursor()
3193                && matches!(
3194                    record.kind,
3195                    RetainedCausalRecordKind::CompactionMarker { participant_index, .. }
3196                        if participant_index == participant.participant_index()
3197                )
3198        })
3199    }
3200
3201    fn has_duplicate_appended_record(&self, appended_records: &[RetainedCausalRecord]) -> bool {
3202        appended_records.iter().any(|row| {
3203            self.retained_records
3204                .iter()
3205                .any(|retained| retained.delivery_seq == row.delivery_seq)
3206        })
3207    }
3208
3209    /// Applies an acknowledgement's exact cursor/binding facts without exposing
3210    /// the participant vector as a server mutation API.
3211    pub(in crate::lifecycle) fn apply_live_identity(
3212        mut self,
3213        participant: FrontierParticipant,
3214    ) -> Result<Self, Box<(Self, LiveFrontierTransitionError)>> {
3215        let Some(current) = self
3216            .active_identities
3217            .participants
3218            .iter_mut()
3219            .find(|current| current.participant_index == participant.participant_index)
3220        else {
3221            return Err(Box::new((self, LiveFrontierTransitionError::Authority)));
3222        };
3223        if participant.cursor < current.cursor
3224            || participant.cursor > self.sequence.ledger.high_watermark()
3225        {
3226            return Err(Box::new((self, LiveFrontierTransitionError::Authority)));
3227        }
3228        *current = participant;
3229        Ok(self)
3230    }
3231
3232    /// Consumes one complete validated frontier into the ordinary record fixed
3233    /// point, preventing storage callers from supplying disconnected retained
3234    /// rows, participant cursors, immutable candidates, or aggregate ledgers.
3235    ///
3236    /// Exact keyed row charges remain durability facts. They are joined to the
3237    /// owned rows here before any floor/capacity/counter transition executes.
3238    /// The returned decision owns either the unchanged prestate and its exact
3239    /// earlier candidate, or the complete projected poststate.
3240    ///
3241    /// # Errors
3242    ///
3243    /// Returns [`OrdinaryRecordProjectionFailure`] for a conversation/binding
3244    /// mismatch, malformed keyed charges/accounting, capacity or observer
3245    /// refusal, counter exhaustion, or an impossible exact-owner relocation.
3246    /// Every failure owns the unchanged frontier and original projection input.
3247    pub fn project_ordinary_record(
3248        self,
3249        input: OrdinaryRecordProjectionInput,
3250    ) -> Result<OrdinaryRecordProjectionDecision, Box<OrdinaryRecordProjectionFailure>> {
3251        let (
3252            request,
3253            receiving_binding_epoch,
3254            encoded_record_charge,
3255            retained_charges,
3256            observer_progress,
3257            closure_accounting,
3258            limits,
3259        ) = input.as_parts();
3260        if request.conversation_id != self.conversation_id {
3261            return Err(projection_failure(
3262                self,
3263                input,
3264                OrdinaryProjectionError::Conversation,
3265            ));
3266        }
3267        let unaccepted_marker_anchors = ordinary_unaccepted_marker_anchors(&self);
3268        let kernel = match project_ordinary_fixed_point(&OrdinaryProjectionFacts {
3269            request: request.clone(),
3270            receiving_binding_epoch,
3271            encoded_record_charge,
3272            retained_records: &self.retained_records,
3273            retained_charges,
3274            active_marker_credit_records: &self.marker_records,
3275            unaccepted_marker_anchors: &unaccepted_marker_anchors,
3276            active_identities: self.active_identities.participants(),
3277            identity_slot_limit: self.identity_slot_limit,
3278            current_floor: self.retained_floor,
3279            observer_progress,
3280            order_ledger: self.order.ledger,
3281            sequence_ledger: self.sequence.ledger,
3282            immutable_candidates: &self.sequence.immutable_candidates,
3283            closure_accounting,
3284            remaining_recovery_claim: closure_accounting.edge_k_remaining(),
3285            limits,
3286        }) {
3287            Ok(value) => value,
3288            Err(error) => return Err(projection_failure(self, input, error)),
3289        };
3290        match kernel {
3291            OrdinaryProjectionKernelDecision::DrainFirst(prefix) => Ok(
3292                OrdinaryRecordProjectionDecision::DrainFirst(Box::new(OrdinaryRecordDrainFirst {
3293                    frontiers: self,
3294                    input,
3295                    candidate: prefix.candidate(),
3296                })),
3297            ),
3298            OrdinaryProjectionKernelDecision::Projected(projected) => {
3299                let observer_floor = match check_observer_floor(
3300                    ObserverCheckedOperation::RecordAdmission(request.clone()),
3301                    observer_progress,
3302                    projected.floor().resulting_floor,
3303                ) {
3304                    ObserverFloorDecision::Eligible(permit) => permit,
3305                    ObserverFloorDecision::Respond(_) => {
3306                        return Err(projection_failure(
3307                            self,
3308                            input,
3309                            OrdinaryProjectionError::ObserverSelectorInvariant,
3310                        ));
3311                    }
3312                };
3313                let closure = match check_remaining_closure(
3314                    &ClosureCheckedEnvelope::RecordAdmission(request.clone()),
3315                    closure_accounting,
3316                    false,
3317                    0,
3318                    projected.required_capacity(),
3319                ) {
3320                    RemainingClosureDecision::Eligible(permit) => *permit,
3321                    RemainingClosureDecision::Respond(_) => {
3322                        return Err(projection_failure(
3323                            self,
3324                            input,
3325                            OrdinaryProjectionError::ClosureSelectorInvariant,
3326                        ));
3327                    }
3328                };
3329                match self.apply_ordinary_projection(*projected, observer_floor, closure) {
3330                    Ok(projected) => Ok(OrdinaryRecordProjectionDecision::Projected(Box::new(
3331                        projected,
3332                    ))),
3333                    Err(failure) => {
3334                        let (frontiers, error) = *failure;
3335                        Err(projection_failure(frontiers, input, error))
3336                    }
3337                }
3338            }
3339        }
3340    }
3341
3342    fn apply_ordinary_projection(
3343        mut self,
3344        projected: OrdinaryFixedPointPlan,
3345        observer_floor: super::ObserverFloorPermit,
3346        closure: super::RemainingClosurePermit,
3347    ) -> Result<ProjectedOrdinaryRecord, Box<(Self, OrdinaryProjectionError)>> {
3348        let Ok(marker_count) = u64::try_from(projected.marker_candidates().len()) else {
3349            return Err(Box::new((
3350                self,
3351                OrdinaryProjectionError::SequenceRelocation,
3352            )));
3353        };
3354        let Some(sequence_delta) = marker_count.checked_add(1) else {
3355            return Err(Box::new((
3356                self,
3357                OrdinaryProjectionError::SequenceRelocation,
3358            )));
3359        };
3360        if let Err(error) = preflight_ordinary_sequence_owners(&self.sequence, sequence_delta) {
3361            return Err(Box::new((self, error)));
3362        }
3363        if let Err(error) = preflight_ordinary_order_owners(&self.order) {
3364            return Err(Box::new((self, error)));
3365        }
3366        let (
3367            floor,
3368            retained_charge,
3369            baseline,
3370            accounting,
3371            required_capacity,
3372            order,
3373            sequence,
3374            caller_record,
3375            caller_charge,
3376            retained_records,
3377            retained_charges,
3378            new_marker_candidates,
3379        ) = projected.into_parts();
3380
3381        let prior_sequence_ledger = self.sequence.ledger;
3382        let prior_order_ledger = self.order.ledger;
3383        relay_ordinary_sequence_owners(&mut self.sequence, sequence_delta);
3384        relay_ordinary_order_owners(&mut self.order);
3385
3386        self.sequence.ledger = sequence.resulting();
3387        self.sequence.immutable_candidates.extend(
3388            new_marker_candidates
3389                .iter()
3390                .copied()
3391                .map(ImmutableSequenceCandidate::Marker),
3392        );
3393        self.order.ledger = order.resulting();
3394        if !new_marker_candidates.is_empty() {
3395            self.order
3396                .immutable_candidates
3397                .push(ImmutableOrderCandidateMajor {
3398                    transaction_order: order.major(),
3399                    candidate_keys: new_marker_candidates
3400                        .iter()
3401                        .map(|candidate| candidate.admission_order)
3402                        .collect(),
3403                });
3404        }
3405        if validate_cross_counter(&self.sequence, &self.order).is_err() {
3406            self.sequence.ledger = prior_sequence_ledger;
3407            self.sequence.immutable_candidates.clear();
3408            self.order.ledger = prior_order_ledger;
3409            self.order.immutable_candidates.clear();
3410            rollback_ordinary_sequence_owners(&mut self.sequence, sequence_delta);
3411            rollback_ordinary_order_owners(&mut self.order);
3412            return Err(Box::new((
3413                self,
3414                OrdinaryProjectionError::SequenceRelocation,
3415            )));
3416        }
3417        self.retained_floor = floor.resulting_floor;
3418        self.retained_records = retained_records;
3419        self.marker_records
3420            .retain(|record| u128::from(record.delivery_seq) >= floor.resulting_floor);
3421
3422        Ok(ProjectedOrdinaryRecord {
3423            frontiers: self,
3424            floor,
3425            retained_charge,
3426            baseline,
3427            accounting,
3428            required_capacity,
3429            order,
3430            sequence,
3431            observer_floor,
3432            closure,
3433            caller_record,
3434            caller_charge,
3435            retained_charges,
3436            new_marker_candidates,
3437        })
3438    }
3439
3440    /// Returns the exact causal key a settled bound/detached Leave would
3441    /// consume without relinquishing frontier authority.
3442    ///
3443    /// This planning view exists so a durable binding can compute the
3444    /// canonical keyed `Left` row charge before calling the consuming commit.
3445    /// The consuming preparation reruns the same validation.
3446    ///
3447    /// # Errors
3448    ///
3449    /// Returns [`PrepareLeaveAuthorityError`] under the same preconditions as
3450    /// [`Self::prepare_settled_leave_authority`].
3451    pub fn planned_settled_leave_admission_order<F>(
3452        &self,
3453        member: &LiveMember<F>,
3454        binding_state: BindingState,
3455    ) -> Result<super::AdmissionOrder, PrepareLeaveAuthorityError> {
3456        let (participant_id, ended_binding_epoch) =
3457            validate_settled_leave_prestate(self, member, binding_state)?;
3458        let selection = select_leave_order(&self.order, participant_id, ended_binding_epoch, None)?;
3459        Ok(super::AdmissionOrder::new(
3460            selection.selected_major,
3461            CandidatePhase::MembershipExit,
3462            participant_id,
3463        ))
3464    }
3465
3466    /// Consumes the exact settled bound/detached `X` authority and relays the
3467    /// surviving order lane behind the selected `Left` major.
3468    ///
3469    /// Bound Leave also invalidates the same participant's exact `A` handle.
3470    /// Every immutable candidate must already have drained. The returned
3471    /// authority owns this frontier snapshot and is intentionally non-cloneable;
3472    /// only [`super::commit_leave`] can consume it.
3473    ///
3474    /// # Errors
3475    ///
3476    /// Returns [`PrepareLeaveAuthorityError`] when identity/binding authority,
3477    /// candidate precedence, a logical handle, or checked relay capacity fails.
3478    pub fn prepare_settled_leave_authority<F>(
3479        mut self,
3480        member: &LiveMember<F>,
3481        binding_state: BindingState,
3482    ) -> Result<PreparedLeaveAuthority, PrepareLeaveAuthorityError> {
3483        let (participant_id, ended_binding_epoch) =
3484            validate_settled_leave_prestate(&self, member, binding_state)?;
3485        let left_transaction_order =
3486            consume_leave_order_lane(&mut self.order, participant_id, ended_binding_epoch, None)?;
3487        Ok(PreparedLeaveAuthority::settled(
3488            self,
3489            member.conversation_id(),
3490            participant_id,
3491            ended_binding_epoch,
3492            left_transaction_order,
3493        ))
3494    }
3495
3496    /// Returns the exact causal key a pending-terminal Leave would consume
3497    /// after its immutable terminal, without relinquishing frontier authority.
3498    ///
3499    /// # Errors
3500    ///
3501    /// Returns [`PrepareLeaveAuthorityError`] under the same preconditions as
3502    /// [`Self::prepare_pending_leave_authority`].
3503    pub fn planned_pending_leave_admission_order<F>(
3504        &self,
3505        member: &LiveMember<F>,
3506        pending: PendingFinalization,
3507    ) -> Result<super::AdmissionOrder, PrepareLeaveAuthorityError> {
3508        let (participant_id, expected_order) =
3509            validate_pending_leave_prestate(self, member, pending)?;
3510        let selection =
3511            select_leave_order(&self.order, participant_id, None, Some(expected_order))?;
3512        Ok(super::AdmissionOrder::new(
3513            selection.selected_major,
3514            CandidatePhase::MembershipExit,
3515            participant_id,
3516        ))
3517    }
3518
3519    /// Consumes the exact pending-terminal plus `X` positional order authority.
3520    ///
3521    /// The pending terminal must be the sole immutable candidate, must match the
3522    /// detached identity's exact prior epoch, and must lie strictly before the
3523    /// participant's `X` handle. The returned non-cloneable authority owns the
3524    /// relayed frontier snapshot and can be consumed only by
3525    /// [`super::commit_pending_leave`].
3526    ///
3527    /// # Errors
3528    ///
3529    /// Returns [`PrepareLeaveAuthorityError`] for mismatched identity/binding,
3530    /// any unrelated candidate, absent logical ownership, or insufficient
3531    /// checked suffix for the later-handle relocation.
3532    pub fn prepare_pending_leave_authority<F>(
3533        mut self,
3534        member: &LiveMember<F>,
3535        pending: PendingFinalization,
3536    ) -> Result<PreparedLeaveAuthority, PrepareLeaveAuthorityError> {
3537        let (participant_id, expected_order) =
3538            validate_pending_leave_prestate(&self, member, pending)?;
3539        let left_transaction_order =
3540            consume_leave_order_lane(&mut self.order, participant_id, None, Some(expected_order))?;
3541        Ok(PreparedLeaveAuthority::pending(
3542            self,
3543            member.conversation_id(),
3544            participant_id,
3545            pending.binding_epoch(),
3546            expected_order,
3547            left_transaction_order,
3548        ))
3549    }
3550
3551    /// Completes the claim-frontier portion of one already-authorized Leave.
3552    ///
3553    /// The transition consumes the retiring identity's `E`, its still-live `T`
3554    /// when applicable, and every product dimension removed with membership.
3555    /// Surviving direct, product, and recovery claims are relayed gap-free after
3556    /// the appended `Left` (or pending terminal plus `Left`) records. The
3557    /// retained suffix is extended at the unchanged floor so no caller-authored
3558    /// floor or snapshot can be substituted for the protocol result.
3559    #[allow(
3560        clippy::too_many_lines,
3561        reason = "the atomic Leave relay keeps membership, both ledgers, products, recovery, and retained rows visibly in one checked transition"
3562    )]
3563    pub(super) fn finish_leave_claims(
3564        mut self,
3565        participant_id: ParticipantId,
3566        ended_binding_epoch: Option<BindingEpoch>,
3567        committed_terminal: Option<CommittedBindingTerminal>,
3568        left_delivery_seq: DeliverySeq,
3569        left_transaction_order: TransactionOrder,
3570    ) -> Result<Self, LeaveCommitError> {
3571        let prior_high = self.sequence.ledger.high_watermark();
3572        let first_appended = prior_high
3573            .checked_add(1)
3574            .ok_or(LeaveCommitError::SequenceAuthority)?;
3575        let expected_left = if committed_terminal.is_some() {
3576            first_appended
3577                .checked_add(1)
3578                .ok_or(LeaveCommitError::SequenceAuthority)?
3579        } else {
3580            first_appended
3581        };
3582        if left_delivery_seq != expected_left {
3583            return Err(LeaveCommitError::SequenceAuthority);
3584        }
3585
3586        match (
3587            committed_terminal,
3588            self.sequence.immutable_candidates.as_slice(),
3589        ) {
3590            (
3591                Some(terminal),
3592                [
3593                    ImmutableSequenceCandidate::BindingTerminal {
3594                        delivery_seq,
3595                        admission_order,
3596                        owner,
3597                    },
3598                ],
3599            ) if *delivery_seq == first_appended
3600                && terminal.delivery_seq() == first_appended
3601                && *admission_order == terminal.admission_order()
3602                && owner.participant_index == participant_id
3603                && owner.binding_epoch == terminal.binding_epoch() => {}
3604            (None, []) => {}
3605            (Some(_) | None, _) => return Err(LeaveCommitError::SequenceAuthority),
3606        }
3607
3608        let Some(active_index) = self
3609            .active_identities
3610            .participants
3611            .iter()
3612            .position(|participant| participant.participant_index == participant_id)
3613        else {
3614            return Err(LeaveCommitError::ResultingFrontier);
3615        };
3616        self.active_identities.participants.remove(active_index);
3617        self.marker_records.retain(|record| {
3618            !matches!(
3619                record.kind,
3620                RetainedCausalRecordKind::CompactionMarker { participant_index, .. }
3621                    if participant_index == participant_id
3622            )
3623        });
3624        // A latch naming the leaver names a marker occurrence just removed and
3625        // a participant no longer active: no take of it remains to refuse.
3626        if self
3627            .fenced_marker_issued
3628            .is_some_and(|(latched, _, _)| latched == participant_id)
3629        {
3630            self.fenced_marker_issued = None;
3631        }
3632        let resulting_live = usize_to_u64(self.active_identities.participants.len());
3633
3634        let mut exit_consumed = false;
3635        let mut terminal_consumed = ended_binding_epoch.is_none();
3636        let mut units = Vec::new();
3637        for claim in self.sequence.movable_claims.iter().copied() {
3638            match claim.owner {
3639                SequenceDirectOwner::MembershipExit {
3640                    participant_index: owner,
3641                } if owner == participant_id => {
3642                    if exit_consumed {
3643                        return Err(LeaveCommitError::ResultingFrontier);
3644                    }
3645                    exit_consumed = true;
3646                }
3647                SequenceDirectOwner::BindingTerminal(owner)
3648                    if owner.participant_index == participant_id
3649                        && Some(owner.binding_epoch) == ended_binding_epoch =>
3650                {
3651                    if terminal_consumed {
3652                        return Err(LeaveCommitError::ResultingFrontier);
3653                    }
3654                    terminal_consumed = true;
3655                }
3656                SequenceDirectOwner::MembershipExit { .. }
3657                | SequenceDirectOwner::BindingTerminal(_) => {
3658                    units.push(LeaveSequenceUnit::Direct(claim));
3659                }
3660            }
3661        }
3662        if !exit_consumed || !terminal_consumed {
3663            return Err(LeaveCommitError::ResultingFrontier);
3664        }
3665
3666        let recovery_owned = self
3667            .sequence
3668            .recovery
3669            .is_some_and(|block| block.participant_index == participant_id);
3670        for range in self.sequence.products.live_times_terminal.iter().copied() {
3671            if range.terminal.participant_index != participant_id && resulting_live != 0 {
3672                units.push(LeaveSequenceUnit::TerminalProduct(TerminalProductRange {
3673                    start: range.start,
3674                    length: resulting_live,
3675                    terminal: range.terminal,
3676                }));
3677            }
3678        }
3679        if let Some(range) = self.sequence.products.live_times_replacement_terminal
3680            && !recovery_owned
3681            && resulting_live != 0
3682        {
3683            units.push(LeaveSequenceUnit::ReplacementProduct(
3684                ReplacementTerminalProductRange {
3685                    start: range.start,
3686                    length: resulting_live,
3687                    participant_index: range.participant_index,
3688                    marker_delivery_seq: range.marker_delivery_seq,
3689                    prior_binding_epoch: range.prior_binding_epoch,
3690                },
3691            ));
3692        }
3693        let resulting_other = resulting_live.saturating_sub(1);
3694        if resulting_other != 0 {
3695            for range in self.sequence.products.other_live_times_exit.iter().copied() {
3696                if range.exit_participant != participant_id {
3697                    units.push(LeaveSequenceUnit::ExitProduct(ExitProductRange {
3698                        start: range.start,
3699                        length: resulting_other,
3700                        exit_participant: range.exit_participant,
3701                    }));
3702                }
3703            }
3704        }
3705        if let Some(recovery) = self.sequence.recovery
3706            && !recovery_owned
3707        {
3708            units.push(LeaveSequenceUnit::Recovery(recovery));
3709        }
3710        units.sort_by_key(|unit| unit.original_start());
3711
3712        let mut cursor = left_delivery_seq.checked_add(1);
3713        let mut movable_claims = Vec::new();
3714        let mut terminal_products = Vec::new();
3715        let mut replacement_product = None;
3716        let mut exit_products = Vec::new();
3717        let mut recovery = None;
3718        for unit in units {
3719            match unit {
3720                LeaveSequenceUnit::Direct(mut claim) => {
3721                    claim.delivery_seq = allocate_leave_sequence_range(&mut cursor, 1)?;
3722                    movable_claims.push(claim);
3723                }
3724                LeaveSequenceUnit::TerminalProduct(mut range) => {
3725                    range.start = allocate_leave_sequence_range(&mut cursor, range.length)?;
3726                    terminal_products.push(range);
3727                }
3728                LeaveSequenceUnit::ReplacementProduct(mut range) => {
3729                    range.start = allocate_leave_sequence_range(&mut cursor, range.length)?;
3730                    replacement_product = Some(range);
3731                }
3732                LeaveSequenceUnit::ExitProduct(mut range) => {
3733                    range.start = allocate_leave_sequence_range(&mut cursor, range.length)?;
3734                    exit_products.push(range);
3735                }
3736                LeaveSequenceUnit::Recovery(mut block) => {
3737                    let length = 2 + u64::from(block.terminal.is_some());
3738                    let start = allocate_leave_sequence_range(&mut cursor, length)?;
3739                    if let Some(mut terminal) = block.terminal {
3740                        terminal.delivery_seq = start;
3741                        block.terminal = Some(terminal);
3742                        block.recovery_attach_seq = start
3743                            .checked_add(1)
3744                            .ok_or(LeaveCommitError::ResultingFrontier)?;
3745                    } else {
3746                        block.recovery_attach_seq = start;
3747                    }
3748                    block.replacement_terminal_seq = block
3749                        .recovery_attach_seq
3750                        .checked_add(1)
3751                        .ok_or(LeaveCommitError::ResultingFrontier)?;
3752                    recovery = Some(block);
3753                }
3754            }
3755        }
3756        movable_claims.sort_by_key(|claim| claim.delivery_seq);
3757        terminal_products.sort_by_key(|range| range.start);
3758        exit_products.sort_by_key(|range| range.start);
3759        let terminal_count = usize_to_u64(
3760            movable_claims
3761                .iter()
3762                .filter(|claim| matches!(claim.owner, SequenceDirectOwner::BindingTerminal(_)))
3763                .count(),
3764        ) + u64::from(recovery.is_some_and(|block| block.terminal.is_some()));
3765        let recovery_reserve = if recovery.is_some() {
3766            RecoverySequenceReserve::DetachedCredentialRecovery
3767        } else {
3768            RecoverySequenceReserve::None
3769        };
3770        let ledger = SequenceLedger::try_new(
3771            left_delivery_seq,
3772            SequenceClaims::new(resulting_live, terminal_count, 0, recovery_reserve),
3773        )
3774        .map_err(|_| LeaveCommitError::ResultingFrontier)?;
3775        self.sequence = SequenceClaimFrontier {
3776            ledger,
3777            movable_claims,
3778            immutable_candidates: Vec::new(),
3779            products: SequenceProductRanges {
3780                live_times_terminal: terminal_products,
3781                live_times_replacement_terminal: replacement_product,
3782                other_live_times_exit: exit_products,
3783            },
3784            recovery,
3785        };
3786
3787        if let Some(terminal) = committed_terminal {
3788            self.retained_records.push(RetainedCausalRecord {
3789                delivery_seq: terminal.delivery_seq(),
3790                admission_order: terminal.admission_order(),
3791                kind: RetainedCausalRecordKind::BindingTerminal(BindingTerminalOwner {
3792                    participant_index: participant_id,
3793                    binding_epoch: terminal.binding_epoch(),
3794                }),
3795            });
3796        }
3797        self.retained_records.push(RetainedCausalRecord {
3798            delivery_seq: left_delivery_seq,
3799            admission_order: super::AdmissionOrder::new(
3800                left_transaction_order,
3801                CandidatePhase::MembershipExit,
3802                participant_id,
3803            ),
3804            kind: RetainedCausalRecordKind::MembershipExit {
3805                participant_index: participant_id,
3806            },
3807        });
3808        self.retained_records
3809            .sort_by_key(|record| record.delivery_seq);
3810
3811        let order_claims = self.order.ledger.claims();
3812        if order_claims.membership_exits() != resulting_live
3813            || self.order.recovery.is_some() != self.sequence.recovery.is_some()
3814            || validate_cross_counter(&self.sequence, &self.order).is_err()
3815        {
3816            return Err(LeaveCommitError::ResultingFrontier);
3817        }
3818        Ok(self)
3819    }
3820
3821    fn marker_candidate(&self, delivery_seq: DeliverySeq) -> Option<ValidatedMarkerCandidate> {
3822        self.sequence
3823            .immutable_candidates
3824            .iter()
3825            .find_map(|candidate| match candidate {
3826                ImmutableSequenceCandidate::Marker(candidate)
3827                    if candidate.delivery_seq == delivery_seq =>
3828                {
3829                    Some(ValidatedMarkerCandidate {
3830                        conversation_id: self.conversation_id,
3831                        candidate: *candidate,
3832                        seal: MarkerAuthoritySeal::Validated,
3833                    })
3834                }
3835                _ => None,
3836            })
3837    }
3838
3839    /// Consumes only the exact next bound marker candidate and atomically
3840    /// materializes its retained marker fact.
3841    ///
3842    /// The delivery high watermark advances once, `M` decreases once, and all
3843    /// surviving numeric owners remain in place because they already begin at
3844    /// the new `H+1`. The shared causal order major is already allocated and is
3845    /// therefore removed only from the immutable tuple lane; marker drain never
3846    /// allocates or advances [`OrderHigh`].
3847    pub(super) fn drain_next_marker_core(
3848        mut self,
3849    ) -> Result<MarkerDrainCore, MarkerDrainCoreError> {
3850        let Some(first) = self.sequence.immutable_candidates.first().copied() else {
3851            return Err(MarkerDrainCoreError::NoCandidate);
3852        };
3853        let ImmutableSequenceCandidate::Marker(marker) = first else {
3854            return Err(MarkerDrainCoreError::BindingTerminalFirst);
3855        };
3856        let expected_sequence = self
3857            .sequence
3858            .ledger
3859            .high_watermark()
3860            .checked_add(1)
3861            .ok_or(MarkerDrainCoreError::SequenceNotNext)?;
3862        if marker.delivery_seq != expected_sequence {
3863            return Err(MarkerDrainCoreError::SequenceNotNext);
3864        }
3865        if order_is_above_high(
3866            marker.admission_order.transaction_order(),
3867            self.order.ledger.high(),
3868        ) {
3869            return Err(MarkerDrainCoreError::CausalMajorNotAllocated);
3870        }
3871        let candidate = self
3872            .marker_candidate(marker.delivery_seq)
3873            .ok_or(MarkerDrainCoreError::NoCandidate)?;
3874
3875        let key = marker.admission_order;
3876        let Some(group_index) = self
3877            .order
3878            .immutable_candidates
3879            .iter()
3880            .position(|group| group.candidate_keys.contains(&key))
3881        else {
3882            return Err(MarkerDrainCoreError::MissingOrderCandidate);
3883        };
3884        let group = &mut self.order.immutable_candidates[group_index];
3885        let Ok(key_index) = group.candidate_keys.binary_search(&key) else {
3886            return Err(MarkerDrainCoreError::MissingOrderCandidate);
3887        };
3888        group.candidate_keys.remove(key_index);
3889        if group.candidate_keys.is_empty() {
3890            self.order.immutable_candidates.remove(group_index);
3891        }
3892        self.sequence.immutable_candidates.remove(0);
3893
3894        let claims = self.sequence.ledger.claims();
3895        let markers = claims
3896            .markers()
3897            .checked_sub(1)
3898            .ok_or(MarkerDrainCoreError::ResultingLedger)?;
3899        self.sequence.ledger = SequenceLedger::try_new(
3900            expected_sequence,
3901            super::SequenceClaims::new(
3902                claims.live_members(),
3903                claims.binding_terminals(),
3904                markers,
3905                claims.recovery(),
3906            ),
3907        )
3908        .map_err(|_| MarkerDrainCoreError::ResultingLedger)?;
3909
3910        let record = RetainedCausalRecord {
3911            delivery_seq: marker.delivery_seq,
3912            admission_order: marker.admission_order,
3913            kind: RetainedCausalRecordKind::CompactionMarker {
3914                participant_index: marker.admission_order.participant_index(),
3915                provenance: marker.provenance,
3916            },
3917        };
3918        self.marker_records.push(record);
3919        self.retained_records.push(record);
3920        Ok(MarkerDrainCore {
3921            candidate,
3922            record: ValidatedMarkerRecord {
3923                conversation_id: self.conversation_id,
3924                record,
3925                provenance: marker.provenance,
3926                target_binding: marker.target_binding,
3927                occurrence: MarkerRecordOccurrence::Undelivered,
3928                seal: MarkerAuthoritySeal::Validated,
3929            },
3930            frontiers: self,
3931        })
3932    }
3933
3934    /// Consumes the exact first pending binding-terminal candidate and
3935    /// atomically materializes its retained terminal fact (the candidate-lane
3936    /// terminal sibling of [`Self::drain_next_marker_core`], per the R-A2
3937    /// candidate drain).
3938    ///
3939    /// The candidate's transaction-order major was already consumed when the
3940    /// terminal pended; only the immutable tuple lane releases it here. The
3941    /// delivery high watermark advances once and the pending terminal claim
3942    /// decreases once. The owning participant stays in the active identity
3943    /// ranks with its detached dead epoch — exactly the poststate an
3944    /// immediately-committed terminal produces — and every surviving claim is
3945    /// relocated through the same unreserved rebuild that live transitions
3946    /// use.
3947    pub(in crate::lifecycle) fn drain_first_binding_terminal(
3948        mut self,
3949        expected_owner: BindingTerminalOwner,
3950        expected_order: super::AdmissionOrder,
3951    ) -> Result<(Self, RetainedCausalRecord), Box<(Self, LiveFrontierTransitionError)>> {
3952        let (delivery_seq, admission_order, owner) =
3953            match self.validate_first_terminal_candidate(expected_owner, expected_order) {
3954                Ok(candidate) => candidate,
3955                Err(error) => return Err(Box::new((self, error))),
3956            };
3957        let claims = self.sequence.ledger.claims();
3958        let Some(sequence_ledger) =
3959            claims
3960                .binding_terminals()
3961                .checked_sub(1)
3962                .and_then(|binding_terminals| {
3963                    SequenceLedger::try_new(
3964                        delivery_seq,
3965                        SequenceClaims::new(
3966                            claims.live_members(),
3967                            binding_terminals,
3968                            claims.markers(),
3969                            claims.recovery(),
3970                        ),
3971                    )
3972                    .ok()
3973                })
3974        else {
3975            return Err(Box::new((
3976                self,
3977                LiveFrontierTransitionError::ResultingFrontier,
3978            )));
3979        };
3980        let order_ledger = self.order.ledger;
3981        let Ok(active) = ActiveIdentityRanks::try_new(
3982            self.active_identities.participants().to_vec(),
3983            sequence_ledger.high_watermark(),
3984            self.identity_slot_limit,
3985        ) else {
3986            return Err(Box::new((self, LiveFrontierTransitionError::Authority)));
3987        };
3988        let (sequence, order) =
3989            match rebuild_unreserved_frontiers(&active, sequence_ledger, order_ledger) {
3990                Ok(frontiers) => frontiers,
3991                Err(error) => return Err(Box::new((self, error))),
3992            };
3993        let record = RetainedCausalRecord {
3994            delivery_seq,
3995            admission_order,
3996            kind: RetainedCausalRecordKind::BindingTerminal(owner),
3997        };
3998        self.active_identities = active;
3999        self.sequence = sequence;
4000        self.order = order;
4001        self.retained_records.push(record);
4002        Ok((self, record))
4003    }
4004
4005    /// Validates the drain prestate without mutation: the sole immutable
4006    /// candidate in both lanes is this exact pending binding terminal at the
4007    /// next delivery sequence, its order major is already allocated, and its
4008    /// owning participant rests detached at the dead epoch.
4009    fn validate_first_terminal_candidate(
4010        &self,
4011        expected_owner: BindingTerminalOwner,
4012        expected_order: super::AdmissionOrder,
4013    ) -> Result<
4014        (DeliverySeq, super::AdmissionOrder, BindingTerminalOwner),
4015        LiveFrontierTransitionError,
4016    > {
4017        if self.sequence.recovery.is_some() || self.order.recovery.is_some() {
4018            return Err(LiveFrontierTransitionError::Precedence(
4019                PrecedenceCondition::FencedRecovery,
4020            ));
4021        }
4022        let [first] = self.sequence.immutable_candidates.as_slice() else {
4023            return Err(LiveFrontierTransitionError::Precedence(
4024                self.precedence_condition(),
4025            ));
4026        };
4027        let ImmutableSequenceCandidate::BindingTerminal {
4028            delivery_seq,
4029            admission_order,
4030            owner,
4031        } = *first
4032        else {
4033            return Err(LiveFrontierTransitionError::Precedence(
4034                self.precedence_condition(),
4035            ));
4036        };
4037        if owner != expected_owner || admission_order != expected_order {
4038            return Err(LiveFrontierTransitionError::Authority);
4039        }
4040        let Some(expected_sequence) = self.sequence.ledger.high_watermark().checked_add(1) else {
4041            return Err(LiveFrontierTransitionError::Exhausted);
4042        };
4043        if delivery_seq != expected_sequence
4044            || order_is_above_high(
4045                admission_order.transaction_order(),
4046                self.order.ledger.high(),
4047            )
4048        {
4049            return Err(LiveFrontierTransitionError::RecordPosition);
4050        }
4051        let [group] = self.order.immutable_candidates.as_slice() else {
4052            return Err(LiveFrontierTransitionError::RecordPosition);
4053        };
4054        if group.candidate_keys.as_slice() != [admission_order] {
4055            return Err(LiveFrontierTransitionError::RecordPosition);
4056        }
4057        let detached_matches = self
4058            .active_identities
4059            .participants()
4060            .iter()
4061            .any(|participant| {
4062                participant.participant_index == owner.participant_index
4063                    && participant.binding == FrontierBinding::Detached(owner.binding_epoch)
4064            });
4065        if !detached_matches {
4066            return Err(LiveFrontierTransitionError::Authority);
4067        }
4068        Ok((delivery_seq, admission_order, owner))
4069    }
4070}
4071
4072fn apply_fenced_ledgers(
4073    sequence: SequenceLedger,
4074    order: OrderLedger,
4075    finalizes_pending: bool,
4076) -> Option<(SequenceLedger, OrderLedger)> {
4077    let sequence = if finalizes_pending {
4078        sequence.apply_fenced_recovery_finalizing_pending()
4079    } else {
4080        sequence.apply_fenced_recovery()
4081    }
4082    .ok()?;
4083    let order = if finalizes_pending {
4084        order.apply_fenced_recovery_finalizing_pending()
4085    } else {
4086        order.apply_fenced_recovery()
4087    }
4088    .ok()?;
4089    Some((sequence, order))
4090}
4091
4092fn rebuild_unreserved_frontiers(
4093    active: &ActiveIdentityRanks,
4094    sequence_ledger: SequenceLedger,
4095    order_ledger: OrderLedger,
4096) -> Result<(SequenceClaimFrontier, OrderClaimFrontier), LiveFrontierTransitionError> {
4097    let terminal_owners: Vec<_> = active
4098        .participants()
4099        .iter()
4100        .filter_map(|participant| match participant.binding() {
4101            FrontierBinding::Bound(binding_epoch) => Some(BindingTerminalOwner {
4102                participant_index: participant.participant_index(),
4103                binding_epoch,
4104            }),
4105            FrontierBinding::Detached(_) => None,
4106        })
4107        .collect();
4108    let live_count = active.len();
4109    let terminal_count =
4110        u64::try_from(terminal_owners.len()).map_err(|_| LiveFrontierTransitionError::Exhausted)?;
4111    let sequence_claims = sequence_ledger.claims();
4112    let order_claims = order_ledger.claims();
4113    if sequence_claims.live_members() != live_count
4114        || sequence_claims.binding_terminals() != terminal_count
4115        || sequence_claims.markers() != 0
4116        || sequence_claims.recovery() != RecoverySequenceReserve::None
4117        || order_claims.active_binding_terminals() != terminal_count
4118        || order_claims.membership_exits() != live_count
4119        || order_claims.recovery_operation()
4120        || order_claims.recovery_replacement_terminal()
4121    {
4122        return Err(LiveFrontierTransitionError::ResultingFrontier);
4123    }
4124
4125    let sequence = rebuild_unreserved_sequence(active, &terminal_owners, sequence_ledger)?;
4126    let order = rebuild_unreserved_order(active, &terminal_owners, order_ledger)?;
4127    validate_cross_counter(&sequence, &order)
4128        .map_err(|_| LiveFrontierTransitionError::ResultingFrontier)?;
4129    Ok((sequence, order))
4130}
4131
4132fn rebuild_unreserved_sequence(
4133    active: &ActiveIdentityRanks,
4134    terminal_owners: &[BindingTerminalOwner],
4135    sequence_ledger: SequenceLedger,
4136) -> Result<SequenceClaimFrontier, LiveFrontierTransitionError> {
4137    let live_count = active.len();
4138    let mut sequence_cursor = sequence_ledger
4139        .high_watermark()
4140        .checked_add(1)
4141        .ok_or(LiveFrontierTransitionError::Exhausted)?;
4142    let mut movable_sequence = Vec::new();
4143    for terminal in terminal_owners {
4144        movable_sequence.push(MovableSequenceClaim {
4145            delivery_seq: take_live_sequence(&mut sequence_cursor, 1)?,
4146            owner: SequenceDirectOwner::BindingTerminal(*terminal),
4147        });
4148    }
4149    for participant in active.participants() {
4150        movable_sequence.push(MovableSequenceClaim {
4151            delivery_seq: take_live_sequence(&mut sequence_cursor, 1)?,
4152            owner: SequenceDirectOwner::MembershipExit {
4153                participant_index: participant.participant_index(),
4154            },
4155        });
4156    }
4157    let mut terminal_products = Vec::new();
4158    for terminal in terminal_owners {
4159        terminal_products.push(TerminalProductRange {
4160            start: take_live_sequence(&mut sequence_cursor, live_count)?,
4161            length: live_count,
4162            terminal: *terminal,
4163        });
4164    }
4165    let exit_product_length = live_count.saturating_sub(1);
4166    let mut exit_products = Vec::new();
4167    if live_count > 1 {
4168        for participant in active.participants() {
4169            exit_products.push(ExitProductRange {
4170                start: take_live_sequence(&mut sequence_cursor, exit_product_length)?,
4171                length: exit_product_length,
4172                exit_participant: participant.participant_index(),
4173            });
4174        }
4175    }
4176    let sequence_end = u128::from(sequence_ledger.high_watermark())
4177        .checked_add(sequence_ledger.required_reserve())
4178        .and_then(|value| value.checked_add(1))
4179        .ok_or(LiveFrontierTransitionError::Exhausted)?;
4180    if u128::from(sequence_cursor) != sequence_end {
4181        return Err(LiveFrontierTransitionError::ResultingFrontier);
4182    }
4183    Ok(SequenceClaimFrontier {
4184        ledger: sequence_ledger,
4185        movable_claims: movable_sequence,
4186        immutable_candidates: Vec::new(),
4187        products: SequenceProductRanges {
4188            live_times_terminal: terminal_products,
4189            live_times_replacement_terminal: None,
4190            other_live_times_exit: exit_products,
4191        },
4192        recovery: None,
4193    })
4194}
4195
4196fn rebuild_unreserved_order(
4197    active: &ActiveIdentityRanks,
4198    terminal_owners: &[BindingTerminalOwner],
4199    order_ledger: OrderLedger,
4200) -> Result<OrderClaimFrontier, LiveFrontierTransitionError> {
4201    let order_claims = order_ledger.claims();
4202    let order_start = order_frontier_start(order_ledger.high());
4203    let mut order_cursor =
4204        u64::try_from(order_start).map_err(|_| LiveFrontierTransitionError::Exhausted)?;
4205    let mut movable_order = Vec::new();
4206    for terminal in terminal_owners.iter().copied() {
4207        movable_order.push(MovableOrderClaim {
4208            transaction_order: take_live_order(&mut order_cursor)?,
4209            owner: OrderDirectOwner::ActiveBindingTerminal(terminal),
4210        });
4211    }
4212    for participant in active.participants() {
4213        movable_order.push(MovableOrderClaim {
4214            transaction_order: take_live_order(&mut order_cursor)?,
4215            owner: OrderDirectOwner::MembershipExit {
4216                participant_index: participant.participant_index(),
4217            },
4218        });
4219    }
4220    if u128::from(order_cursor) != order_start + order_claims.total() {
4221        return Err(LiveFrontierTransitionError::ResultingFrontier);
4222    }
4223    Ok(OrderClaimFrontier {
4224        ledger: order_ledger,
4225        movable_claims: movable_order,
4226        immutable_candidates: Vec::new(),
4227        recovery: None,
4228    })
4229}
4230
4231fn rebuild_pending_terminal_frontiers(
4232    active: &ActiveIdentityRanks,
4233    pending_owner: BindingTerminalOwner,
4234    delivery_seq: DeliverySeq,
4235    admission_order: super::AdmissionOrder,
4236    sequence_ledger: SequenceLedger,
4237    order_ledger: OrderLedger,
4238) -> Result<(SequenceClaimFrontier, OrderClaimFrontier), LiveFrontierTransitionError> {
4239    if admission_order.candidate_phase() != CandidatePhase::BindingTerminal
4240        || admission_order.participant_index() != pending_owner.participant_index
4241        || admission_order.transaction_order()
4242            != match order_ledger.high() {
4243                OrderHigh::Allocated(high) => high,
4244                OrderHigh::Empty => return Err(LiveFrontierTransitionError::RecordPosition),
4245            }
4246        || sequence_ledger.high_watermark().checked_add(1) != Some(delivery_seq)
4247    {
4248        return Err(LiveFrontierTransitionError::RecordPosition);
4249    }
4250    let bound_owners: Vec<_> = active
4251        .participants()
4252        .iter()
4253        .filter_map(|participant| match participant.binding() {
4254            FrontierBinding::Bound(binding_epoch) => Some(BindingTerminalOwner {
4255                participant_index: participant.participant_index(),
4256                binding_epoch,
4257            }),
4258            FrontierBinding::Detached(_) => None,
4259        })
4260        .collect();
4261    let mut all_terminal_owners = bound_owners.clone();
4262    all_terminal_owners.push(pending_owner);
4263    all_terminal_owners.sort_unstable_by_key(|owner| owner.participant_index);
4264    let live_count = active.len();
4265    let bound_count =
4266        u64::try_from(bound_owners.len()).map_err(|_| LiveFrontierTransitionError::Exhausted)?;
4267    let terminal_count = u64::try_from(all_terminal_owners.len())
4268        .map_err(|_| LiveFrontierTransitionError::Exhausted)?;
4269    let sequence_claims = sequence_ledger.claims();
4270    let order_claims = order_ledger.claims();
4271    if sequence_claims.live_members() != live_count
4272        || sequence_claims.binding_terminals() != terminal_count
4273        || sequence_claims.markers() != 0
4274        || sequence_claims.recovery() != RecoverySequenceReserve::None
4275        || order_claims.active_binding_terminals() != bound_count
4276        || order_claims.membership_exits() != live_count
4277        || order_claims.recovery_operation()
4278        || order_claims.recovery_replacement_terminal()
4279    {
4280        return Err(LiveFrontierTransitionError::ResultingFrontier);
4281    }
4282
4283    let sequence = rebuild_pending_terminal_sequence(
4284        active,
4285        pending_owner,
4286        delivery_seq,
4287        admission_order,
4288        sequence_ledger,
4289        &bound_owners,
4290        &all_terminal_owners,
4291    )?;
4292    let order =
4293        rebuild_pending_terminal_order(active, admission_order, order_ledger, bound_owners)?;
4294    validate_cross_counter(&sequence, &order)
4295        .map_err(|_| LiveFrontierTransitionError::ResultingFrontier)?;
4296    Ok((sequence, order))
4297}
4298
4299fn rebuild_pending_terminal_sequence(
4300    active: &ActiveIdentityRanks,
4301    pending_owner: BindingTerminalOwner,
4302    delivery_seq: DeliverySeq,
4303    admission_order: super::AdmissionOrder,
4304    sequence_ledger: SequenceLedger,
4305    bound_owners: &[BindingTerminalOwner],
4306    all_terminal_owners: &[BindingTerminalOwner],
4307) -> Result<SequenceClaimFrontier, LiveFrontierTransitionError> {
4308    let live_count = active.len();
4309    let mut sequence_cursor = delivery_seq;
4310    let candidate_sequence = take_live_sequence(&mut sequence_cursor, 1)?;
4311    let mut movable_sequence = Vec::new();
4312    for terminal in bound_owners {
4313        movable_sequence.push(MovableSequenceClaim {
4314            delivery_seq: take_live_sequence(&mut sequence_cursor, 1)?,
4315            owner: SequenceDirectOwner::BindingTerminal(*terminal),
4316        });
4317    }
4318    for participant in active.participants() {
4319        movable_sequence.push(MovableSequenceClaim {
4320            delivery_seq: take_live_sequence(&mut sequence_cursor, 1)?,
4321            owner: SequenceDirectOwner::MembershipExit {
4322                participant_index: participant.participant_index(),
4323            },
4324        });
4325    }
4326    let mut terminal_products = Vec::new();
4327    for terminal in all_terminal_owners {
4328        terminal_products.push(TerminalProductRange {
4329            start: take_live_sequence(&mut sequence_cursor, live_count)?,
4330            length: live_count,
4331            terminal: *terminal,
4332        });
4333    }
4334    let exit_product_length = live_count.saturating_sub(1);
4335    let mut exit_products = Vec::new();
4336    if live_count > 1 {
4337        for participant in active.participants() {
4338            exit_products.push(ExitProductRange {
4339                start: take_live_sequence(&mut sequence_cursor, exit_product_length)?,
4340                length: exit_product_length,
4341                exit_participant: participant.participant_index(),
4342            });
4343        }
4344    }
4345    let sequence_end = u128::from(sequence_ledger.high_watermark())
4346        .checked_add(sequence_ledger.required_reserve())
4347        .and_then(|value| value.checked_add(1))
4348        .ok_or(LiveFrontierTransitionError::Exhausted)?;
4349    if u128::from(sequence_cursor) != sequence_end {
4350        return Err(LiveFrontierTransitionError::ResultingFrontier);
4351    }
4352    Ok(SequenceClaimFrontier {
4353        ledger: sequence_ledger,
4354        movable_claims: movable_sequence,
4355        immutable_candidates: alloc::vec![ImmutableSequenceCandidate::BindingTerminal {
4356            delivery_seq: candidate_sequence,
4357            admission_order,
4358            owner: pending_owner,
4359        }],
4360        products: SequenceProductRanges {
4361            live_times_terminal: terminal_products,
4362            live_times_replacement_terminal: None,
4363            other_live_times_exit: exit_products,
4364        },
4365        recovery: None,
4366    })
4367}
4368
4369fn rebuild_pending_terminal_order(
4370    active: &ActiveIdentityRanks,
4371    admission_order: super::AdmissionOrder,
4372    order_ledger: OrderLedger,
4373    bound_owners: Vec<BindingTerminalOwner>,
4374) -> Result<OrderClaimFrontier, LiveFrontierTransitionError> {
4375    let order_claims = order_ledger.claims();
4376    let order_start = order_frontier_start(order_ledger.high());
4377    let mut order_cursor =
4378        u64::try_from(order_start).map_err(|_| LiveFrontierTransitionError::Exhausted)?;
4379    let mut movable_order = Vec::new();
4380    for terminal in bound_owners {
4381        movable_order.push(MovableOrderClaim {
4382            transaction_order: take_live_order(&mut order_cursor)?,
4383            owner: OrderDirectOwner::ActiveBindingTerminal(terminal),
4384        });
4385    }
4386    for participant in active.participants() {
4387        movable_order.push(MovableOrderClaim {
4388            transaction_order: take_live_order(&mut order_cursor)?,
4389            owner: OrderDirectOwner::MembershipExit {
4390                participant_index: participant.participant_index(),
4391            },
4392        });
4393    }
4394    if u128::from(order_cursor) != order_start + order_claims.total() {
4395        return Err(LiveFrontierTransitionError::ResultingFrontier);
4396    }
4397    Ok(OrderClaimFrontier {
4398        ledger: order_ledger,
4399        movable_claims: movable_order,
4400        immutable_candidates: alloc::vec![ImmutableOrderCandidateMajor {
4401            transaction_order: admission_order.transaction_order(),
4402            candidate_keys: alloc::vec![admission_order],
4403        }],
4404        recovery: None,
4405    })
4406}
4407
4408fn take_live_sequence(
4409    cursor: &mut DeliverySeq,
4410    length: u64,
4411) -> Result<DeliverySeq, LiveFrontierTransitionError> {
4412    let start = *cursor;
4413    *cursor = cursor
4414        .checked_add(length)
4415        .ok_or(LiveFrontierTransitionError::Exhausted)?;
4416    Ok(start)
4417}
4418
4419fn take_live_order(
4420    cursor: &mut TransactionOrder,
4421) -> Result<TransactionOrder, LiveFrontierTransitionError> {
4422    let value = *cursor;
4423    *cursor = cursor
4424        .checked_add(1)
4425        .ok_or(LiveFrontierTransitionError::Exhausted)?;
4426    Ok(value)
4427}
4428
4429fn ordinary_unaccepted_marker_anchors(frontiers: &ClaimFrontiers) -> Vec<DeliverySeq> {
4430    frontiers
4431        .marker_records
4432        .iter()
4433        .filter_map(|record| {
4434            let RetainedCausalRecordKind::CompactionMarker {
4435                participant_index, ..
4436            } = record.kind
4437            else {
4438                return None;
4439            };
4440            active_participant(&frontiers.active_identities, participant_index)
4441                .is_some_and(|participant| participant.cursor < record.delivery_seq)
4442                .then_some(record.delivery_seq)
4443        })
4444        .collect()
4445}
4446
4447fn projection_failure(
4448    frontiers: ClaimFrontiers,
4449    input: OrdinaryRecordProjectionInput,
4450    error: OrdinaryProjectionError,
4451) -> Box<OrdinaryRecordProjectionFailure> {
4452    Box::new(OrdinaryRecordProjectionFailure {
4453        frontiers,
4454        input,
4455        error,
4456    })
4457}
4458
4459fn preflight_ordinary_sequence_owners(
4460    sequence: &SequenceClaimFrontier,
4461    delta: u64,
4462) -> Result<(), OrdinaryProjectionError> {
4463    if !sequence.immutable_candidates.is_empty() {
4464        return Err(OrdinaryProjectionError::SequenceRelocation);
4465    }
4466    for claim in &sequence.movable_claims {
4467        claim
4468            .delivery_seq
4469            .checked_add(delta)
4470            .ok_or(OrdinaryProjectionError::SequenceRelocation)?;
4471    }
4472    for range in &sequence.products.live_times_terminal {
4473        range
4474            .start
4475            .checked_add(delta)
4476            .ok_or(OrdinaryProjectionError::SequenceRelocation)?;
4477    }
4478    if let Some(range) = &sequence.products.live_times_replacement_terminal {
4479        range
4480            .start
4481            .checked_add(delta)
4482            .ok_or(OrdinaryProjectionError::SequenceRelocation)?;
4483    }
4484    for range in &sequence.products.other_live_times_exit {
4485        range
4486            .start
4487            .checked_add(delta)
4488            .ok_or(OrdinaryProjectionError::SequenceRelocation)?;
4489    }
4490    if let Some(recovery) = &sequence.recovery {
4491        if let Some(terminal) = &recovery.terminal {
4492            terminal
4493                .delivery_seq
4494                .checked_add(delta)
4495                .ok_or(OrdinaryProjectionError::SequenceRelocation)?;
4496        }
4497        recovery
4498            .recovery_attach_seq
4499            .checked_add(delta)
4500            .ok_or(OrdinaryProjectionError::SequenceRelocation)?;
4501        recovery
4502            .replacement_terminal_seq
4503            .checked_add(delta)
4504            .ok_or(OrdinaryProjectionError::SequenceRelocation)?;
4505    }
4506    Ok(())
4507}
4508
4509fn relay_ordinary_sequence_owners(sequence: &mut SequenceClaimFrontier, delta: u64) {
4510    for claim in &mut sequence.movable_claims {
4511        claim.delivery_seq = claim.delivery_seq.wrapping_add(delta);
4512    }
4513    for range in &mut sequence.products.live_times_terminal {
4514        range.start = range.start.wrapping_add(delta);
4515    }
4516    if let Some(range) = &mut sequence.products.live_times_replacement_terminal {
4517        range.start = range.start.wrapping_add(delta);
4518    }
4519    for range in &mut sequence.products.other_live_times_exit {
4520        range.start = range.start.wrapping_add(delta);
4521    }
4522    if let Some(recovery) = &mut sequence.recovery {
4523        if let Some(terminal) = &mut recovery.terminal {
4524            terminal.delivery_seq = terminal.delivery_seq.wrapping_add(delta);
4525        }
4526        recovery.recovery_attach_seq = recovery.recovery_attach_seq.wrapping_add(delta);
4527        recovery.replacement_terminal_seq = recovery.replacement_terminal_seq.wrapping_add(delta);
4528    }
4529}
4530
4531fn rollback_ordinary_sequence_owners(sequence: &mut SequenceClaimFrontier, delta: u64) {
4532    for claim in &mut sequence.movable_claims {
4533        claim.delivery_seq = claim.delivery_seq.wrapping_sub(delta);
4534    }
4535    for range in &mut sequence.products.live_times_terminal {
4536        range.start = range.start.wrapping_sub(delta);
4537    }
4538    if let Some(range) = &mut sequence.products.live_times_replacement_terminal {
4539        range.start = range.start.wrapping_sub(delta);
4540    }
4541    for range in &mut sequence.products.other_live_times_exit {
4542        range.start = range.start.wrapping_sub(delta);
4543    }
4544    if let Some(recovery) = &mut sequence.recovery {
4545        if let Some(terminal) = &mut recovery.terminal {
4546            terminal.delivery_seq = terminal.delivery_seq.wrapping_sub(delta);
4547        }
4548        recovery.recovery_attach_seq = recovery.recovery_attach_seq.wrapping_sub(delta);
4549        recovery.replacement_terminal_seq = recovery.replacement_terminal_seq.wrapping_sub(delta);
4550    }
4551}
4552
4553fn preflight_ordinary_order_owners(
4554    order: &OrderClaimFrontier,
4555) -> Result<(), OrdinaryProjectionError> {
4556    if !order.immutable_candidates.is_empty() {
4557        return Err(OrdinaryProjectionError::OrderRelocation);
4558    }
4559    for claim in &order.movable_claims {
4560        claim
4561            .transaction_order
4562            .checked_add(1)
4563            .ok_or(OrdinaryProjectionError::OrderRelocation)?;
4564    }
4565    if let Some(recovery) = &order.recovery {
4566        if let Some(active_binding) = &recovery.active_binding {
4567            active_binding
4568                .transaction_order
4569                .checked_add(1)
4570                .ok_or(OrdinaryProjectionError::OrderRelocation)?;
4571        }
4572        recovery
4573            .recovery_operation_order
4574            .checked_add(1)
4575            .ok_or(OrdinaryProjectionError::OrderRelocation)?;
4576        recovery
4577            .replacement_terminal_order
4578            .checked_add(1)
4579            .ok_or(OrdinaryProjectionError::OrderRelocation)?;
4580    }
4581    Ok(())
4582}
4583
4584fn relay_ordinary_order_owners(order: &mut OrderClaimFrontier) {
4585    for claim in &mut order.movable_claims {
4586        claim.transaction_order = claim.transaction_order.wrapping_add(1);
4587    }
4588    if let Some(recovery) = &mut order.recovery {
4589        if let Some(active_binding) = &mut recovery.active_binding {
4590            active_binding.transaction_order = active_binding.transaction_order.wrapping_add(1);
4591        }
4592        recovery.recovery_operation_order = recovery.recovery_operation_order.wrapping_add(1);
4593        recovery.replacement_terminal_order = recovery.replacement_terminal_order.wrapping_add(1);
4594    }
4595}
4596
4597fn rollback_ordinary_order_owners(order: &mut OrderClaimFrontier) {
4598    for claim in &mut order.movable_claims {
4599        claim.transaction_order = claim.transaction_order.wrapping_sub(1);
4600    }
4601    if let Some(recovery) = &mut order.recovery {
4602        if let Some(active_binding) = &mut recovery.active_binding {
4603            active_binding.transaction_order = active_binding.transaction_order.wrapping_sub(1);
4604        }
4605        recovery.recovery_operation_order = recovery.recovery_operation_order.wrapping_sub(1);
4606        recovery.replacement_terminal_order = recovery.replacement_terminal_order.wrapping_sub(1);
4607    }
4608}
4609
4610/// Checks a leaving member against its frontier identity.
4611///
4612/// A Bound or pending member's cursor must equal its frontier cursor exactly.
4613/// A settled DETACHED member's frontier cursor may stand ABOVE its member
4614/// cursor, and only above. A non-finalizer binding fate carries the frontier's
4615/// copy of the cursor to the high watermark and leaves the member's
4616/// acknowledged boundary where it was
4617/// (`claim_frontier/binding_fate_transition.rs`); an attach reinstalls that
4618/// boundary and re-mints the marker anchors the carry crossed
4619/// (`docs/design/STOP-PATH-ANCHORS-2026-08-29.md:212-233`). The split is
4620/// deliberate: R-C3 (`docs/design/PARTICIPANT-CONTRACT.md:3158-3167`) needs the
4621/// gap between the two either re-offered or marked abandoned on that attach.
4622/// A Detached Leave releases the cursor and tombstones the member, so equality
4623/// is the wrong test there: requiring it made every killed-then-detached
4624/// member's ordinary Leave fail `Identity`
4625/// (`docs/design/DETACHED-LEAVE-GUARD-2026-10-07.md`). A frontier cursor BELOW
4626/// the member's is no fate's work and still refuses.
4627fn validate_leave_identity<F>(
4628    frontiers: &ClaimFrontiers,
4629    member: &LiveMember<F>,
4630    settled_detached: bool,
4631) -> Result<(), PrepareLeaveAuthorityError> {
4632    if member.conversation_id() != frontiers.conversation_id {
4633        return Err(PrepareLeaveAuthorityError::Conversation);
4634    }
4635    let Some(participant) =
4636        active_participant(&frontiers.active_identities, member.participant_id())
4637    else {
4638        return Err(PrepareLeaveAuthorityError::Identity);
4639    };
4640    let cursor_matches = if settled_detached {
4641        participant.cursor >= member.cursor()
4642    } else {
4643        participant.cursor == member.cursor()
4644    };
4645    if !cursor_matches {
4646        return Err(PrepareLeaveAuthorityError::Identity);
4647    }
4648    Ok(())
4649}
4650
4651fn validate_settled_leave_prestate<F>(
4652    frontiers: &ClaimFrontiers,
4653    member: &LiveMember<F>,
4654    binding_state: BindingState,
4655) -> Result<(ParticipantId, Option<BindingEpoch>), PrepareLeaveAuthorityError> {
4656    let participant_id = member.participant_id();
4657    validate_leave_identity(
4658        frontiers,
4659        member,
4660        matches!(binding_state, BindingState::Detached),
4661    )?;
4662    if !frontiers.order.immutable_candidates.is_empty() {
4663        return Err(PrepareLeaveAuthorityError::ImmutablePrefix);
4664    }
4665    let ended_binding_epoch = match binding_state {
4666        BindingState::Detached => {
4667            let Some(participant) =
4668                active_participant(&frontiers.active_identities, participant_id)
4669            else {
4670                return Err(PrepareLeaveAuthorityError::Identity);
4671            };
4672            if !matches!(participant.binding, FrontierBinding::Detached(_)) {
4673                return Err(PrepareLeaveAuthorityError::Binding);
4674            }
4675            None
4676        }
4677        BindingState::Bound(binding)
4678            if binding.conversation_id == frontiers.conversation_id
4679                && binding.participant_id == participant_id =>
4680        {
4681            let Some(participant) =
4682                active_participant(&frontiers.active_identities, participant_id)
4683            else {
4684                return Err(PrepareLeaveAuthorityError::Identity);
4685            };
4686            if participant.binding != FrontierBinding::Bound(binding.binding_epoch) {
4687                return Err(PrepareLeaveAuthorityError::Binding);
4688            }
4689            Some(binding.binding_epoch)
4690        }
4691        BindingState::Bound(_) | BindingState::PendingFinalization(_) => {
4692            return Err(PrepareLeaveAuthorityError::Binding);
4693        }
4694    };
4695    Ok((participant_id, ended_binding_epoch))
4696}
4697
4698fn validate_pending_leave_prestate<F>(
4699    frontiers: &ClaimFrontiers,
4700    member: &LiveMember<F>,
4701    pending: PendingFinalization,
4702) -> Result<(ParticipantId, super::AdmissionOrder), PrepareLeaveAuthorityError> {
4703    let participant_id = member.participant_id();
4704    // Pending: the fate has not completed, so nothing has carried the
4705    // frontier cursor and exact equality still holds.
4706    validate_leave_identity(frontiers, member, false)?;
4707    if pending.conversation_id() != frontiers.conversation_id
4708        || pending.participant_id() != participant_id
4709    {
4710        return Err(PrepareLeaveAuthorityError::Binding);
4711    }
4712    let Some(participant) = active_participant(&frontiers.active_identities, participant_id) else {
4713        return Err(PrepareLeaveAuthorityError::Identity);
4714    };
4715    if participant.binding != FrontierBinding::Detached(pending.binding_epoch()) {
4716        return Err(PrepareLeaveAuthorityError::Binding);
4717    }
4718    let expected_order = pending.admission_order();
4719    let exact_sequence_candidate = matches!(
4720        frontiers.sequence.immutable_candidates.as_slice(),
4721        [ImmutableSequenceCandidate::BindingTerminal {
4722            admission_order,
4723            owner,
4724            ..
4725        }] if *admission_order == expected_order
4726            && owner.participant_index == participant_id
4727            && owner.binding_epoch == pending.binding_epoch()
4728    );
4729    let exact_order_candidate = matches!(
4730        frontiers.order.immutable_candidates.as_slice(),
4731        [ImmutableOrderCandidateMajor {
4732            transaction_order,
4733            candidate_keys,
4734        }] if *transaction_order == expected_order.transaction_order()
4735            && candidate_keys.as_slice() == [expected_order]
4736    );
4737    if !exact_sequence_candidate || !exact_order_candidate {
4738        return Err(PrepareLeaveAuthorityError::PendingCandidate);
4739    }
4740    Ok((participant_id, expected_order))
4741}
4742
4743#[derive(Clone, Copy)]
4744enum LeaveRelayUnit {
4745    Direct(MovableOrderClaim),
4746    Recovery(RecoveryOrderBlock),
4747}
4748
4749impl LeaveRelayUnit {
4750    fn start(self) -> TransactionOrder {
4751        match self {
4752            Self::Direct(claim) => claim.transaction_order,
4753            Self::Recovery(block) => block_start_validated_order(block),
4754        }
4755    }
4756
4757    const fn len(self) -> u64 {
4758        match self {
4759            Self::Direct(_) => 1,
4760            Self::Recovery(block) => match block.active_binding {
4761                Some(_) => 3,
4762                None => 2,
4763            },
4764        }
4765    }
4766}
4767
4768struct LeaveOrderSelection {
4769    units: Vec<LeaveRelayUnit>,
4770    selected_major: TransactionOrder,
4771}
4772
4773fn select_leave_order(
4774    order: &OrderClaimFrontier,
4775    participant_id: ParticipantId,
4776    ended_binding_epoch: Option<BindingEpoch>,
4777    pending_order: Option<super::AdmissionOrder>,
4778) -> Result<LeaveOrderSelection, PrepareLeaveAuthorityError> {
4779    let Some(exit_index) = order.movable_claims.iter().position(|claim| {
4780        claim.owner
4781            == OrderDirectOwner::MembershipExit {
4782                participant_index: participant_id,
4783            }
4784    }) else {
4785        return Err(PrepareLeaveAuthorityError::MembershipExitClaim);
4786    };
4787    let exit_claim = order.movable_claims[exit_index];
4788    if pending_order
4789        .is_some_and(|pending| pending.transaction_order() >= exit_claim.transaction_order)
4790    {
4791        return Err(PrepareLeaveAuthorityError::PendingCandidate);
4792    }
4793    let active_index = matching_active_claim(order, participant_id, ended_binding_epoch)?;
4794    let mut units = Vec::new();
4795    for (index, claim) in order.movable_claims.iter().copied().enumerate() {
4796        if index != exit_index && Some(index) != active_index {
4797            units.push(LeaveRelayUnit::Direct(claim));
4798        }
4799    }
4800    if let Some(recovery) = order
4801        .recovery
4802        .filter(|recovery| recovery.participant_index != participant_id)
4803    {
4804        units.push(LeaveRelayUnit::Recovery(recovery));
4805    }
4806    units.sort_by_key(|unit| unit.start());
4807    let surviving_handles: u128 = units.iter().map(|unit| u128::from(unit.len())).sum();
4808    let exit_major = exit_claim.transaction_order;
4809    let later_handle_fits = u128::from(u64::MAX - exit_major) >= surviving_handles;
4810    let selected_major = if later_handle_fits {
4811        exit_major
4812    } else if pending_order.is_some() {
4813        return Err(PrepareLeaveAuthorityError::OrderCapacity);
4814    } else {
4815        u64::try_from(order_frontier_start(order.ledger.high()))
4816            .map_err(|_| PrepareLeaveAuthorityError::OrderCapacity)?
4817    };
4818    Ok(LeaveOrderSelection {
4819        units,
4820        selected_major,
4821    })
4822}
4823
4824fn matching_active_claim(
4825    order: &OrderClaimFrontier,
4826    participant_id: ParticipantId,
4827    ended_binding_epoch: Option<BindingEpoch>,
4828) -> Result<Option<usize>, PrepareLeaveAuthorityError> {
4829    let Some(binding_epoch) = ended_binding_epoch else {
4830        return Ok(None);
4831    };
4832    let expected = OrderDirectOwner::ActiveBindingTerminal(BindingTerminalOwner {
4833        participant_index: participant_id,
4834        binding_epoch,
4835    });
4836    order
4837        .movable_claims
4838        .iter()
4839        .position(|claim| claim.owner == expected)
4840        .map(Some)
4841        .ok_or(PrepareLeaveAuthorityError::ActiveBindingClaim)
4842}
4843
4844fn relay_leave_order_units(
4845    units: Vec<LeaveRelayUnit>,
4846    selected_major: TransactionOrder,
4847) -> Result<(Vec<MovableOrderClaim>, Option<RecoveryOrderBlock>), PrepareLeaveAuthorityError> {
4848    let mut cursor = selected_major.checked_add(1);
4849    let mut movable_claims = Vec::new();
4850    let mut recovery = None;
4851    for unit in units {
4852        match unit {
4853            LeaveRelayUnit::Direct(mut claim) => {
4854                let Some(position) = cursor else {
4855                    return Err(PrepareLeaveAuthorityError::OrderCapacity);
4856                };
4857                claim.transaction_order = position;
4858                movable_claims.push(claim);
4859                cursor = position.checked_add(1);
4860            }
4861            LeaveRelayUnit::Recovery(block) => {
4862                let (relayed, next) = relay_recovery_block(block, cursor)?;
4863                recovery = Some(relayed);
4864                cursor = next;
4865            }
4866        }
4867    }
4868    movable_claims.sort_by_key(|claim| claim.transaction_order);
4869    Ok((movable_claims, recovery))
4870}
4871
4872const fn relay_recovery_block(
4873    block: RecoveryOrderBlock,
4874    cursor: Option<TransactionOrder>,
4875) -> Result<(RecoveryOrderBlock, Option<TransactionOrder>), PrepareLeaveAuthorityError> {
4876    let mut next = cursor;
4877    let active_binding = if let Some(mut active) = block.active_binding {
4878        let Some(position) = next else {
4879            return Err(PrepareLeaveAuthorityError::OrderCapacity);
4880        };
4881        active.transaction_order = position;
4882        next = position.checked_add(1);
4883        Some(active)
4884    } else {
4885        None
4886    };
4887    let Some(recovery_operation_order) = next else {
4888        return Err(PrepareLeaveAuthorityError::OrderCapacity);
4889    };
4890    let Some(replacement_terminal_order) = recovery_operation_order.checked_add(1) else {
4891        return Err(PrepareLeaveAuthorityError::OrderCapacity);
4892    };
4893    Ok((
4894        RecoveryOrderBlock {
4895            active_binding,
4896            recovery_operation_order,
4897            replacement_terminal_order,
4898            participant_index: block.participant_index,
4899            marker_delivery_seq: block.marker_delivery_seq,
4900            recovered_binding_epoch: block.recovered_binding_epoch,
4901        },
4902        replacement_terminal_order.checked_add(1),
4903    ))
4904}
4905
4906fn leave_resulting_order_ledger(
4907    selected_major: TransactionOrder,
4908    movable_claims: &[MovableOrderClaim],
4909    recovery: Option<RecoveryOrderBlock>,
4910) -> Result<OrderLedger, PrepareLeaveAuthorityError> {
4911    let active_binding_terminals =
4912        usize_to_u64(
4913            movable_claims
4914                .iter()
4915                .filter(|claim| matches!(claim.owner, OrderDirectOwner::ActiveBindingTerminal(_)))
4916                .count(),
4917        ) + u64::from(recovery.is_some_and(|block| block.active_binding.is_some()));
4918    let membership_exits = usize_to_u64(
4919        movable_claims
4920            .iter()
4921            .filter(|claim| matches!(claim.owner, OrderDirectOwner::MembershipExit { .. }))
4922            .count(),
4923    );
4924    let has_recovery = recovery.is_some();
4925    let resulting_claims = OrderClaims::new(
4926        active_binding_terminals,
4927        membership_exits,
4928        has_recovery,
4929        has_recovery,
4930    )
4931    .map_err(|_| PrepareLeaveAuthorityError::ResultingOrderLedger)?;
4932    OrderLedger::try_new(OrderHigh::Allocated(selected_major), resulting_claims)
4933        .map_err(|_| PrepareLeaveAuthorityError::ResultingOrderLedger)
4934}
4935
4936fn consume_leave_order_lane(
4937    order: &mut OrderClaimFrontier,
4938    participant_id: ParticipantId,
4939    ended_binding_epoch: Option<BindingEpoch>,
4940    pending_order: Option<super::AdmissionOrder>,
4941) -> Result<TransactionOrder, PrepareLeaveAuthorityError> {
4942    let selection = select_leave_order(order, participant_id, ended_binding_epoch, pending_order)?;
4943    let (movable_claims, recovery) =
4944        relay_leave_order_units(selection.units, selection.selected_major)?;
4945    let ledger = leave_resulting_order_ledger(selection.selected_major, &movable_claims, recovery)?;
4946    *order = OrderClaimFrontier {
4947        ledger,
4948        movable_claims,
4949        immutable_candidates: Vec::new(),
4950        recovery,
4951    };
4952    Ok(selection.selected_major)
4953}
4954
4955impl ClaimFrontiersPrevalidated {
4956    /// Returns the conversation whose raw closure edge must be restored.
4957    #[must_use]
4958    pub(super) const fn conversation_id(&self) -> ConversationId {
4959        self.conversation_id
4960    }
4961
4962    /// Consumes the sole retained-marker authority needed by cold edge restore.
4963    ///
4964    /// A second request is refused even for the same sequence. The returned
4965    /// token is non-cloneable and binds conversation, record key, target
4966    /// participant, and exact current/last binding epoch.
4967    pub(super) fn take_marker_record(
4968        &mut self,
4969        request: MarkerRecordRequest,
4970    ) -> Option<ValidatedMarkerRecord> {
4971        if self.issued_marker_record.is_some() {
4972            return None;
4973        }
4974        let record = self
4975            .retained_records
4976            .iter()
4977            .find(|record| record.delivery_seq == request.marker_delivery_seq)
4978            .copied()?;
4979        let RetainedCausalRecordKind::CompactionMarker {
4980            participant_index,
4981            provenance,
4982        } = record.kind
4983        else {
4984            return None;
4985        };
4986        if participant_index != request.participant_index {
4987            return None;
4988        }
4989        let participant = active_participant(&self.active_identities, participant_index)?;
4990        let historical_delivery = self
4991            .historical_marker_deliveries
4992            .iter()
4993            .find(|authority| authority.marker_delivery_seq == request.marker_delivery_seq);
4994        let (target_binding, occurrence) = match request.use_kind {
4995            MarkerRecordUse::Planned(target) => {
4996                if participant.binding != target || historical_delivery.is_some() {
4997                    return None;
4998                }
4999                (target, MarkerRecordOccurrence::Undelivered)
5000            }
5001            MarkerRecordUse::Delivered(target) => {
5002                let delivered_binding_epoch = binding_epoch(target);
5003                if participant.binding != target
5004                    || !historical_delivery.is_some_and(|authority| {
5005                        authority.participant_index == participant_index
5006                            && authority.delivered_binding_epoch == delivered_binding_epoch
5007                    })
5008                {
5009                    return None;
5010                }
5011                (target, MarkerRecordOccurrence::Delivered)
5012            }
5013            MarkerRecordUse::Recovered {
5014                prior_binding_epoch,
5015                recovered_binding_epoch,
5016            } => {
5017                if participant.binding != FrontierBinding::Detached(recovered_binding_epoch)
5018                    || !historical_delivery.is_some_and(|authority| {
5019                        authority.participant_index == participant_index
5020                            && authority.delivered_binding_epoch == prior_binding_epoch
5021                    })
5022                {
5023                    return None;
5024                }
5025                (
5026                    FrontierBinding::Detached(prior_binding_epoch),
5027                    MarkerRecordOccurrence::Delivered,
5028                )
5029            }
5030        };
5031        self.issued_marker_record = Some(request);
5032        Some(ValidatedMarkerRecord {
5033            conversation_id: self.conversation_id,
5034            record,
5035            provenance,
5036            target_binding,
5037            occurrence,
5038            seal: MarkerAuthoritySeal::Validated,
5039        })
5040    }
5041
5042    /// Completes logical-owner restoration after storage has rebuilt its exact
5043    /// typed closure edge from any sealed retained-marker token.
5044    pub(super) fn finish(
5045        self,
5046        edge: Option<super::StoredEdge>,
5047    ) -> Result<ClaimFrontiers, ParticipantStateCorruptReason> {
5048        self.validate_current_marker_edge(edge)?;
5049        self.validate_historical_delivery_consumers(edge)?;
5050        let recovery_provenance = self.resolve_recovery_provenance(edge)?;
5051        let sequence = restore_sequence_frontier(
5052            &self.active_identities,
5053            self.sequence_restore,
5054            self.retained_floor,
5055            self.sequence_ledger,
5056            recovery_provenance,
5057            &self.retained_records,
5058            &self.historical_causal_authorities,
5059        )
5060        .map_err(corrupt_frontier)?;
5061        let order = restore_order_frontier(
5062            &self.active_identities,
5063            self.order_restore,
5064            self.order_ledger,
5065            recovery_provenance,
5066        )
5067        .map_err(corrupt_frontier)?;
5068        validate_cross_counter(&sequence, &order).map_err(corrupt_frontier)?;
5069        Ok(ClaimFrontiers {
5070            ordinary_cursor_release: None,
5071            conversation_id: self.conversation_id,
5072            active_identities: self.active_identities,
5073            identity_slot_limit: self.identity_slot_limit,
5074            retained_floor: self.retained_floor,
5075            retained_records: self.retained_records,
5076            marker_records: self.marker_records,
5077            fenced_marker_issued: self.fenced_marker_issued,
5078            sequence,
5079            order,
5080            historical_marker_deliveries: self.historical_marker_deliveries,
5081            historical_causal_authorities: self.historical_causal_authorities,
5082        })
5083    }
5084
5085    fn validate_historical_delivery_consumers(
5086        &self,
5087        edge: Option<super::StoredEdge>,
5088    ) -> Result<(), ParticipantStateCorruptReason> {
5089        for history in &self.historical_marker_deliveries {
5090            let recovered_origin = self.binding_origins.iter().any(|origin| {
5091                origin.participant_id() == history.participant_index
5092                    && origin.recovered_marker()
5093                        == Some((history.marker_delivery_seq, history.delivered_binding_epoch))
5094            });
5095            let current_marker_edge = match edge {
5096                Some(super::StoredEdge::ParticipantCursorProgress(progress)) => {
5097                    progress.participant_id() == history.participant_index
5098                        && progress.marker_delivery_seq() == Some(history.marker_delivery_seq)
5099                        && progress.binding_epoch() == history.delivered_binding_epoch
5100                }
5101                Some(super::StoredEdge::DetachedCredentialRecovery(recovery)) => {
5102                    recovery.participant_id() == history.participant_index
5103                        && recovery.marker_delivery_seq() == history.marker_delivery_seq
5104                        && recovery.prior_binding_epoch() == history.delivered_binding_epoch
5105                }
5106                Some(
5107                    super::StoredEdge::ObserverProjection(_)
5108                    | super::StoredEdge::PhysicalCompaction(_)
5109                    | super::StoredEdge::MarkerDelivery(_)
5110                    | super::StoredEdge::DetachedMarkerRelease(_)
5111                    | super::StoredEdge::DetachedCursorRelease(_),
5112                )
5113                | None => false,
5114            };
5115            if !recovered_origin && !current_marker_edge {
5116                return Err(self.marker_corruption(history.marker_delivery_seq));
5117            }
5118        }
5119        Ok(())
5120    }
5121
5122    fn validate_current_marker_edge(
5123        &self,
5124        edge: Option<super::StoredEdge>,
5125    ) -> Result<(), ParticipantStateCorruptReason> {
5126        if let Some(MarkerRecordRequest {
5127            participant_index,
5128            marker_delivery_seq,
5129            use_kind:
5130                MarkerRecordUse::Recovered {
5131                    recovered_binding_epoch,
5132                    ..
5133                },
5134        }) = self.issued_marker_record
5135        {
5136            let recovered_edge_matches = matches!(
5137                edge,
5138                Some(super::StoredEdge::DetachedCursorRelease(release))
5139                    if release.participant_id() == participant_index
5140                        && release.last_dead_binding_epoch() == recovered_binding_epoch
5141            );
5142            if !recovered_edge_matches {
5143                return Err(self.marker_corruption(marker_delivery_seq));
5144            }
5145            return Ok(());
5146        }
5147        let Some(context) = edge.and_then(marker_edge_context) else {
5148            return Ok(());
5149        };
5150        if self
5151            .issued_marker_record
5152            .is_none_or(|request| request.marker_delivery_seq != context.marker_delivery_seq)
5153            || !self.marker_context_matches(context)
5154        {
5155            return Err(self.marker_corruption(context.marker_delivery_seq));
5156        }
5157        Ok(())
5158    }
5159
5160    fn resolve_recovery_provenance(
5161        &self,
5162        edge: Option<super::StoredEdge>,
5163    ) -> Result<Option<RecoveryClaimProvenance>, ParticipantStateCorruptReason> {
5164        let Some(marker_delivery_seq) = self.recovery_marker_delivery_seq else {
5165            return Ok(None);
5166        };
5167
5168        if let Some(super::StoredEdge::DetachedCredentialRecovery(recovery)) = edge {
5169            return self
5170                .resolve_postfate_recovery_provenance(marker_delivery_seq, recovery)
5171                .map(Some);
5172        }
5173
5174        let candidate =
5175            self.sequence_restore.immutable_candidates.iter().find_map(
5176                |candidate| match candidate {
5177                    ImmutableSequenceCandidate::Marker(marker)
5178                        if marker.delivery_seq == marker_delivery_seq
5179                            && matches!(marker.target_binding, FrontierBinding::Bound(_)) =>
5180                    {
5181                        Some(*marker)
5182                    }
5183                    _ => None,
5184                },
5185            );
5186        let retained = self.retained_records.iter().find_map(|record| {
5187            let RetainedCausalRecordKind::CompactionMarker {
5188                participant_index, ..
5189            } = record.kind
5190            else {
5191                return None;
5192            };
5193            if record.delivery_seq != marker_delivery_seq {
5194                return None;
5195            }
5196            let participant = active_participant(&self.active_identities, participant_index)?;
5197            let FrontierBinding::Bound(binding_epoch) = participant.binding else {
5198                return None;
5199            };
5200            let historical = self.historical_marker_deliveries.iter().find(|history| {
5201                history.participant_index == participant_index
5202                    && history.marker_delivery_seq == marker_delivery_seq
5203            })?;
5204            Some((
5205                participant_index,
5206                binding_epoch,
5207                historical.delivered_binding_epoch,
5208            ))
5209        });
5210        let provenance = match (candidate, retained) {
5211            (Some(marker), None) => RecoveryClaimProvenance {
5212                participant_index: marker.admission_order.participant_index(),
5213                marker_delivery_seq,
5214                prior_binding_epoch: binding_epoch(marker.target_binding),
5215                current_binding_epoch: binding_epoch(marker.target_binding),
5216                phase: RecoveryClaimPhase::PreFate,
5217            },
5218            (None, Some((participant_index, current_binding_epoch, prior_binding_epoch))) => {
5219                let phase = if current_binding_epoch == prior_binding_epoch {
5220                    let Some(context) = edge.and_then(marker_edge_context) else {
5221                        return Err(self.marker_corruption(marker_delivery_seq));
5222                    };
5223                    if context.marker_delivery_seq != marker_delivery_seq
5224                        || context.participant_index != participant_index
5225                        || context.binding_epoch != prior_binding_epoch
5226                        || context.target_binding != FrontierBinding::Bound(prior_binding_epoch)
5227                    {
5228                        return Err(self.marker_corruption(marker_delivery_seq));
5229                    }
5230                    RecoveryClaimPhase::PreFate
5231                } else {
5232                    let recovered_origin_matches = self.binding_origins.iter().any(|origin| {
5233                        origin.participant_id() == participant_index
5234                            && origin.binding_epoch() == current_binding_epoch
5235                            && origin.recovered_marker()
5236                                == Some((marker_delivery_seq, prior_binding_epoch))
5237                    });
5238                    if !recovered_origin_matches
5239                        || !matches!(
5240                            edge,
5241                            Some(
5242                                super::StoredEdge::ObserverProjection(_)
5243                                    | super::StoredEdge::PhysicalCompaction(_)
5244                            )
5245                        )
5246                    {
5247                        return Err(self.marker_corruption(marker_delivery_seq));
5248                    }
5249                    RecoveryClaimPhase::RecoveredBound
5250                };
5251                RecoveryClaimProvenance {
5252                    participant_index,
5253                    marker_delivery_seq,
5254                    prior_binding_epoch,
5255                    current_binding_epoch,
5256                    phase,
5257                }
5258            }
5259            (Some(_), Some(_)) | (None, None) => {
5260                return Err(self.marker_corruption(marker_delivery_seq));
5261            }
5262        };
5263        Ok(Some(provenance))
5264    }
5265
5266    fn resolve_postfate_recovery_provenance(
5267        &self,
5268        marker_delivery_seq: DeliverySeq,
5269        recovery: super::DetachedCredentialRecovery,
5270    ) -> Result<RecoveryClaimProvenance, ParticipantStateCorruptReason> {
5271        let provenance = RecoveryClaimProvenance {
5272            participant_index: recovery.participant_id(),
5273            marker_delivery_seq: recovery.marker_delivery_seq(),
5274            prior_binding_epoch: recovery.prior_binding_epoch(),
5275            current_binding_epoch: recovery.prior_binding_epoch(),
5276            phase: RecoveryClaimPhase::PostFate,
5277        };
5278        let context = MarkerEdgeContext {
5279            participant_index: provenance.participant_index,
5280            marker_delivery_seq: provenance.marker_delivery_seq,
5281            binding_epoch: provenance.prior_binding_epoch,
5282            target_binding: FrontierBinding::Detached(provenance.prior_binding_epoch),
5283        };
5284        if marker_delivery_seq != provenance.marker_delivery_seq
5285            || self
5286                .issued_marker_record
5287                .is_none_or(|request| request.marker_delivery_seq != marker_delivery_seq)
5288            || !self.marker_context_matches(context)
5289        {
5290            return Err(self.marker_corruption(marker_delivery_seq));
5291        }
5292        Ok(provenance)
5293    }
5294
5295    fn marker_context_matches(&self, context: MarkerEdgeContext) -> bool {
5296        self.marker_records.iter().any(|record| {
5297            matches!(
5298                record.kind,
5299                RetainedCausalRecordKind::CompactionMarker {
5300                    participant_index,
5301                    ..
5302                } if participant_index == context.participant_index
5303            ) && record.delivery_seq == context.marker_delivery_seq
5304                && active_participant(&self.active_identities, context.participant_index)
5305                    .is_some_and(|participant| participant.binding == context.target_binding)
5306        })
5307    }
5308
5309    fn marker_corruption(&self, delivery_seq: DeliverySeq) -> ParticipantStateCorruptReason {
5310        corrupt_frontier(sequence_error(
5311            sequence_ordinal(self.sequence_ledger, delivery_seq),
5312            ClaimFrontierInvalidReason::RecoveryBlock,
5313        ))
5314    }
5315}
5316
5317#[derive(Clone, Copy, Debug, PartialEq, Eq)]
5318struct MarkerEdgeContext {
5319    participant_index: ParticipantId,
5320    marker_delivery_seq: DeliverySeq,
5321    binding_epoch: BindingEpoch,
5322    target_binding: FrontierBinding,
5323}
5324
5325fn marker_edge_context(edge: super::StoredEdge) -> Option<MarkerEdgeContext> {
5326    match edge {
5327        super::StoredEdge::MarkerDelivery(delivery) => Some(MarkerEdgeContext {
5328            participant_index: delivery.participant_id(),
5329            marker_delivery_seq: delivery.marker_delivery_seq(),
5330            binding_epoch: delivery.binding_epoch(),
5331            target_binding: FrontierBinding::Bound(delivery.binding_epoch()),
5332        }),
5333        super::StoredEdge::ParticipantCursorProgress(progress) => {
5334            let marker_delivery_seq = progress.marker_delivery_seq()?;
5335            Some(MarkerEdgeContext {
5336                participant_index: progress.participant_id(),
5337                marker_delivery_seq,
5338                binding_epoch: progress.binding_epoch(),
5339                target_binding: FrontierBinding::Bound(progress.binding_epoch()),
5340            })
5341        }
5342        super::StoredEdge::DetachedCredentialRecovery(recovery) => Some(MarkerEdgeContext {
5343            participant_index: recovery.participant_id(),
5344            marker_delivery_seq: recovery.marker_delivery_seq(),
5345            binding_epoch: recovery.prior_binding_epoch(),
5346            target_binding: FrontierBinding::Detached(recovery.prior_binding_epoch()),
5347        }),
5348        super::StoredEdge::DetachedMarkerRelease(release) => Some(MarkerEdgeContext {
5349            participant_index: release.participant_id(),
5350            marker_delivery_seq: release.marker_delivery_seq(),
5351            binding_epoch: release.last_dead_binding_epoch(),
5352            target_binding: FrontierBinding::Detached(release.last_dead_binding_epoch()),
5353        }),
5354        super::StoredEdge::ObserverProjection(_)
5355        | super::StoredEdge::PhysicalCompaction(_)
5356        | super::StoredEdge::DetachedCursorRelease(_) => None,
5357    }
5358}
5359
5360#[derive(Clone, Copy, Debug, PartialEq, Eq)]
5361#[repr(u8)]
5362enum SequenceClass {
5363    Exit = 0,
5364    Terminal = 1,
5365    Marker = 2,
5366    RecoveryAttach = 3,
5367    RecoveryReplacementTerminal = 4,
5368    LiveTimesTerminal = 5,
5369    LiveTimesReplacementTerminal = 6,
5370    OtherLiveTimesExit = 7,
5371}
5372
5373#[derive(Clone, Copy, Debug, PartialEq, Eq)]
5374#[repr(u8)]
5375enum OrderClass {
5376    ActiveBindingTerminal = 0,
5377    MembershipExit = 1,
5378    RecoveryOperation = 2,
5379    RecoveryReplacementTerminal = 3,
5380}
5381
5382#[derive(Clone, Copy, Debug, PartialEq, Eq)]
5383struct NumericSegment<C> {
5384    start: u128,
5385    length: u128,
5386    class: Option<C>,
5387    immutable: bool,
5388}
5389
5390fn first_duplicate_candidate_key(
5391    candidates: &[ImmutableSequenceCandidate],
5392    retained_records: &[RetainedCausalRecord],
5393) -> Option<super::AdmissionOrder> {
5394    let mut keys: Vec<_> = candidates
5395        .iter()
5396        .map(|candidate| candidate.admission_order())
5397        .chain(retained_records.iter().map(|record| record.admission_order))
5398        .collect();
5399    keys.sort_unstable();
5400    let mut previous = None;
5401    for key in keys {
5402        if previous == Some(key) {
5403            return Some(key);
5404        }
5405        previous = Some(key);
5406    }
5407    None
5408}
5409
5410fn validate_unique_candidate_keys(
5411    candidates: &[ImmutableSequenceCandidate],
5412    retained_records: &[RetainedCausalRecord],
5413) -> Result<(), ParticipantStateCorruptReason> {
5414    let Some(order) = first_duplicate_candidate_key(candidates, retained_records) else {
5415        return Ok(());
5416    };
5417    Err(ParticipantStateCorruptReason::DuplicateCandidateKey {
5418        transaction_order: order.transaction_order(),
5419        candidate_phase: order.candidate_phase(),
5420        participant_index: order.participant_index(),
5421    })
5422}
5423
5424fn validate_sequence_numeric(
5425    restore: &SequenceClaimFrontierRestore,
5426    ledger: SequenceLedger,
5427) -> Result<(), ClaimFrontierError> {
5428    let mut segments = sequence_segments(restore);
5429    validate_numeric_segments(
5430        ClaimFrontierCounter::DeliverySequence,
5431        u128::from(ledger.high_watermark()) + 1,
5432        ledger.required_reserve(),
5433        &mut segments,
5434        &sequence_expected_counts(ledger),
5435    )
5436}
5437
5438fn validate_order_numeric(
5439    restore: &OrderClaimFrontierRestore,
5440    ledger: OrderLedger,
5441) -> Result<(), ClaimFrontierError> {
5442    let mut segments = order_segments(restore, ledger.high());
5443    validate_numeric_segments(
5444        ClaimFrontierCounter::TransactionOrder,
5445        order_frontier_start(ledger.high()),
5446        order_frontier_candidate_count(restore, ledger.high()) + ledger.claims().total(),
5447        &mut segments,
5448        &order_expected_counts(ledger),
5449    )
5450}
5451
5452fn validate_bounded_shape(
5453    restore: &ClaimFrontiersRestore,
5454    sequence_ledger: SequenceLedger,
5455) -> Result<(), ClaimFrontierError> {
5456    let identity_limit = u128::from(restore.identity_slot_limit);
5457    let twice_identity_limit = identity_limit.saturating_mul(2);
5458    let order_candidate_keys = restore
5459        .order
5460        .immutable_candidates
5461        .iter()
5462        .fold(0_u128, |count, candidate| {
5463            count.saturating_add(usize_to_u128(candidate.candidate_keys.len()))
5464        });
5465    let bounded = usize_to_u128(restore.active_identities.len()) <= identity_limit
5466        && usize_to_u128(restore.sequence.movable_claims.len()) <= twice_identity_limit
5467        && usize_to_u128(restore.sequence.immutable_candidates.len()) <= twice_identity_limit
5468        && usize_to_u128(restore.sequence.products.live_times_terminal.len()) <= identity_limit
5469        && usize_to_u128(restore.sequence.products.other_live_times_exit.len()) <= identity_limit
5470        && usize_to_u128(restore.historical_marker_deliveries.len())
5471            <= u128::from(restore.retained_record_limit)
5472        && usize_to_u128(restore.historical_causal_facts.len()) <= twice_identity_limit
5473        && usize_to_u128(restore.order.movable_claims.len()) <= twice_identity_limit
5474        && usize_to_u128(restore.order.immutable_candidates.len()) <= twice_identity_limit
5475        && order_candidate_keys <= twice_identity_limit;
5476    if bounded {
5477        Ok(())
5478    } else {
5479        Err(sequence_error(
5480            sequence_ledger.required_reserve(),
5481            ClaimFrontierInvalidReason::LogicalOwner,
5482        ))
5483    }
5484}
5485
5486fn validated_retained_records(
5487    mut records: Vec<RetainedCausalRecord>,
5488    retained_floor: u128,
5489    retained_record_limit: u64,
5490    identity_slot_limit: u64,
5491    ledger: SequenceLedger,
5492) -> Result<Vec<RetainedCausalRecord>, ClaimFrontierError> {
5493    let high_end = u128::from(ledger.high_watermark()) + 1;
5494    let expected_count = high_end.saturating_sub(retained_floor);
5495    if retained_floor > high_end
5496        || usize_to_u128(records.len()) > u128::from(retained_record_limit)
5497        || usize_to_u128(records.len()) != expected_count
5498    {
5499        return Err(sequence_error(
5500            usize_to_u128(records.len()).min(expected_count),
5501            ClaimFrontierInvalidReason::LogicalOwner,
5502        ));
5503    }
5504    records.sort_by_key(|record| record.delivery_seq);
5505    let mut previous_admission_order = None;
5506    for (record_index, record) in records.iter().enumerate() {
5507        let (participant_index, valid_kind) = match record.kind {
5508            RetainedCausalRecordKind::BindingTerminal(owner) => (
5509                owner.participant_index,
5510                record.admission_order.candidate_phase() == CandidatePhase::BindingTerminal
5511                    && record.admission_order.participant_index() == owner.participant_index,
5512            ),
5513            RetainedCausalRecordKind::MembershipExit { participant_index } => (
5514                participant_index,
5515                record.admission_order.candidate_phase() == CandidatePhase::MembershipExit
5516                    && record.admission_order.participant_index() == participant_index,
5517            ),
5518            RetainedCausalRecordKind::AttachLifecycle {
5519                participant_index, ..
5520            } => (
5521                participant_index,
5522                record.admission_order.candidate_phase() == CandidatePhase::AttachLifecycle
5523                    && record.admission_order.participant_index() == participant_index,
5524            ),
5525            RetainedCausalRecordKind::OrdinaryRecord { participant_index } => (
5526                participant_index,
5527                record.admission_order.candidate_phase() == CandidatePhase::OrdinaryRecord
5528                    && record.admission_order.participant_index() == participant_index,
5529            ),
5530            RetainedCausalRecordKind::CompactionMarker {
5531                participant_index,
5532                provenance,
5533            } => (
5534                participant_index,
5535                record.admission_order.candidate_phase() == CandidatePhase::CompactionMarker
5536                    && record.admission_order.participant_index() == participant_index
5537                    && marker_provenance_targets(provenance, participant_index),
5538            ),
5539        };
5540        let expected_sequence = retained_floor + rank_index(record_index);
5541        if participant_index >= identity_slot_limit
5542            || !valid_kind
5543            || previous_admission_order.is_some_and(|previous| previous >= record.admission_order)
5544            || u128::from(record.delivery_seq) != expected_sequence
5545        {
5546            return Err(sequence_error(
5547                rank_index(record_index),
5548                ClaimFrontierInvalidReason::CandidateKey,
5549            ));
5550        }
5551        previous_admission_order = Some(record.admission_order);
5552    }
5553    Ok(records)
5554}
5555
5556fn validated_active_marker_records(
5557    retained_markers: &[RetainedCausalRecord],
5558    mut active_marker_anchors: Vec<DeliverySeq>,
5559    identity_slot_limit: u64,
5560    ledger: SequenceLedger,
5561) -> Result<Vec<RetainedCausalRecord>, ClaimFrontierError> {
5562    if usize_to_u128(active_marker_anchors.len()) > u128::from(identity_slot_limit) {
5563        return Err(sequence_error(
5564            ledger.required_reserve(),
5565            ClaimFrontierInvalidReason::LogicalOwner,
5566        ));
5567    }
5568    active_marker_anchors.sort_unstable();
5569    let mut previous_sequence = None;
5570    let mut owners = Vec::new();
5571    let mut active_records = Vec::new();
5572    for delivery_seq in active_marker_anchors {
5573        let Some(record) = retained_markers
5574            .iter()
5575            .find(|record| record.delivery_seq == delivery_seq)
5576            .copied()
5577        else {
5578            return Err(sequence_error(
5579                sequence_ordinal(ledger, delivery_seq),
5580                ClaimFrontierInvalidReason::LogicalOwner,
5581            ));
5582        };
5583        let RetainedCausalRecordKind::CompactionMarker {
5584            participant_index, ..
5585        } = record.kind
5586        else {
5587            return Err(sequence_error(
5588                sequence_ordinal(ledger, delivery_seq),
5589                ClaimFrontierInvalidReason::LogicalOwner,
5590            ));
5591        };
5592        if previous_sequence == Some(delivery_seq) || owners.contains(&participant_index) {
5593            return Err(sequence_error(
5594                sequence_ordinal(ledger, delivery_seq),
5595                ClaimFrontierInvalidReason::LogicalOwner,
5596            ));
5597        }
5598        previous_sequence = Some(delivery_seq);
5599        owners.push(participant_index);
5600        active_records.push(record);
5601    }
5602    Ok(active_records)
5603}
5604
5605fn validated_historical_marker_deliveries(
5606    mut facts: Vec<HistoricalMarkerDeliveryFactRestore>,
5607    conversation_id: ConversationId,
5608    active: &ActiveIdentityRanks,
5609    sources: &MarkerDeliverySources<'_>,
5610    retained_record_limit: u64,
5611    ledger: SequenceLedger,
5612) -> Result<Vec<HistoricalMarkerDeliveryAuthority>, ClaimFrontierError> {
5613    let MarkerDeliverySources {
5614        retained: retained_records,
5615        historical: historical_causal_authorities,
5616        candidates: immutable_candidates,
5617    } = *sources;
5618    if usize_to_u128(facts.len()) > u128::from(retained_record_limit) {
5619        return Err(sequence_error(
5620            ledger.required_reserve(),
5621            ClaimFrontierInvalidReason::LogicalOwner,
5622        ));
5623    }
5624    facts.sort_by_key(|fact| fact.marker_delivery_seq);
5625    let mut previous_sequence = None;
5626    let mut authorities = Vec::new();
5627    for fact in facts {
5628        let matching_record = retained_records.iter().find(|record| {
5629            record.delivery_seq == fact.marker_delivery_seq
5630                && matches!(
5631                    record.kind,
5632                    RetainedCausalRecordKind::CompactionMarker {
5633                        participant_index,
5634                        ..
5635                    } if participant_index == fact.participant_index
5636                )
5637        });
5638        let current_bound =
5639            active_participant(active, fact.participant_index).is_some_and(|participant| {
5640                participant.binding == FrontierBinding::Bound(fact.delivered_binding_epoch)
5641            });
5642        let terminal_order = historical_causal_authorities
5643            .iter()
5644            .find_map(|authority| match authority.kind {
5645                HistoricalCausalKind::BindingTerminal(owner)
5646                    if binding_terminal_matches_delivery(owner, fact) =>
5647                {
5648                    Some(authority.admission_order)
5649                }
5650                HistoricalCausalKind::BindingTerminal(_)
5651                | HistoricalCausalKind::MembershipExit(_) => None,
5652            })
5653            .or_else(|| {
5654                retained_records
5655                    .iter()
5656                    .find_map(|record| match record.kind {
5657                        RetainedCausalRecordKind::BindingTerminal(owner)
5658                            if binding_terminal_matches_delivery(owner, fact) =>
5659                        {
5660                            Some(record.admission_order)
5661                        }
5662                        RetainedCausalRecordKind::BindingTerminal(_)
5663                        | RetainedCausalRecordKind::MembershipExit { .. }
5664                        | RetainedCausalRecordKind::AttachLifecycle { .. }
5665                        | RetainedCausalRecordKind::OrdinaryRecord { .. }
5666                        | RetainedCausalRecordKind::CompactionMarker { .. } => None,
5667                    })
5668            })
5669            .or_else(|| {
5670                immutable_candidates
5671                    .iter()
5672                    .find_map(|candidate| match candidate {
5673                        ImmutableSequenceCandidate::BindingTerminal {
5674                            admission_order,
5675                            owner,
5676                            ..
5677                        } if binding_terminal_matches_delivery(*owner, fact) => {
5678                            Some(*admission_order)
5679                        }
5680                        ImmutableSequenceCandidate::BindingTerminal { .. }
5681                        | ImmutableSequenceCandidate::Marker(_) => None,
5682                    })
5683            });
5684        let historical_epoch_is_backed = matching_record.is_some_and(|marker_record| {
5685            current_bound
5686                || terminal_order
5687                    .is_some_and(|terminal_order| terminal_order > marker_record.admission_order)
5688        });
5689        if fact.conversation_id != conversation_id
5690            || previous_sequence == Some(fact.marker_delivery_seq)
5691            || !historical_epoch_is_backed
5692        {
5693            return Err(sequence_error(
5694                sequence_ordinal(ledger, fact.marker_delivery_seq),
5695                ClaimFrontierInvalidReason::LogicalOwner,
5696            ));
5697        }
5698        previous_sequence = Some(fact.marker_delivery_seq);
5699        authorities.push(HistoricalMarkerDeliveryAuthority {
5700            participant_index: fact.participant_index,
5701            marker_delivery_seq: fact.marker_delivery_seq,
5702            delivered_binding_epoch: fact.delivered_binding_epoch,
5703        });
5704    }
5705    Ok(authorities)
5706}
5707
5708fn binding_terminal_matches_delivery(
5709    owner: BindingTerminalOwner,
5710    fact: HistoricalMarkerDeliveryFactRestore,
5711) -> bool {
5712    (owner.participant_index, owner.binding_epoch)
5713        == (fact.participant_index, fact.delivered_binding_epoch)
5714}
5715
5716struct MarkerDeliverySources<'a> {
5717    retained: &'a [RetainedCausalRecord],
5718    historical: &'a [HistoricalCausalAuthority],
5719    candidates: &'a [ImmutableSequenceCandidate],
5720}
5721
5722struct BindingOriginValidation<'a> {
5723    conversation_id: ConversationId,
5724    active: &'a ActiveIdentityRanks,
5725    origins: &'a [BindingOrigin],
5726    retained_records: &'a [RetainedCausalRecord],
5727    causal_authorities: &'a [HistoricalCausalAuthority],
5728    historical_marker_deliveries: &'a [HistoricalMarkerDeliveryAuthority],
5729    total: bool,
5730    ledger: SequenceLedger,
5731}
5732
5733impl BindingOriginValidation<'_> {
5734    fn validate(&self) -> Result<(), ClaimFrontierError> {
5735        if !self.total {
5736            return if self.origins.is_empty() {
5737                Ok(())
5738            } else {
5739                Err(self.logical_owner_error())
5740            };
5741        }
5742        if self.origins.len() != self.active.participants.len() {
5743            return Err(self.logical_owner_error());
5744        }
5745        for participant in &self.active.participants {
5746            let mut matching = self
5747                .origins
5748                .iter()
5749                .filter(|origin| origin.participant_id() == participant.participant_index);
5750            let Some(origin) = matching.next() else {
5751                return Err(self.logical_owner_error());
5752            };
5753            if matching.next().is_some() {
5754                return Err(self.logical_owner_error());
5755            }
5756            self.validate_origin(*participant, *origin)?;
5757        }
5758        Ok(())
5759    }
5760
5761    fn validate_origin(
5762        &self,
5763        participant: FrontierParticipant,
5764        origin: BindingOrigin,
5765    ) -> Result<(), ClaimFrontierError> {
5766        let current_epoch = binding_epoch(participant.binding);
5767        let attached = origin.attached();
5768        if origin.conversation_id() != self.conversation_id
5769            || origin.binding_epoch() != current_epoch
5770            || attached.conversation_id() != self.conversation_id
5771            || attached.participant_id() != participant.participant_index
5772            || attached.binding_epoch() != current_epoch
5773            || attached.admission_order().candidate_phase() != CandidatePhase::AttachLifecycle
5774        {
5775            return Err(self.logical_owner_error());
5776        }
5777        let mut retained_attach_for_binding = self.retained_records.iter().filter(|record| {
5778            matches!(
5779                record.kind,
5780                RetainedCausalRecordKind::AttachLifecycle {
5781                    participant_index,
5782                    binding_epoch,
5783                } if participant_index == participant.participant_index
5784                    && binding_epoch == current_epoch
5785            )
5786        });
5787        let retained_attach_matches = retained_attach_for_binding.clone().any(|record| {
5788            record.delivery_seq == attached.delivery_seq()
5789                && record.admission_order == attached.admission_order()
5790        });
5791        if retained_attach_for_binding.next().is_some() && !retained_attach_matches {
5792            return Err(self.logical_owner_error());
5793        }
5794        if let Some((marker_delivery_seq, prior_binding_epoch)) = origin.recovered_marker() {
5795            let generation_is_next = prior_binding_epoch
5796                .capability_generation
5797                .get()
5798                .checked_add(1)
5799                == Some(current_epoch.capability_generation.get());
5800            let marker_history_matches = self.historical_marker_deliveries.iter().any(|history| {
5801                history.participant_index == participant.participant_index
5802                    && history.marker_delivery_seq == marker_delivery_seq
5803                    && history.delivered_binding_epoch == prior_binding_epoch
5804            });
5805            if !generation_is_next || !marker_history_matches {
5806                return Err(sequence_error(
5807                    sequence_ordinal(self.ledger, marker_delivery_seq),
5808                    ClaimFrontierInvalidReason::RecoveryBlock,
5809                ));
5810            }
5811        } else if matches!(participant.binding, FrontierBinding::Detached(_))
5812            && !binding_terminal_exists(
5813                participant.participant_index,
5814                current_epoch,
5815                self.retained_records,
5816                self.causal_authorities,
5817            )
5818        {
5819            return Err(self.logical_owner_error());
5820        }
5821        Ok(())
5822    }
5823
5824    const fn logical_owner_error(&self) -> ClaimFrontierError {
5825        sequence_error(
5826            self.ledger.required_reserve(),
5827            ClaimFrontierInvalidReason::LogicalOwner,
5828        )
5829    }
5830}
5831
5832fn binding_terminal_exists(
5833    participant_index: ParticipantId,
5834    binding_epoch: BindingEpoch,
5835    retained_records: &[RetainedCausalRecord],
5836    historical_causal_authorities: &[HistoricalCausalAuthority],
5837) -> bool {
5838    retained_records.iter().any(|record| {
5839        matches!(
5840            record.kind,
5841            RetainedCausalRecordKind::BindingTerminal(owner)
5842                if owner.participant_index == participant_index
5843                    && owner.binding_epoch == binding_epoch
5844        )
5845    }) || historical_causal_authorities.iter().any(|authority| {
5846        matches!(
5847            authority.kind,
5848            HistoricalCausalKind::BindingTerminal(owner)
5849                if owner.participant_index == participant_index
5850                    && owner.binding_epoch == binding_epoch
5851        )
5852    })
5853}
5854
5855fn validate_marker_credit_owners(
5856    candidates: &[ImmutableSequenceCandidate],
5857    marker_records: &[RetainedCausalRecord],
5858    identity_slot_limit: u64,
5859    ledger: SequenceLedger,
5860) -> Result<(), ClaimFrontierError> {
5861    let mut owners = Vec::new();
5862    for record in marker_records {
5863        let RetainedCausalRecordKind::CompactionMarker {
5864            participant_index, ..
5865        } = record.kind
5866        else {
5867            continue;
5868        };
5869        if owners.contains(&participant_index) {
5870            return Err(sequence_error(
5871                ledger.required_reserve(),
5872                ClaimFrontierInvalidReason::LogicalOwner,
5873            ));
5874        }
5875        owners.push(participant_index);
5876    }
5877    for candidate in candidates {
5878        let ImmutableSequenceCandidate::Marker(marker) = candidate else {
5879            continue;
5880        };
5881        let participant_index = marker.admission_order.participant_index();
5882        if owners.contains(&participant_index) {
5883            return Err(sequence_error(
5884                sequence_ordinal(ledger, marker.delivery_seq),
5885                ClaimFrontierInvalidReason::LogicalOwner,
5886            ));
5887        }
5888        owners.push(participant_index);
5889    }
5890    if usize_to_u128(owners.len()) > u128::from(identity_slot_limit) {
5891        return Err(sequence_error(
5892            ledger.required_reserve(),
5893            ClaimFrontierInvalidReason::LogicalOwner,
5894        ));
5895    }
5896    Ok(())
5897}
5898
5899fn validated_historical_authorities(
5900    facts: Vec<HistoricalCausalFactRestore>,
5901    conversation_id: ConversationId,
5902    identity_slot_limit: u64,
5903    ledger: SequenceLedger,
5904    history: &ValidatedConversationHistory,
5905) -> Result<Vec<HistoricalCausalAuthority>, ClaimFrontierError> {
5906    if usize_to_u128(facts.len()) > u128::from(identity_slot_limit).saturating_mul(2) {
5907        return Err(sequence_error(
5908            ledger.required_reserve(),
5909            ClaimFrontierInvalidReason::LogicalOwner,
5910        ));
5911    }
5912    let authorities: Vec<_> = facts
5913        .into_iter()
5914        .map(HistoricalCausalAuthority::from_restore)
5915        .collect();
5916    let mut seen = Vec::new();
5917    for authority in &authorities {
5918        let (participant_index, phase) = match authority.kind {
5919            HistoricalCausalKind::BindingTerminal(owner) => {
5920                (owner.participant_index, CandidatePhase::BindingTerminal)
5921            }
5922            HistoricalCausalKind::MembershipExit(participant_index) => {
5923                (participant_index, CandidatePhase::MembershipExit)
5924            }
5925        };
5926        if authority.conversation_id != conversation_id
5927            || participant_index >= identity_slot_limit
5928            || authority.admission_order.participant_index() != participant_index
5929            || authority.admission_order.candidate_phase() != phase
5930            || seen.contains(authority)
5931            || !history.causal_authorities.contains(authority)
5932        {
5933            return Err(sequence_error(
5934                ledger.required_reserve(),
5935                ClaimFrontierInvalidReason::LogicalOwner,
5936            ));
5937        }
5938        seen.push(*authority);
5939    }
5940    Ok(authorities)
5941}
5942
5943const fn corrupt_frontier(error: ClaimFrontierError) -> ParticipantStateCorruptReason {
5944    ParticipantStateCorruptReason::ClaimFrontierInvalid {
5945        counter: match error.counter {
5946            ClaimFrontierCounter::DeliverySequence => ClaimCounter::DeliverySeq,
5947            ClaimFrontierCounter::TransactionOrder => ClaimCounter::TransactionOrder,
5948        },
5949        first_bad_position: error.first_bad_position,
5950    }
5951}
5952
5953fn sequence_segments(restore: &SequenceClaimFrontierRestore) -> Vec<NumericSegment<SequenceClass>> {
5954    let mut segments = Vec::new();
5955    for claim in &restore.movable_claims {
5956        segments.push(NumericSegment {
5957            start: u128::from(claim.delivery_seq),
5958            length: 1,
5959            class: Some(match claim.owner {
5960                SequenceDirectOwner::MembershipExit { .. } => SequenceClass::Exit,
5961                SequenceDirectOwner::BindingTerminal(_) => SequenceClass::Terminal,
5962            }),
5963            immutable: false,
5964        });
5965    }
5966    for candidate in &restore.immutable_candidates {
5967        segments.push(NumericSegment {
5968            start: u128::from(candidate.delivery_seq()),
5969            length: 1,
5970            class: Some(sequence_candidate_class(*candidate)),
5971            immutable: true,
5972        });
5973    }
5974    for range in &restore.products.live_times_terminal {
5975        segments.push(NumericSegment {
5976            start: u128::from(range.start),
5977            length: u128::from(range.length),
5978            class: Some(SequenceClass::LiveTimesTerminal),
5979            immutable: false,
5980        });
5981    }
5982    if let Some(range) = restore.products.live_times_replacement_terminal {
5983        segments.push(NumericSegment {
5984            start: u128::from(range.start),
5985            length: u128::from(range.length),
5986            class: Some(SequenceClass::LiveTimesReplacementTerminal),
5987            immutable: false,
5988        });
5989    }
5990    for range in &restore.products.other_live_times_exit {
5991        segments.push(NumericSegment {
5992            start: u128::from(range.start),
5993            length: u128::from(range.length),
5994            class: Some(SequenceClass::OtherLiveTimesExit),
5995            immutable: false,
5996        });
5997    }
5998    if let Some(recovery) = restore.recovery {
5999        if let Some(terminal) = recovery.terminal {
6000            segments.push(NumericSegment {
6001                start: u128::from(terminal.delivery_seq),
6002                length: 1,
6003                class: Some(SequenceClass::Terminal),
6004                immutable: false,
6005            });
6006        }
6007        segments.push(NumericSegment {
6008            start: u128::from(recovery.recovery_attach_seq),
6009            length: 1,
6010            class: Some(SequenceClass::RecoveryAttach),
6011            immutable: false,
6012        });
6013        segments.push(NumericSegment {
6014            start: u128::from(recovery.replacement_terminal_seq),
6015            length: 1,
6016            class: Some(SequenceClass::RecoveryReplacementTerminal),
6017            immutable: false,
6018        });
6019    }
6020    segments
6021}
6022
6023fn order_segments(
6024    restore: &OrderClaimFrontierRestore,
6025    high: OrderHigh,
6026) -> Vec<NumericSegment<OrderClass>> {
6027    let mut segments = Vec::new();
6028    for claim in &restore.movable_claims {
6029        segments.push(NumericSegment {
6030            start: u128::from(claim.transaction_order),
6031            length: 1,
6032            class: Some(match claim.owner {
6033                OrderDirectOwner::ActiveBindingTerminal(_) => OrderClass::ActiveBindingTerminal,
6034                OrderDirectOwner::MembershipExit { .. } => OrderClass::MembershipExit,
6035            }),
6036            immutable: false,
6037        });
6038    }
6039    for candidate in restore
6040        .immutable_candidates
6041        .iter()
6042        .filter(|candidate| order_is_above_high(candidate.transaction_order, high))
6043    {
6044        segments.push(NumericSegment {
6045            start: u128::from(candidate.transaction_order),
6046            length: 1,
6047            class: None,
6048            immutable: true,
6049        });
6050    }
6051    if let Some(recovery) = restore.recovery {
6052        if let Some(active_binding) = recovery.active_binding {
6053            segments.push(NumericSegment {
6054                start: u128::from(active_binding.transaction_order),
6055                length: 1,
6056                class: Some(OrderClass::ActiveBindingTerminal),
6057                immutable: false,
6058            });
6059        }
6060        segments.push(NumericSegment {
6061            start: u128::from(recovery.recovery_operation_order),
6062            length: 1,
6063            class: Some(OrderClass::RecoveryOperation),
6064            immutable: false,
6065        });
6066        segments.push(NumericSegment {
6067            start: u128::from(recovery.replacement_terminal_order),
6068            length: 1,
6069            class: Some(OrderClass::RecoveryReplacementTerminal),
6070            immutable: false,
6071        });
6072    }
6073    segments
6074}
6075
6076fn restore_sequence_frontier(
6077    active: &ActiveIdentityRanks,
6078    restore: SequenceClaimFrontierRestore,
6079    retained_floor: u128,
6080    ledger: SequenceLedger,
6081    recovery_provenance: Option<RecoveryClaimProvenance>,
6082    retained_records: &[RetainedCausalRecord],
6083    historical_causal_authorities: &[HistoricalCausalAuthority],
6084) -> Result<SequenceClaimFrontier, ClaimFrontierError> {
6085    let mut segments = sequence_segments(&restore);
6086
6087    let expected_counts = sequence_expected_counts(ledger);
6088    validate_numeric_segments(
6089        ClaimFrontierCounter::DeliverySequence,
6090        u128::from(ledger.high_watermark()) + 1,
6091        ledger.required_reserve(),
6092        &mut segments,
6093        &expected_counts,
6094    )?;
6095    validate_sequence_recovery(
6096        active,
6097        restore.recovery,
6098        recovery_provenance,
6099        ledger,
6100        ledger.required_reserve(),
6101    )?;
6102    validate_sequence_candidates(
6103        active,
6104        &restore.immutable_candidates,
6105        retained_floor,
6106        retained_records,
6107        historical_causal_authorities,
6108        ledger,
6109    )?;
6110    let terminal_owners = validate_sequence_direct_owners(
6111        active,
6112        &restore.movable_claims,
6113        &restore.immutable_candidates,
6114        restore.recovery,
6115        ledger,
6116    )?;
6117    let products = validate_sequence_products(
6118        active,
6119        restore.products,
6120        &terminal_owners,
6121        restore.recovery,
6122        recovery_provenance,
6123        ledger,
6124    )?;
6125    let recovery = restore
6126        .recovery
6127        .zip(recovery_provenance)
6128        .map(|(value, provenance)| RecoverySequenceBlock {
6129            terminal: value.terminal,
6130            recovery_attach_seq: value.recovery_attach_seq,
6131            replacement_terminal_seq: value.replacement_terminal_seq,
6132            participant_index: provenance.participant_index,
6133            marker_delivery_seq: provenance.marker_delivery_seq,
6134            recovered_binding_epoch: provenance.prior_binding_epoch,
6135        });
6136
6137    let mut movable_claims = restore.movable_claims;
6138    movable_claims.sort_by_key(|claim| claim.delivery_seq);
6139    let mut immutable_candidates = restore.immutable_candidates;
6140    immutable_candidates.sort_by_key(|candidate| candidate.delivery_seq());
6141
6142    Ok(SequenceClaimFrontier {
6143        ledger,
6144        movable_claims,
6145        immutable_candidates,
6146        products,
6147        recovery,
6148    })
6149}
6150
6151fn restore_order_frontier(
6152    active: &ActiveIdentityRanks,
6153    restore: OrderClaimFrontierRestore,
6154    ledger: OrderLedger,
6155    recovery_provenance: Option<RecoveryClaimProvenance>,
6156) -> Result<OrderClaimFrontier, ClaimFrontierError> {
6157    let mut segments = order_segments(&restore, ledger.high());
6158
6159    let candidate_count = order_frontier_candidate_count(&restore, ledger.high());
6160    let expected_length = candidate_count + ledger.claims().total();
6161    let expected_counts = order_expected_counts(ledger);
6162    validate_numeric_segments(
6163        ClaimFrontierCounter::TransactionOrder,
6164        order_frontier_start(ledger.high()),
6165        expected_length,
6166        &mut segments,
6167        &expected_counts,
6168    )?;
6169    validate_order_recovery(
6170        active,
6171        restore.recovery,
6172        recovery_provenance,
6173        ledger,
6174        expected_length,
6175    )?;
6176    let immutable_candidates = validate_order_candidates(&restore.immutable_candidates, ledger)?;
6177    validate_order_direct_owners(active, &restore.movable_claims, restore.recovery, ledger)?;
6178    let recovery = restore
6179        .recovery
6180        .zip(recovery_provenance)
6181        .map(|(value, provenance)| RecoveryOrderBlock {
6182            active_binding: value.active_binding,
6183            recovery_operation_order: value.recovery_operation_order,
6184            replacement_terminal_order: value.replacement_terminal_order,
6185            participant_index: provenance.participant_index,
6186            marker_delivery_seq: provenance.marker_delivery_seq,
6187            recovered_binding_epoch: provenance.prior_binding_epoch,
6188        });
6189
6190    let mut movable_claims = restore.movable_claims;
6191    movable_claims.sort_by_key(|claim| claim.transaction_order);
6192
6193    Ok(OrderClaimFrontier {
6194        ledger,
6195        movable_claims,
6196        immutable_candidates,
6197        recovery,
6198    })
6199}
6200
6201/// Describes a validated sequence frontier as the restore input
6202/// [`restore_sequence_frontier`] rebuilds it from.
6203///
6204/// The ledger travels beside the input, and the recovery block's and
6205/// replacement product's participant, marker, and epoch are omitted because
6206/// restore re-derives them from recovery provenance.
6207fn describe_sequence_frontier(frontier: &SequenceClaimFrontier) -> SequenceClaimFrontierRestore {
6208    SequenceClaimFrontierRestore {
6209        movable_claims: frontier.movable_claims.clone(),
6210        immutable_candidates: frontier.immutable_candidates.clone(),
6211        products: SequenceProductRangesRestore {
6212            live_times_terminal: frontier
6213                .products
6214                .live_times_terminal
6215                .iter()
6216                .map(|range| TerminalProductRangeRestore {
6217                    start: range.start,
6218                    length: range.length,
6219                    terminal: range.terminal,
6220                })
6221                .collect(),
6222            live_times_replacement_terminal: frontier.products.live_times_replacement_terminal.map(
6223                |range| ReplacementTerminalProductRangeRestore {
6224                    start: range.start,
6225                    length: range.length,
6226                },
6227            ),
6228            other_live_times_exit: frontier
6229                .products
6230                .other_live_times_exit
6231                .iter()
6232                .map(|range| ExitProductRangeRestore {
6233                    start: range.start,
6234                    length: range.length,
6235                    exit_participant: range.exit_participant,
6236                })
6237                .collect(),
6238        },
6239        recovery: frontier.recovery.map(|block| RecoverySequenceBlockRestore {
6240            terminal: block.terminal,
6241            recovery_attach_seq: block.recovery_attach_seq,
6242            replacement_terminal_seq: block.replacement_terminal_seq,
6243        }),
6244    }
6245}
6246
6247/// Describes a validated order frontier as the restore input
6248/// [`restore_order_frontier`] rebuilds it from.
6249///
6250/// The ledger travels beside the input, and the recovery block's participant,
6251/// marker, and epoch are omitted because restore re-derives them from
6252/// recovery provenance.
6253fn describe_order_frontier(frontier: &OrderClaimFrontier) -> OrderClaimFrontierRestore {
6254    OrderClaimFrontierRestore {
6255        movable_claims: frontier.movable_claims.clone(),
6256        immutable_candidates: frontier
6257            .immutable_candidates
6258            .iter()
6259            .map(|major| ImmutableOrderCandidateMajorRestore {
6260                transaction_order: major.transaction_order,
6261                candidate_keys: major.candidate_keys.clone(),
6262            })
6263            .collect(),
6264        recovery: frontier.recovery.map(|block| RecoveryOrderBlockRestore {
6265            active_binding: block.active_binding,
6266            recovery_operation_order: block.recovery_operation_order,
6267            replacement_terminal_order: block.replacement_terminal_order,
6268        }),
6269    }
6270}
6271
6272fn validate_numeric_segments<C: Copy + Into<usize>>(
6273    counter: ClaimFrontierCounter,
6274    first_value: u128,
6275    expected_length: u128,
6276    segments: &mut [NumericSegment<C>],
6277    expected_counts: &[u128],
6278) -> Result<(), ClaimFrontierError> {
6279    segments.sort_by_key(|segment| segment.start);
6280    let mut events = numeric_events(counter, first_value, segments)?;
6281    let emitted = scan_numeric_events(counter, first_value, &mut events)?;
6282    if emitted != expected_length {
6283        return Err(frontier_error(
6284            counter,
6285            emitted.min(expected_length),
6286            ClaimFrontierInvalidReason::AggregateLedger,
6287        ));
6288    }
6289    validate_immutable_prefix(counter, first_value, segments)?;
6290    validate_segment_class_counts(counter, expected_length, segments, expected_counts)
6291}
6292
6293fn numeric_events<C>(
6294    counter: ClaimFrontierCounter,
6295    first_value: u128,
6296    segments: &[NumericSegment<C>],
6297) -> Result<Vec<(u128, i8)>, ClaimFrontierError> {
6298    let mut events = Vec::new();
6299    let counter_limit = u128::from(u64::MAX) + 1;
6300    for segment in segments {
6301        if segment.length == 0 {
6302            continue;
6303        }
6304        let Some(end) = segment.start.checked_add(segment.length) else {
6305            return Err(frontier_error(
6306                counter,
6307                counter_limit.saturating_sub(first_value),
6308                ClaimFrontierInvalidReason::NumericPosition,
6309            ));
6310        };
6311        if segment.start < first_value {
6312            return Err(frontier_error(
6313                counter,
6314                0,
6315                ClaimFrontierInvalidReason::NumericPosition,
6316            ));
6317        }
6318        if end > counter_limit {
6319            return Err(frontier_error(
6320                counter,
6321                counter_limit.saturating_sub(first_value),
6322                ClaimFrontierInvalidReason::NumericPosition,
6323            ));
6324        }
6325        events.push((segment.start, 1_i8));
6326        events.push((end, -1_i8));
6327    }
6328    Ok(events)
6329}
6330
6331fn scan_numeric_events(
6332    counter: ClaimFrontierCounter,
6333    first_value: u128,
6334    events: &mut [(u128, i8)],
6335) -> Result<u128, ClaimFrontierError> {
6336    events.sort_unstable_by_key(|event| event.0);
6337    let mut event_index = 0_usize;
6338    let mut coordinate = first_value;
6339    let mut coverage = 0_i128;
6340    let mut emitted = 0_u128;
6341    while let Some((event_coordinate, _)) = events.get(event_index).copied() {
6342        if event_coordinate > coordinate {
6343            if coverage == 0 {
6344                return Err(frontier_error(
6345                    counter,
6346                    emitted,
6347                    ClaimFrontierInvalidReason::NumericPosition,
6348                ));
6349            }
6350            if coverage > 1 {
6351                return Err(frontier_error(
6352                    counter,
6353                    emitted.saturating_add(1),
6354                    ClaimFrontierInvalidReason::NumericPosition,
6355                ));
6356            }
6357            emitted = emitted.saturating_add(event_coordinate - coordinate);
6358            coordinate = event_coordinate;
6359        }
6360        while let Some((same_coordinate, delta)) = events.get(event_index).copied() {
6361            if same_coordinate != coordinate {
6362                break;
6363            }
6364            coverage += i128::from(delta);
6365            event_index += 1;
6366        }
6367    }
6368    if coverage != 0 {
6369        return Err(frontier_error(
6370            counter,
6371            emitted,
6372            ClaimFrontierInvalidReason::NumericPosition,
6373        ));
6374    }
6375    Ok(emitted)
6376}
6377
6378fn validate_immutable_prefix<C>(
6379    counter: ClaimFrontierCounter,
6380    first_value: u128,
6381    segments: &[NumericSegment<C>],
6382) -> Result<(), ClaimFrontierError> {
6383    let mut first_movable = None;
6384    for segment in segments.iter().filter(|segment| segment.length != 0) {
6385        if segment.immutable {
6386            if let Some(first_movable) = first_movable {
6387                return Err(frontier_error(
6388                    counter,
6389                    first_movable,
6390                    ClaimFrontierInvalidReason::NumericPosition,
6391                ));
6392            }
6393        } else if first_movable.is_none() {
6394            first_movable = Some(segment.start - first_value);
6395        }
6396    }
6397    Ok(())
6398}
6399
6400fn validate_segment_class_counts<C: Copy + Into<usize>>(
6401    counter: ClaimFrontierCounter,
6402    expected_length: u128,
6403    segments: &[NumericSegment<C>],
6404    expected_counts: &[u128],
6405) -> Result<(), ClaimFrontierError> {
6406    let mut actual_counts = core::iter::repeat_n(0_u128, expected_counts.len()).collect::<Vec<_>>();
6407    let mut class_ordinal = 0_u128;
6408    for segment in segments {
6409        if segment.length == 0 {
6410            continue;
6411        }
6412        if let Some(class) = segment.class {
6413            let index = class.into();
6414            let prior = actual_counts[index];
6415            let Some(resulting) = prior.checked_add(segment.length) else {
6416                return Err(frontier_error(
6417                    counter,
6418                    class_ordinal,
6419                    ClaimFrontierInvalidReason::AggregateLedger,
6420                ));
6421            };
6422            if resulting > expected_counts[index] {
6423                return Err(frontier_error(
6424                    counter,
6425                    class_ordinal + expected_counts[index].saturating_sub(prior),
6426                    ClaimFrontierInvalidReason::AggregateLedger,
6427                ));
6428            }
6429            actual_counts[index] = resulting;
6430        }
6431        class_ordinal += segment.length;
6432    }
6433    if actual_counts != expected_counts {
6434        return Err(frontier_error(
6435            counter,
6436            expected_length,
6437            ClaimFrontierInvalidReason::AggregateLedger,
6438        ));
6439    }
6440    Ok(())
6441}
6442
6443#[cfg(test)]
6444pub(super) fn validate_numeric_union_for_test(
6445    first_value: u128,
6446    expected_length: u128,
6447    ranges: &[(u128, u128)],
6448) -> Result<(), ClaimFrontierError> {
6449    let mut segments: Vec<_> = ranges
6450        .iter()
6451        .map(|(start, length)| NumericSegment {
6452            start: *start,
6453            length: *length,
6454            class: Some(SequenceClass::Exit),
6455            immutable: false,
6456        })
6457        .collect();
6458    validate_numeric_segments(
6459        ClaimFrontierCounter::DeliverySequence,
6460        first_value,
6461        expected_length,
6462        &mut segments,
6463        &[expected_length, 0, 0, 0, 0, 0, 0, 0],
6464    )
6465}
6466
6467impl From<SequenceClass> for usize {
6468    fn from(value: SequenceClass) -> Self {
6469        value as Self
6470    }
6471}
6472
6473impl From<OrderClass> for usize {
6474    fn from(value: OrderClass) -> Self {
6475        value as Self
6476    }
6477}
6478
6479const fn sequence_candidate_class(candidate: ImmutableSequenceCandidate) -> SequenceClass {
6480    match candidate {
6481        ImmutableSequenceCandidate::BindingTerminal { .. } => SequenceClass::Terminal,
6482        ImmutableSequenceCandidate::Marker(marker) => match marker.current_owner {
6483            MarkerSequenceOwner::Marker => SequenceClass::Marker,
6484            MarkerSequenceOwner::ConditionalProduct(SequenceProductClass::LiveTimesTerminal) => {
6485                SequenceClass::LiveTimesTerminal
6486            }
6487            MarkerSequenceOwner::ConditionalProduct(
6488                SequenceProductClass::LiveTimesReplacementTerminal,
6489            ) => SequenceClass::LiveTimesReplacementTerminal,
6490            MarkerSequenceOwner::ConditionalProduct(SequenceProductClass::OtherLiveTimesExit) => {
6491                SequenceClass::OtherLiveTimesExit
6492            }
6493        },
6494    }
6495}
6496
6497fn sequence_expected_counts(ledger: SequenceLedger) -> [u128; 8] {
6498    let budget = ledger.budget();
6499    [
6500        u128::from(budget.e),
6501        u128::from(budget.t),
6502        u128::from(budget.m),
6503        u128::from(budget.rs),
6504        u128::from(budget.rt),
6505        budget.l_times_t,
6506        budget.l_times_rt,
6507        budget.l_other_times_e,
6508    ]
6509}
6510
6511fn order_expected_counts(ledger: OrderLedger) -> [u128; 4] {
6512    let claims = ledger.claims();
6513    [
6514        u128::from(claims.active_binding_terminals()),
6515        u128::from(claims.membership_exits()),
6516        u128::from(claims.recovery_operation()),
6517        u128::from(claims.recovery_replacement_terminal()),
6518    ]
6519}
6520
6521fn validate_sequence_recovery(
6522    active: &ActiveIdentityRanks,
6523    recovery: Option<RecoverySequenceBlockRestore>,
6524    provenance: Option<RecoveryClaimProvenance>,
6525    ledger: SequenceLedger,
6526    frontier_length: u128,
6527) -> Result<(), ClaimFrontierError> {
6528    let expected = ledger.claims().recovery();
6529    match (expected, recovery, provenance) {
6530        (RecoverySequenceReserve::None, None, None) => Ok(()),
6531        (RecoverySequenceReserve::DetachedCredentialRecovery, None, _)
6532        | (RecoverySequenceReserve::DetachedCredentialRecovery, Some(_), None)
6533        | (RecoverySequenceReserve::None, None, Some(_)) => Err(sequence_error(
6534            frontier_length,
6535            ClaimFrontierInvalidReason::RecoveryBlock,
6536        )),
6537        (RecoverySequenceReserve::None, Some(block), _) => Err(sequence_error(
6538            sequence_ordinal(ledger, block_start_sequence(block)),
6539            ClaimFrontierInvalidReason::RecoveryBlock,
6540        )),
6541        (RecoverySequenceReserve::DetachedCredentialRecovery, Some(block), Some(provenance)) => {
6542            let block_ordinal = sequence_ordinal(ledger, block_start_sequence(block));
6543            let expected_recovery_attach = block
6544                .terminal
6545                .map_or(Some(block.recovery_attach_seq), |terminal| {
6546                    terminal.delivery_seq.checked_add(1)
6547                });
6548            if expected_recovery_attach != Some(block.recovery_attach_seq) {
6549                return Err(sequence_error(
6550                    block_ordinal + 1,
6551                    ClaimFrontierInvalidReason::RecoveryBlock,
6552                ));
6553            }
6554            if block.recovery_attach_seq.checked_add(1) != Some(block.replacement_terminal_seq) {
6555                return Err(sequence_error(
6556                    block_ordinal + u128::from(block.terminal.is_some()) + 1,
6557                    ClaimFrontierInvalidReason::RecoveryBlock,
6558                ));
6559            }
6560            let Some(participant) = active_participant(active, provenance.participant_index) else {
6561                return Err(sequence_error(
6562                    block_ordinal,
6563                    ClaimFrontierInvalidReason::LogicalOwner,
6564                ));
6565            };
6566            let expected_binding = match provenance.phase {
6567                RecoveryClaimPhase::PreFate => {
6568                    FrontierBinding::Bound(provenance.prior_binding_epoch)
6569                }
6570                RecoveryClaimPhase::PostFate => {
6571                    FrontierBinding::Detached(provenance.prior_binding_epoch)
6572                }
6573                RecoveryClaimPhase::RecoveredBound => {
6574                    FrontierBinding::Bound(provenance.current_binding_epoch)
6575                }
6576            };
6577            if participant.binding != expected_binding {
6578                return Err(sequence_error(
6579                    block_ordinal,
6580                    ClaimFrontierInvalidReason::LogicalOwner,
6581                ));
6582            }
6583            let terminal_valid = match (provenance.phase, block.terminal) {
6584                (RecoveryClaimPhase::PreFate, Some(terminal)) => {
6585                    terminal.owner.participant_index == provenance.participant_index
6586                        && terminal.owner.binding_epoch == provenance.prior_binding_epoch
6587                }
6588                (RecoveryClaimPhase::PostFate | RecoveryClaimPhase::RecoveredBound, None) => true,
6589                _ => false,
6590            };
6591            if !terminal_valid {
6592                return Err(sequence_error(
6593                    block_ordinal,
6594                    ClaimFrontierInvalidReason::RecoveryBlock,
6595                ));
6596            }
6597            Ok(())
6598        }
6599    }
6600}
6601
6602fn validate_sequence_candidates(
6603    active: &ActiveIdentityRanks,
6604    candidates: &[ImmutableSequenceCandidate],
6605    retained_floor: u128,
6606    retained_records: &[RetainedCausalRecord],
6607    historical_causal_authorities: &[HistoricalCausalAuthority],
6608    ledger: SequenceLedger,
6609) -> Result<(), ClaimFrontierError> {
6610    let mut seen_keys = Vec::new();
6611    let mut previous_sequence = None;
6612    let mut previous_order = retained_records.last().map(|record| record.admission_order);
6613    for candidate in candidates {
6614        let ordinal = sequence_ordinal(ledger, candidate.delivery_seq());
6615        let order = candidate.admission_order();
6616        if previous_sequence.is_some_and(|previous| previous >= candidate.delivery_seq())
6617            || previous_order.is_some_and(|previous| previous >= order)
6618            || seen_keys.contains(&order)
6619        {
6620            return Err(sequence_error(
6621                ordinal,
6622                ClaimFrontierInvalidReason::CandidateKey,
6623            ));
6624        }
6625        previous_sequence = Some(candidate.delivery_seq());
6626        previous_order = Some(order);
6627        seen_keys.push(order);
6628        match candidate {
6629            ImmutableSequenceCandidate::BindingTerminal { owner, .. } => {
6630                if order.candidate_phase() != CandidatePhase::BindingTerminal
6631                    || order.participant_index() != owner.participant_index
6632                    || !terminal_matches_active(active, *owner)
6633                {
6634                    return Err(sequence_error(
6635                        ordinal,
6636                        ClaimFrontierInvalidReason::CandidateKey,
6637                    ));
6638                }
6639            }
6640            ImmutableSequenceCandidate::Marker(marker) => {
6641                let Some(participant) = active_participant(active, order.participant_index())
6642                else {
6643                    return Err(sequence_error(
6644                        ordinal,
6645                        ClaimFrontierInvalidReason::LogicalOwner,
6646                    ));
6647                };
6648                if order.candidate_phase() != CandidatePhase::CompactionMarker
6649                    || marker.current_owner != MarkerSequenceOwner::Marker
6650                    || marker.target_binding != participant.binding
6651                    || marker.abandoned_after != participant.cursor
6652                    || marker.abandoned_after > marker.abandoned_through
6653                    || u128::from(marker.physical_floor_at_decision) != retained_floor
6654                    || u128::from(marker.physical_floor_at_decision)
6655                        > u128::from(marker.abandoned_through) + 1
6656                    || marker.abandoned_through >= marker.delivery_seq
6657                    || !marker_provenance_targets(marker.provenance, order.participant_index())
6658                    || !marker_has_causal_authority(
6659                        *marker,
6660                        retained_records,
6661                        historical_causal_authorities,
6662                    )
6663                {
6664                    return Err(sequence_error(
6665                        ordinal,
6666                        ClaimFrontierInvalidReason::CandidateKey,
6667                    ));
6668                }
6669            }
6670        }
6671    }
6672    Ok(())
6673}
6674
6675#[derive(Clone, Copy, Debug, PartialEq, Eq)]
6676enum TerminalOccurrenceKind {
6677    Movable,
6678    Candidate,
6679}
6680
6681#[derive(Clone, Copy, Debug, PartialEq, Eq)]
6682struct TerminalOccurrence {
6683    owner: BindingTerminalOwner,
6684    ordinal: u128,
6685    kind: TerminalOccurrenceKind,
6686}
6687
6688/// The canonical order of binding-terminal owners, for the sorts that compare
6689/// owner sets, count them or look them up: the participant, then its binding
6690/// epoch by [`BindingEpoch::canonical_sort_key`]. It does not mean a later
6691/// binding.
6692pub(in crate::lifecycle) const fn terminal_owner_sort_key(
6693    owner: &BindingTerminalOwner,
6694) -> (ParticipantId, (ConnectionIncarnation, Generation)) {
6695    (
6696        owner.participant_index,
6697        owner.binding_epoch.canonical_sort_key(),
6698    )
6699}
6700
6701fn validate_sequence_direct_owners(
6702    active: &ActiveIdentityRanks,
6703    movable: &[MovableSequenceClaim],
6704    candidates: &[ImmutableSequenceCandidate],
6705    recovery: Option<RecoverySequenceBlockRestore>,
6706    ledger: SequenceLedger,
6707) -> Result<Vec<BindingTerminalOwner>, ClaimFrontierError> {
6708    let (mut exit_owners, terminal_occurrences) =
6709        collect_sequence_direct_owners(active, movable, candidates, recovery, ledger)?;
6710    validate_sequence_exit_owners(active, &mut exit_owners, ledger)?;
6711    validate_sequence_terminal_owners(active, &terminal_occurrences, ledger)?;
6712    let mut owners: Vec<_> = terminal_occurrences
6713        .into_iter()
6714        .map(|occurrence| occurrence.owner)
6715        .collect();
6716    owners.sort_by_key(terminal_owner_sort_key);
6717    Ok(owners)
6718}
6719
6720fn collect_sequence_direct_owners(
6721    active: &ActiveIdentityRanks,
6722    movable: &[MovableSequenceClaim],
6723    candidates: &[ImmutableSequenceCandidate],
6724    recovery: Option<RecoverySequenceBlockRestore>,
6725    ledger: SequenceLedger,
6726) -> Result<(Vec<ParticipantId>, Vec<TerminalOccurrence>), ClaimFrontierError> {
6727    let mut ordered_movable = movable.to_vec();
6728    ordered_movable.sort_by_key(|claim| claim.delivery_seq);
6729    let mut exit_owners = Vec::new();
6730    let mut terminal_occurrences = Vec::new();
6731    for claim in ordered_movable {
6732        let ordinal = sequence_ordinal(ledger, claim.delivery_seq);
6733        match claim.owner {
6734            SequenceDirectOwner::MembershipExit { participant_index } => {
6735                if !active.contains(participant_index) || exit_owners.contains(&participant_index) {
6736                    return Err(sequence_error(
6737                        ordinal,
6738                        ClaimFrontierInvalidReason::LogicalOwner,
6739                    ));
6740                }
6741                exit_owners.push(participant_index);
6742            }
6743            SequenceDirectOwner::BindingTerminal(owner) => {
6744                if !terminal_matches_bound(active, owner) {
6745                    return Err(sequence_error(
6746                        ordinal,
6747                        ClaimFrontierInvalidReason::LogicalOwner,
6748                    ));
6749                }
6750                push_terminal_occurrence(
6751                    &mut terminal_occurrences,
6752                    owner,
6753                    ordinal,
6754                    TerminalOccurrenceKind::Movable,
6755                )?;
6756            }
6757        }
6758    }
6759    for candidate in candidates {
6760        if let ImmutableSequenceCandidate::BindingTerminal { owner, .. } = candidate {
6761            let ordinal = sequence_ordinal(ledger, candidate.delivery_seq());
6762            push_terminal_occurrence(
6763                &mut terminal_occurrences,
6764                *owner,
6765                ordinal,
6766                TerminalOccurrenceKind::Candidate,
6767            )?;
6768        }
6769    }
6770    if let Some(terminal) = recovery.and_then(|block| block.terminal) {
6771        let ordinal = sequence_ordinal(ledger, terminal.delivery_seq);
6772        push_terminal_occurrence(
6773            &mut terminal_occurrences,
6774            terminal.owner,
6775            ordinal,
6776            TerminalOccurrenceKind::Movable,
6777        )?;
6778    }
6779    Ok((exit_owners, terminal_occurrences))
6780}
6781
6782fn push_terminal_occurrence(
6783    occurrences: &mut Vec<TerminalOccurrence>,
6784    owner: BindingTerminalOwner,
6785    ordinal: u128,
6786    kind: TerminalOccurrenceKind,
6787) -> Result<(), ClaimFrontierError> {
6788    if occurrences
6789        .iter()
6790        .any(|occurrence| occurrence.owner == owner)
6791    {
6792        return Err(sequence_error(
6793            ordinal,
6794            ClaimFrontierInvalidReason::LogicalOwner,
6795        ));
6796    }
6797    occurrences.push(TerminalOccurrence {
6798        owner,
6799        ordinal,
6800        kind,
6801    });
6802    Ok(())
6803}
6804
6805fn validate_sequence_exit_owners(
6806    active: &ActiveIdentityRanks,
6807    exit_owners: &mut [ParticipantId],
6808    ledger: SequenceLedger,
6809) -> Result<(), ClaimFrontierError> {
6810    exit_owners.sort_unstable();
6811    if exit_owners.len() != active.participants.len()
6812        || !exit_owners.iter().copied().eq(active
6813            .participants
6814            .iter()
6815            .map(|participant| participant.participant_index))
6816    {
6817        return Err(sequence_error(
6818            ledger.required_reserve(),
6819            ClaimFrontierInvalidReason::LogicalOwner,
6820        ));
6821    }
6822    Ok(())
6823}
6824
6825fn validate_sequence_terminal_owners(
6826    active: &ActiveIdentityRanks,
6827    terminal_occurrences: &[TerminalOccurrence],
6828    ledger: SequenceLedger,
6829) -> Result<(), ClaimFrontierError> {
6830    for participant in &active.participants {
6831        let matching: Vec<_> = terminal_occurrences
6832            .iter()
6833            .filter(|occurrence| {
6834                occurrence.owner.participant_index == participant.participant_index
6835            })
6836            .copied()
6837            .collect();
6838        match participant.binding {
6839            FrontierBinding::Bound(epoch) => {
6840                if !matches!(matching.as_slice(), [occurrence] if occurrence.owner.binding_epoch == epoch)
6841                {
6842                    return Err(sequence_error(
6843                        matching.first().map_or_else(
6844                            || ledger.required_reserve(),
6845                            |occurrence| occurrence.ordinal,
6846                        ),
6847                        ClaimFrontierInvalidReason::LogicalOwner,
6848                    ));
6849                }
6850            }
6851            FrontierBinding::Detached(epoch) => {
6852                if matching.len() > 1
6853                    || matching.first().is_some_and(|occurrence| {
6854                        occurrence.owner.binding_epoch != epoch
6855                            || occurrence.kind != TerminalOccurrenceKind::Candidate
6856                    })
6857                {
6858                    return Err(sequence_error(
6859                        matching.first().map_or_else(
6860                            || ledger.required_reserve(),
6861                            |occurrence| occurrence.ordinal,
6862                        ),
6863                        ClaimFrontierInvalidReason::LogicalOwner,
6864                    ));
6865                }
6866            }
6867        }
6868    }
6869    Ok(())
6870}
6871
6872fn validate_sequence_products(
6873    active: &ActiveIdentityRanks,
6874    restore: SequenceProductRangesRestore,
6875    terminal_owners: &[BindingTerminalOwner],
6876    recovery: Option<RecoverySequenceBlockRestore>,
6877    recovery_provenance: Option<RecoveryClaimProvenance>,
6878    ledger: SequenceLedger,
6879) -> Result<SequenceProductRanges, ClaimFrontierError> {
6880    let live_count = usize_to_u64(active.participants.len());
6881    let other_count = live_count.saturating_sub(1);
6882    let live_times_terminal = validate_terminal_product_ranges(
6883        restore.live_times_terminal,
6884        terminal_owners,
6885        live_count,
6886        ledger,
6887    )?;
6888    let live_times_replacement_terminal = validate_replacement_product_range(
6889        restore.live_times_replacement_terminal,
6890        recovery,
6891        recovery_provenance,
6892        live_count,
6893        ledger,
6894    )?;
6895    let other_live_times_exit =
6896        validate_exit_product_ranges(active, restore.other_live_times_exit, other_count, ledger)?;
6897    Ok(SequenceProductRanges {
6898        live_times_terminal,
6899        live_times_replacement_terminal,
6900        other_live_times_exit,
6901    })
6902}
6903
6904fn validate_terminal_product_ranges(
6905    mut ranges: Vec<TerminalProductRangeRestore>,
6906    terminal_owners: &[BindingTerminalOwner],
6907    live_count: u64,
6908    ledger: SequenceLedger,
6909) -> Result<Vec<TerminalProductRange>, ClaimFrontierError> {
6910    ranges.sort_by_key(|range| range.start);
6911    let mut seen_terminals = Vec::new();
6912    let mut live_times_terminal = Vec::new();
6913    for range in ranges {
6914        let ordinal = sequence_ordinal(ledger, range.start);
6915        if range.length != live_count
6916            || !terminal_owners.contains(&range.terminal)
6917            || seen_terminals.contains(&range.terminal)
6918        {
6919            return Err(sequence_error(
6920                ordinal,
6921                ClaimFrontierInvalidReason::ProductRange,
6922            ));
6923        }
6924        seen_terminals.push(range.terminal);
6925        live_times_terminal.push(TerminalProductRange {
6926            start: range.start,
6927            length: range.length,
6928            terminal: range.terminal,
6929        });
6930    }
6931    if seen_terminals.len() != terminal_owners.len() {
6932        return Err(sequence_error(
6933            ledger.required_reserve(),
6934            ClaimFrontierInvalidReason::ProductRange,
6935        ));
6936    }
6937    Ok(live_times_terminal)
6938}
6939
6940fn validate_replacement_product_range(
6941    range: Option<ReplacementTerminalProductRangeRestore>,
6942    recovery: Option<RecoverySequenceBlockRestore>,
6943    recovery_provenance: Option<RecoveryClaimProvenance>,
6944    live_count: u64,
6945    ledger: SequenceLedger,
6946) -> Result<Option<ReplacementTerminalProductRange>, ClaimFrontierError> {
6947    let validated = match (range, recovery, recovery_provenance) {
6948        (None, None, None) => None,
6949        (Some(range), Some(_), Some(provenance)) if range.length == live_count => {
6950            Some(ReplacementTerminalProductRange {
6951                start: range.start,
6952                length: range.length,
6953                participant_index: provenance.participant_index,
6954                marker_delivery_seq: provenance.marker_delivery_seq,
6955                prior_binding_epoch: provenance.prior_binding_epoch,
6956            })
6957        }
6958        (Some(range), _, _) => {
6959            return Err(sequence_error(
6960                sequence_ordinal(ledger, range.start),
6961                ClaimFrontierInvalidReason::ProductRange,
6962            ));
6963        }
6964        (None, Some(_), _) | (None, None, Some(_)) => {
6965            return Err(sequence_error(
6966                ledger.required_reserve(),
6967                ClaimFrontierInvalidReason::ProductRange,
6968            ));
6969        }
6970    };
6971    Ok(validated)
6972}
6973
6974fn validate_exit_product_ranges(
6975    active: &ActiveIdentityRanks,
6976    mut ranges: Vec<ExitProductRangeRestore>,
6977    other_count: u64,
6978    ledger: SequenceLedger,
6979) -> Result<Vec<ExitProductRange>, ClaimFrontierError> {
6980    ranges.sort_by_key(|range| range.start);
6981    let mut seen_exits = Vec::new();
6982    let mut other_live_times_exit = Vec::new();
6983    if other_count == 0 && !ranges.is_empty() {
6984        return Err(sequence_error(
6985            ledger.required_reserve(),
6986            ClaimFrontierInvalidReason::ProductRange,
6987        ));
6988    }
6989    for range in ranges {
6990        let ordinal = sequence_ordinal(ledger, range.start);
6991        if range.length != other_count
6992            || !active.contains(range.exit_participant)
6993            || seen_exits.contains(&range.exit_participant)
6994        {
6995            return Err(sequence_error(
6996                ordinal,
6997                ClaimFrontierInvalidReason::ProductRange,
6998            ));
6999        }
7000        seen_exits.push(range.exit_participant);
7001        other_live_times_exit.push(ExitProductRange {
7002            start: range.start,
7003            length: range.length,
7004            exit_participant: range.exit_participant,
7005        });
7006    }
7007    seen_exits.sort_unstable();
7008    let expected_exit_ranges = if other_count == 0 {
7009        0
7010    } else {
7011        active.participants.len()
7012    };
7013    if seen_exits.len() != expected_exit_ranges
7014        || !seen_exits.iter().copied().eq(active
7015            .participants
7016            .iter()
7017            .take(expected_exit_ranges)
7018            .map(|participant| participant.participant_index))
7019    {
7020        return Err(sequence_error(
7021            ledger.required_reserve(),
7022            ClaimFrontierInvalidReason::ProductRange,
7023        ));
7024    }
7025    Ok(other_live_times_exit)
7026}
7027
7028fn validate_order_recovery(
7029    active: &ActiveIdentityRanks,
7030    recovery: Option<RecoveryOrderBlockRestore>,
7031    provenance: Option<RecoveryClaimProvenance>,
7032    ledger: OrderLedger,
7033    frontier_length: u128,
7034) -> Result<(), ClaimFrontierError> {
7035    let claims = ledger.claims();
7036    let expected = claims.recovery_operation() && claims.recovery_replacement_terminal();
7037    match (expected, recovery, provenance) {
7038        (false, None, None) => Ok(()),
7039        (true, None, _) | (true, Some(_), None) | (false, None, Some(_)) => Err(order_error(
7040            frontier_length,
7041            ClaimFrontierInvalidReason::RecoveryBlock,
7042        )),
7043        (false, Some(block), _) => Err(order_error(
7044            order_ordinal(ledger, block_start_order(block)),
7045            ClaimFrontierInvalidReason::RecoveryBlock,
7046        )),
7047        (true, Some(block), Some(provenance)) => {
7048            let block_ordinal = order_ordinal(ledger, block_start_order(block));
7049            let expected_recovery_operation = block
7050                .active_binding
7051                .map_or(Some(block.recovery_operation_order), |active_binding| {
7052                    active_binding.transaction_order.checked_add(1)
7053                });
7054            if expected_recovery_operation != Some(block.recovery_operation_order) {
7055                return Err(order_error(
7056                    block_ordinal + 1,
7057                    ClaimFrontierInvalidReason::RecoveryBlock,
7058                ));
7059            }
7060            if block.recovery_operation_order.checked_add(1)
7061                != Some(block.replacement_terminal_order)
7062            {
7063                return Err(order_error(
7064                    block_ordinal + u128::from(block.active_binding.is_some()) + 1,
7065                    ClaimFrontierInvalidReason::RecoveryBlock,
7066                ));
7067            }
7068            let Some(participant) = active_participant(active, provenance.participant_index) else {
7069                return Err(order_error(
7070                    block_ordinal,
7071                    ClaimFrontierInvalidReason::LogicalOwner,
7072                ));
7073            };
7074            let expected_binding = match provenance.phase {
7075                RecoveryClaimPhase::PreFate => {
7076                    FrontierBinding::Bound(provenance.prior_binding_epoch)
7077                }
7078                RecoveryClaimPhase::PostFate => {
7079                    FrontierBinding::Detached(provenance.prior_binding_epoch)
7080                }
7081                RecoveryClaimPhase::RecoveredBound => {
7082                    FrontierBinding::Bound(provenance.current_binding_epoch)
7083                }
7084            };
7085            if participant.binding != expected_binding {
7086                return Err(order_error(
7087                    block_ordinal,
7088                    ClaimFrontierInvalidReason::LogicalOwner,
7089                ));
7090            }
7091            let active_binding_valid = match (provenance.phase, block.active_binding) {
7092                (RecoveryClaimPhase::PreFate, Some(active_binding)) => {
7093                    active_binding.owner.participant_index == provenance.participant_index
7094                        && active_binding.owner.binding_epoch == provenance.prior_binding_epoch
7095                }
7096                (RecoveryClaimPhase::PostFate | RecoveryClaimPhase::RecoveredBound, None) => true,
7097                _ => false,
7098            };
7099            if !active_binding_valid {
7100                return Err(order_error(
7101                    block_ordinal,
7102                    ClaimFrontierInvalidReason::RecoveryBlock,
7103                ));
7104            }
7105            Ok(())
7106        }
7107    }
7108}
7109
7110fn validate_order_candidates(
7111    restore: &[ImmutableOrderCandidateMajorRestore],
7112    ledger: OrderLedger,
7113) -> Result<Vec<ImmutableOrderCandidateMajor>, ClaimFrontierError> {
7114    let mut groups = restore.to_vec();
7115    groups.sort_by_key(|group| group.transaction_order);
7116    let mut seen_keys = Vec::new();
7117    let mut previous_major = None;
7118    let mut validated = Vec::new();
7119    for group in groups {
7120        let ordinal = order_ordinal(ledger, group.transaction_order);
7121        let below_allocated_high = matches!(
7122            ledger.high(),
7123            OrderHigh::Allocated(high) if group.transaction_order < high
7124        );
7125        if group.candidate_keys.is_empty()
7126            || previous_major == Some(group.transaction_order)
7127            || below_allocated_high
7128        {
7129            return Err(order_error(
7130                ordinal,
7131                ClaimFrontierInvalidReason::CandidateKey,
7132            ));
7133        }
7134        previous_major = Some(group.transaction_order);
7135        let mut previous = None;
7136        for key in &group.candidate_keys {
7137            if key.transaction_order() != group.transaction_order
7138                || previous.is_some_and(|previous| previous >= *key)
7139                || seen_keys.contains(key)
7140            {
7141                return Err(order_error(
7142                    ordinal,
7143                    ClaimFrontierInvalidReason::CandidateKey,
7144                ));
7145            }
7146            previous = Some(*key);
7147            seen_keys.push(*key);
7148        }
7149        validated.push(ImmutableOrderCandidateMajor {
7150            transaction_order: group.transaction_order,
7151            candidate_keys: group.candidate_keys,
7152        });
7153    }
7154    Ok(validated)
7155}
7156
7157fn validate_order_direct_owners(
7158    active: &ActiveIdentityRanks,
7159    movable: &[MovableOrderClaim],
7160    recovery: Option<RecoveryOrderBlockRestore>,
7161    ledger: OrderLedger,
7162) -> Result<(), ClaimFrontierError> {
7163    let mut ordered = movable.to_vec();
7164    ordered.sort_by_key(|claim| claim.transaction_order);
7165    let mut exits = Vec::new();
7166    let mut terminals = Vec::new();
7167    for claim in ordered {
7168        let ordinal = order_ordinal(ledger, claim.transaction_order);
7169        match claim.owner {
7170            OrderDirectOwner::MembershipExit { participant_index } => {
7171                if !active.contains(participant_index) || exits.contains(&participant_index) {
7172                    return Err(order_error(
7173                        ordinal,
7174                        ClaimFrontierInvalidReason::LogicalOwner,
7175                    ));
7176                }
7177                exits.push(participant_index);
7178            }
7179            OrderDirectOwner::ActiveBindingTerminal(owner) => {
7180                if !terminal_matches_bound(active, owner) || terminals.contains(&owner) {
7181                    return Err(order_error(
7182                        ordinal,
7183                        ClaimFrontierInvalidReason::LogicalOwner,
7184                    ));
7185                }
7186                terminals.push(owner);
7187            }
7188        }
7189    }
7190    if let Some(active_binding) = recovery.and_then(|block| block.active_binding) {
7191        let ordinal = order_ordinal(ledger, active_binding.transaction_order);
7192        if !terminal_matches_bound(active, active_binding.owner)
7193            || terminals.contains(&active_binding.owner)
7194        {
7195            return Err(order_error(
7196                ordinal,
7197                ClaimFrontierInvalidReason::LogicalOwner,
7198            ));
7199        }
7200        terminals.push(active_binding.owner);
7201    }
7202    exits.sort_unstable();
7203    if exits.len() != active.participants.len()
7204        || !exits.iter().copied().eq(active
7205            .participants
7206            .iter()
7207            .map(|participant| participant.participant_index))
7208    {
7209        return Err(order_error(
7210            ledger.claims().total(),
7211            ClaimFrontierInvalidReason::LogicalOwner,
7212        ));
7213    }
7214    terminals.sort_by_key(terminal_owner_sort_key);
7215    if usize_to_u128(terminals.len()) != u128::from(ledger.claims().active_binding_terminals()) {
7216        return Err(order_error(
7217            ledger.claims().total(),
7218            ClaimFrontierInvalidReason::LogicalOwner,
7219        ));
7220    }
7221    Ok(())
7222}
7223
7224fn validate_cross_counter(
7225    sequence: &SequenceClaimFrontier,
7226    order: &OrderClaimFrontier,
7227) -> Result<(), ClaimFrontierError> {
7228    match (sequence.recovery, order.recovery) {
7229        (None, None) => {}
7230        (Some(sequence_block), Some(order_block))
7231            if sequence_block.participant_index == order_block.participant_index
7232                && sequence_block.marker_delivery_seq == order_block.marker_delivery_seq
7233                && sequence_block.recovered_binding_epoch
7234                    == order_block.recovered_binding_epoch
7235                && sequence_block.terminal.map(|terminal| terminal.owner)
7236                    == order_block.active_binding.map(|active| active.owner) => {}
7237        (Some(sequence_block), _) => {
7238            return Err(sequence_error(
7239                sequence_ordinal(
7240                    sequence.ledger,
7241                    block_start_validated_sequence(sequence_block),
7242                ),
7243                ClaimFrontierInvalidReason::RecoveryBlock,
7244            ));
7245        }
7246        (None, Some(order_block)) => {
7247            return Err(order_error(
7248                order_ordinal(order.ledger, block_start_validated_order(order_block)),
7249                ClaimFrontierInvalidReason::RecoveryBlock,
7250            ));
7251        }
7252    }
7253
7254    let mut order_candidate_keys = Vec::new();
7255    for group in &order.immutable_candidates {
7256        order_candidate_keys.extend(group.candidate_keys.iter().copied());
7257    }
7258    for candidate in &sequence.immutable_candidates {
7259        let key = candidate.admission_order();
7260        if !order_candidate_keys.contains(&key) {
7261            return Err(sequence_error(
7262                sequence_ordinal(sequence.ledger, candidate.delivery_seq()),
7263                ClaimFrontierInvalidReason::CandidateKey,
7264            ));
7265        }
7266    }
7267    for group in &order.immutable_candidates {
7268        for key in &group.candidate_keys {
7269            if !sequence
7270                .immutable_candidates
7271                .iter()
7272                .any(|candidate| candidate.admission_order() == *key)
7273            {
7274                return Err(order_error(
7275                    order_ordinal(order.ledger, group.transaction_order),
7276                    ClaimFrontierInvalidReason::CandidateKey,
7277                ));
7278            }
7279        }
7280    }
7281
7282    let mut sequence_movable_terminals = Vec::new();
7283    for claim in &sequence.movable_claims {
7284        if let SequenceDirectOwner::BindingTerminal(owner) = claim.owner {
7285            sequence_movable_terminals.push(owner);
7286        }
7287    }
7288    if let Some(terminal) = sequence.recovery.and_then(|block| block.terminal) {
7289        sequence_movable_terminals.push(terminal.owner);
7290    }
7291    let mut order_movable_terminals = Vec::new();
7292    for claim in &order.movable_claims {
7293        if let OrderDirectOwner::ActiveBindingTerminal(owner) = claim.owner {
7294            order_movable_terminals.push(owner);
7295        }
7296    }
7297    if let Some(active_binding) = order.recovery.and_then(|block| block.active_binding) {
7298        order_movable_terminals.push(active_binding.owner);
7299    }
7300    sequence_movable_terminals.sort_by_key(terminal_owner_sort_key);
7301    order_movable_terminals.sort_by_key(terminal_owner_sort_key);
7302    if sequence_movable_terminals != order_movable_terminals {
7303        return Err(sequence_error(
7304            sequence.ledger.required_reserve(),
7305            ClaimFrontierInvalidReason::LogicalOwner,
7306        ));
7307    }
7308    Ok(())
7309}
7310
7311const fn marker_provenance_targets(provenance: MarkerProvenance, target: ParticipantId) -> bool {
7312    match provenance {
7313        MarkerProvenance::NonProductM => true,
7314        MarkerProvenance::TerminalProduct {
7315            affected_participant,
7316            ..
7317        } => affected_participant == target,
7318        MarkerProvenance::ExitProduct {
7319            exit_participant,
7320            remaining_participant,
7321        } => exit_participant != remaining_participant && remaining_participant == target,
7322    }
7323}
7324
7325fn marker_has_causal_authority(
7326    marker: MarkerCandidateAuthority,
7327    records: &[RetainedCausalRecord],
7328    historical: &[HistoricalCausalAuthority],
7329) -> bool {
7330    if marker.provenance == MarkerProvenance::NonProductM {
7331        return true;
7332    }
7333    let retained_match = records.iter().any(|record| {
7334        if record.admission_order.transaction_order() != marker.admission_order.transaction_order()
7335        {
7336            return false;
7337        }
7338        match marker.provenance {
7339            MarkerProvenance::NonProductM => true,
7340            MarkerProvenance::TerminalProduct {
7341                terminal: TerminalProductSource::Binding(owner),
7342                ..
7343            } => matches!(
7344                record.kind,
7345                RetainedCausalRecordKind::BindingTerminal(actual) if actual == owner
7346            ),
7347            MarkerProvenance::TerminalProduct {
7348                terminal:
7349                    TerminalProductSource::RecoveryReplacement {
7350                        participant_index,
7351                        binding_epoch,
7352                    },
7353                ..
7354            } => matches!(
7355                record.kind,
7356                RetainedCausalRecordKind::BindingTerminal(owner)
7357                    if owner.participant_index == participant_index
7358                        && owner.binding_epoch == binding_epoch
7359            ),
7360            MarkerProvenance::ExitProduct {
7361                exit_participant, ..
7362            } => matches!(
7363                record.kind,
7364                RetainedCausalRecordKind::MembershipExit { participant_index }
7365                    if participant_index == exit_participant
7366            ),
7367        }
7368    });
7369    retained_match
7370        || historical
7371            .iter()
7372            .any(|authority| match (marker.provenance, authority.kind) {
7373                (
7374                    MarkerProvenance::TerminalProduct {
7375                        terminal: TerminalProductSource::Binding(expected),
7376                        ..
7377                    },
7378                    HistoricalCausalKind::BindingTerminal(owner),
7379                ) => {
7380                    owner == expected
7381                        && authority.admission_order.transaction_order()
7382                            == marker.admission_order.transaction_order()
7383                }
7384                (
7385                    MarkerProvenance::TerminalProduct {
7386                        terminal:
7387                            TerminalProductSource::RecoveryReplacement {
7388                                participant_index,
7389                                binding_epoch,
7390                            },
7391                        ..
7392                    },
7393                    HistoricalCausalKind::BindingTerminal(owner),
7394                ) => {
7395                    owner.participant_index == participant_index
7396                        && owner.binding_epoch == binding_epoch
7397                        && authority.admission_order.transaction_order()
7398                            == marker.admission_order.transaction_order()
7399                }
7400                (
7401                    MarkerProvenance::ExitProduct {
7402                        exit_participant, ..
7403                    },
7404                    HistoricalCausalKind::MembershipExit(participant_index),
7405                ) => {
7406                    participant_index == exit_participant
7407                        && authority.admission_order.transaction_order()
7408                            == marker.admission_order.transaction_order()
7409                }
7410                _ => false,
7411            })
7412}
7413
7414fn terminal_matches_active(active: &ActiveIdentityRanks, owner: BindingTerminalOwner) -> bool {
7415    active_participant(active, owner.participant_index)
7416        .is_some_and(|participant| binding_epoch(participant.binding) == owner.binding_epoch)
7417}
7418
7419fn terminal_matches_bound(active: &ActiveIdentityRanks, owner: BindingTerminalOwner) -> bool {
7420    active_participant(active, owner.participant_index).is_some_and(|participant| {
7421        participant.binding == FrontierBinding::Bound(owner.binding_epoch)
7422    })
7423}
7424
7425fn active_participant(
7426    active: &ActiveIdentityRanks,
7427    participant_index: ParticipantId,
7428) -> Option<FrontierParticipant> {
7429    active
7430        .participants
7431        .binary_search_by_key(&participant_index, |participant| {
7432            participant.participant_index
7433        })
7434        .ok()
7435        .and_then(|index| active.participants.get(index))
7436        .copied()
7437}
7438
7439const fn binding_epoch(binding: FrontierBinding) -> BindingEpoch {
7440    match binding {
7441        FrontierBinding::Bound(epoch) | FrontierBinding::Detached(epoch) => epoch,
7442    }
7443}
7444
7445fn block_start_sequence(block: RecoverySequenceBlockRestore) -> DeliverySeq {
7446    block
7447        .terminal
7448        .map_or(block.recovery_attach_seq, |terminal| terminal.delivery_seq)
7449}
7450
7451fn block_start_validated_sequence(block: RecoverySequenceBlock) -> DeliverySeq {
7452    block
7453        .terminal
7454        .map_or(block.recovery_attach_seq, |terminal| terminal.delivery_seq)
7455}
7456
7457fn block_start_order(block: RecoveryOrderBlockRestore) -> TransactionOrder {
7458    block
7459        .active_binding
7460        .map_or(block.recovery_operation_order, |active_binding| {
7461            active_binding.transaction_order
7462        })
7463}
7464
7465fn block_start_validated_order(block: RecoveryOrderBlock) -> TransactionOrder {
7466    block
7467        .active_binding
7468        .map_or(block.recovery_operation_order, |active_binding| {
7469            active_binding.transaction_order
7470        })
7471}
7472
7473fn order_frontier_start(high: OrderHigh) -> u128 {
7474    match high {
7475        OrderHigh::Empty => 0,
7476        OrderHigh::Allocated(high) => u128::from(high) + 1,
7477    }
7478}
7479
7480const fn order_is_above_high(value: TransactionOrder, high: OrderHigh) -> bool {
7481    match high {
7482        OrderHigh::Empty => true,
7483        OrderHigh::Allocated(high) => value > high,
7484    }
7485}
7486
7487fn order_frontier_candidate_count(restore: &OrderClaimFrontierRestore, high: OrderHigh) -> u128 {
7488    usize_to_u128(
7489        restore
7490            .immutable_candidates
7491            .iter()
7492            .filter(|candidate| order_is_above_high(candidate.transaction_order, high))
7493            .count(),
7494    )
7495}
7496
7497fn sequence_ordinal(ledger: SequenceLedger, value: DeliverySeq) -> u128 {
7498    u128::from(value).saturating_sub(u128::from(ledger.high_watermark()) + 1)
7499}
7500
7501fn order_ordinal(ledger: OrderLedger, value: TransactionOrder) -> u128 {
7502    u128::from(value).saturating_sub(order_frontier_start(ledger.high()))
7503}
7504
7505fn checked_rank_value(start: DeliverySeq, active_rank: usize) -> Option<DeliverySeq> {
7506    let rank = u64::try_from(active_rank).ok()?;
7507    start.checked_add(rank)
7508}
7509
7510fn usize_to_u64(value: usize) -> u64 {
7511    u64::try_from(value).unwrap_or(u64::MAX)
7512}
7513
7514fn usize_to_u128(value: usize) -> u128 {
7515    u64::try_from(value).map_or(u128::MAX, u128::from)
7516}
7517
7518fn rank_index(rank: usize) -> u128 {
7519    usize_to_u128(rank)
7520}
7521
7522const fn frontier_error(
7523    counter: ClaimFrontierCounter,
7524    first_bad_position: u128,
7525    reason: ClaimFrontierInvalidReason,
7526) -> ClaimFrontierError {
7527    ClaimFrontierError {
7528        counter,
7529        first_bad_position,
7530        reason,
7531    }
7532}
7533
7534const fn sequence_error(
7535    first_bad_position: u128,
7536    reason: ClaimFrontierInvalidReason,
7537) -> ClaimFrontierError {
7538    frontier_error(
7539        ClaimFrontierCounter::DeliverySequence,
7540        first_bad_position,
7541        reason,
7542    )
7543}
7544
7545const fn order_error(
7546    first_bad_position: u128,
7547    reason: ClaimFrontierInvalidReason,
7548) -> ClaimFrontierError {
7549    frontier_error(
7550        ClaimFrontierCounter::TransactionOrder,
7551        first_bad_position,
7552        reason,
7553    )
7554}