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