Skip to main content

automation_structures/
api.rs

1//! Checked public entry points for ordinary Rust consumers.
2//!
3//! Proof-oriented carriers mirror formal actions and use Verus preconditions.
4//! These public types keep their carrier invariants internal and convert every
5//! caller-controlled action guard into an executable result.
6
7use crate::connectives::cursor::Cursor as CursorCarrier;
8use crate::primitives::actuation_pass::ActuationPass as ActuationPassCarrier;
9use crate::primitives::audit_sink::AuditSink as AuditSinkCarrier;
10use crate::primitives::backtracking_traversal::BacktrackingTraversal as BacktrackingTraversalCarrier;
11use crate::primitives::budget::Budget as BudgetCarrier;
12use crate::primitives::competitive_selection::{
13    CompetitiveSelectionHard as CompetitiveSelectionHardCarrier,
14    CompetitiveSelectionHardExclusive as CompetitiveSelectionHardExclusiveCarrier,
15    CompetitiveSelectionRanked as CompetitiveSelectionRankedCarrier,
16    CompetitiveSelectionSoft as CompetitiveSelectionSoftCarrier,
17};
18use crate::primitives::convergence_governor_phase_aware::ConvergenceGovernorPhaseAware as ConvergenceGovernorCarrier;
19use crate::primitives::propagation_pass::PropagationPass as PropagationPassCarrier;
20use crate::primitives::quality_hierarchy::QualityHierarchy as QualityHierarchyCarrier;
21use crate::primitives::resource_registry::ResourceRegistry as RegistryCarrier;
22use vstd::prelude::*;
23
24pub use crate::primitives::convergence_governor_phase_aware::{
25    GovState as ConvergenceState, Phase as ConvergencePhase,
26};
27pub use crate::primitives::propagation_pass::Round as PropagationRound;
28
29verus! {
30
31/// A disabled budget transition.
32#[derive(Clone, Copy, Debug, Eq, PartialEq)]
33#[non_exhaustive]
34pub enum BudgetError {
35    /// The requested amount exceeds the held reservation.
36    AmountExceedsReservation,
37    /// The requested amount exceeds the committed allocation.
38    AmountExceedsAllocation,
39    /// The requested amount exceeds the pending eviction amount.
40    AmountExceedsPendingEviction,
41}
42
43/// A checked budget whose three claims cannot exceed its fixed capacity.
44pub struct Budget {
45    inner: BudgetCarrier,
46}
47
48impl Budget {
49    #[verifier::type_invariant]
50    closed spec fn well_formed(&self) -> bool {
51        self.inner.safety_invariant()
52    }
53
54    /// Construct an empty budget with `capacity` units.
55    pub fn new(capacity: u64) -> (budget: Self) {
56        let inner = BudgetCarrier::new(capacity);
57        Self { inner }
58    }
59
60    /// Return the fixed budget ceiling.
61    pub fn capacity(&self) -> u64 {
62        self.inner.capacity
63    }
64
65    /// Return units committed for use.
66    pub fn allocated(&self) -> u64 {
67        self.inner.allocated
68    }
69
70    /// Return units held but not committed.
71    pub fn reserved(&self) -> u64 {
72        self.inner.reserved
73    }
74
75    /// Return units currently being reclaimed.
76    pub fn pending_eviction(&self) -> u64 {
77        self.inner.pending_eviction
78    }
79
80    /// Return units not claimed by any budget state.
81    pub fn available(&self) -> (available: u64) {
82        proof { use_type_invariant(&*self); }
83        self.inner.available()
84    }
85
86    /// Try to commit unused capacity directly.
87    #[must_use]
88    pub fn try_allocate(&mut self, amount: u64) -> (accepted: bool) {
89        proof { use_type_invariant(&*self); }
90        let mut carrier = budget_sentinel();
91        core::mem::swap(&mut self.inner, &mut carrier);
92        let accepted = carrier.try_allocate(amount);
93        core::mem::swap(&mut self.inner, &mut carrier);
94        accepted
95    }
96
97    /// Try to reserve unused capacity.
98    #[must_use]
99    pub fn try_reserve(&mut self, amount: u64) -> (accepted: bool) {
100        proof { use_type_invariant(&*self); }
101        let mut carrier = budget_sentinel();
102        core::mem::swap(&mut self.inner, &mut carrier);
103        let accepted = carrier.reserve(amount);
104        core::mem::swap(&mut self.inner, &mut carrier);
105        accepted
106    }
107
108    /// Move held capacity into committed allocation.
109    pub fn commit_reservation(&mut self, amount: u64) -> (result: Result<(), BudgetError>) {
110        proof { use_type_invariant(&*self); }
111        if amount <= self.inner.reserved {
112            let mut carrier = budget_sentinel();
113            core::mem::swap(&mut self.inner, &mut carrier);
114            carrier.commit_reservation(amount);
115            core::mem::swap(&mut self.inner, &mut carrier);
116            Ok(())
117        } else {
118            Err(BudgetError::AmountExceedsReservation)
119        }
120    }
121
122    /// Release committed allocation.
123    pub fn release(&mut self, amount: u64) -> (result: Result<(), BudgetError>) {
124        proof { use_type_invariant(&*self); }
125        if amount <= self.inner.allocated {
126            let mut carrier = budget_sentinel();
127            core::mem::swap(&mut self.inner, &mut carrier);
128            carrier.release(amount);
129            core::mem::swap(&mut self.inner, &mut carrier);
130            Ok(())
131        } else {
132            Err(BudgetError::AmountExceedsAllocation)
133        }
134    }
135
136    /// Move committed allocation into pending eviction.
137    pub fn mark_eviction(&mut self, amount: u64) -> (result: Result<(), BudgetError>) {
138        proof { use_type_invariant(&*self); }
139        if amount <= self.inner.allocated {
140            let mut carrier = budget_sentinel();
141            core::mem::swap(&mut self.inner, &mut carrier);
142            carrier.mark_eviction(amount);
143            core::mem::swap(&mut self.inner, &mut carrier);
144            Ok(())
145        } else {
146            Err(BudgetError::AmountExceedsAllocation)
147        }
148    }
149
150    /// Finish reclaiming pending eviction.
151    pub fn complete_eviction(&mut self, amount: u64) -> (result: Result<(), BudgetError>) {
152        proof { use_type_invariant(&*self); }
153        if amount <= self.inner.pending_eviction {
154            let mut carrier = budget_sentinel();
155            core::mem::swap(&mut self.inner, &mut carrier);
156            carrier.complete_eviction(amount);
157            core::mem::swap(&mut self.inner, &mut carrier);
158            Ok(())
159        } else {
160            Err(BudgetError::AmountExceedsPendingEviction)
161        }
162    }
163}
164
165/// A unique-key resource registry.
166pub struct ResourceRegistry {
167    inner: RegistryCarrier,
168}
169
170impl ResourceRegistry {
171    #[verifier::type_invariant]
172    closed spec fn well_formed(&self) -> bool {
173        self.inner.unique_mapping()
174    }
175
176    /// Construct an empty registry.
177    pub fn new() -> (registry: Self) {
178        let inner = RegistryCarrier::new();
179        Self { inner }
180    }
181
182    /// Number of registered keys.
183    pub fn len(&self) -> usize {
184        self.inner.entries.len()
185    }
186
187    /// Whether no keys are registered.
188    pub fn is_empty(&self) -> bool {
189        self.inner.entries.is_empty()
190    }
191
192    /// Look up a registered value.
193    pub fn get(&self, key: u64) -> (value: Option<u64>) {
194        proof { use_type_invariant(&*self); }
195        self.inner.lookup(key)
196    }
197
198    /// Insert or replace a key and return its previous value.
199    pub fn insert(&mut self, key: u64, value: u64) -> (previous: Option<u64>) {
200        proof { use_type_invariant(&*self); }
201        let previous = self.inner.lookup(key);
202        let mut carrier = registry_sentinel();
203        core::mem::swap(&mut self.inner, &mut carrier);
204        carrier.register(key, value);
205        core::mem::swap(&mut self.inner, &mut carrier);
206        previous
207    }
208
209    /// Remove a key and return its previous value.
210    pub fn remove(&mut self, key: u64) -> (previous: Option<u64>) {
211        proof { use_type_invariant(&*self); }
212        let previous = self.inner.lookup(key);
213        match previous {
214            Some(value) => {
215                let mut carrier = registry_sentinel();
216                core::mem::swap(&mut self.inner, &mut carrier);
217                carrier.deregister(key);
218                core::mem::swap(&mut self.inner, &mut carrier);
219                Some(value)
220            },
221            None => None,
222        }
223    }
224
225    /// Read an entry by storage index for deterministic inspection.
226    #[expect(clippy::indexing_slicing, reason = "the branch proves the registry index is in bounds")]
227    pub fn entry(&self, index: usize) -> Option<(u64, u64)> {
228        if index < self.inner.entries.len() {
229            Some(self.inner.entries[index])
230        } else {
231            None
232        }
233    }
234}
235
236/// A public immutable audit record.
237#[derive(Clone, Copy, Debug, Eq, PartialEq)]
238pub struct AuditRecord {
239    /// The operation recorded by the sink.
240    pub operation: u64,
241    /// The predecessor hash stored in this record.
242    pub previous_hash: u64,
243    /// The record's concrete model hash.
244    pub hash: u64,
245}
246
247/// A bounded append-only audit chain.
248pub struct AuditSink {
249    inner: AuditSinkCarrier,
250}
251
252impl AuditSink {
253    #[verifier::type_invariant]
254    closed spec fn well_formed(&self) -> bool {
255        self.inner.inv()
256    }
257
258    /// Construct an empty sink with a fixed record capacity.
259    pub fn new(capacity: usize) -> (sink: Self) {
260        let inner = AuditSinkCarrier::new(capacity);
261        Self { inner }
262    }
263
264    /// Maximum number of retained records.
265    pub fn capacity(&self) -> usize {
266        self.inner.max_log_len
267    }
268
269    /// Number of retained records.
270    pub fn len(&self) -> usize {
271        self.inner.log.len()
272    }
273
274    /// Whether the sink contains no records.
275    pub fn is_empty(&self) -> bool {
276        self.inner.log.is_empty()
277    }
278
279    /// Current chain head.
280    pub fn last_hash(&self) -> u64 {
281        self.inner.last_hash
282    }
283
284    /// Append an operation if capacity remains.
285    #[must_use]
286    pub fn try_record(&mut self, operation: u64) -> (accepted: bool) {
287        proof { use_type_invariant(&*self); }
288        let mut carrier = audit_sentinel();
289        core::mem::swap(&mut self.inner, &mut carrier);
290        let accepted = carrier.record(operation);
291        core::mem::swap(&mut self.inner, &mut carrier);
292        accepted
293    }
294
295    /// Recompute and validate the concrete structural chain.
296    pub fn validate(&self) -> (valid: bool) {
297        proof { use_type_invariant(&*self); }
298        self.inner.validate()
299    }
300
301    /// Read an immutable record by index.
302    #[expect(clippy::indexing_slicing, reason = "the branch proves the audit index is in bounds")]
303    pub fn record(&self, index: usize) -> Option<AuditRecord> {
304        if index < self.inner.log.len() {
305            let entry = &self.inner.log[index];
306            Some(AuditRecord {
307                operation: entry.operation,
308                previous_hash: entry.prev_hash,
309                hash: entry.hash,
310            })
311        } else {
312            None
313        }
314    }
315}
316
317/// A rejected monotone Cursor movement.
318#[derive(Clone, Copy, Debug, Eq, PartialEq)]
319#[non_exhaustive]
320pub enum CursorError {
321    /// The requested position precedes the retained position.
322    Regression,
323}
324
325/// A checked retained position for consumer progress.
326pub struct Cursor {
327    inner: CursorCarrier,
328}
329
330impl Cursor {
331    /// Construct a cursor at `position`.
332    pub fn new(position: usize) -> (cursor: Self) {
333        Self { inner: CursorCarrier::new(position) }
334    }
335
336    /// Read the retained position.
337    pub fn position(&self) -> usize {
338        self.inner.position
339    }
340
341    /// Move monotonically to `position`.
342    pub fn advance_to(&mut self, position: usize) -> (result: Result<(), CursorError>) {
343        if position < self.inner.position {
344            return Err(CursorError::Regression);
345        }
346        self.inner.advance_to(position);
347        Ok(())
348    }
349}
350
351/// Invalid construction input for a propagation pass.
352#[derive(Clone, Copy, Debug, Eq, PartialEq)]
353#[non_exhaustive]
354pub enum PropagationBuildError {
355    /// An initial value exceeds the declared value ceiling.
356    InitialValueOutOfRange,
357    /// An edge endpoint is not a node in the initial value vector.
358    EdgeEndpointOutOfRange,
359}
360
361/// A disabled propagation transition.
362#[derive(Clone, Copy, Debug, Eq, PartialEq)]
363#[non_exhaustive]
364pub enum PropagationError {
365    /// A node index is outside the admitted graph.
366    NodeOutOfRange,
367    /// A round is already running.
368    RoundAlreadyRunning,
369    /// The operation requires a running round.
370    RoundNotRunning,
371    /// The node has already committed its update in this round.
372    NodeAlreadyUpdated,
373    /// Not every node has committed an update.
374    RoundIncomplete,
375    /// The pass is settled or has exhausted its iteration ceiling.
376    PassTerminated,
377    /// The pass is not yet settled and has not reached its ceiling.
378    PassStillRunning,
379}
380
381/// A snapshot-local bounded propagation pass.
382pub struct PropagationPass {
383    inner: PropagationPassCarrier,
384}
385
386impl PropagationPass {
387    #[verifier::type_invariant]
388    closed spec fn well_formed(&self) -> bool {
389        self.inner.inv()
390    }
391
392    /// Validate and construct a pass. The node universe is the initial value length.
393    pub fn new(
394        max_iterations: u64,
395        max_value: u64,
396        edges: Vec<(usize, usize)>,
397        initial_values: Vec<u64>,
398    ) -> (result: Result<Self, PropagationBuildError>) {
399        if !values_within_max(&initial_values, max_value) {
400            return Err(PropagationBuildError::InitialValueOutOfRange);
401        }
402        let num_nodes = initial_values.len();
403        if !edges_within_nodes(&edges, num_nodes) {
404            return Err(PropagationBuildError::EdgeEndpointOutOfRange);
405        }
406        let inner = PropagationPassCarrier::new(
407            num_nodes,
408            max_iterations,
409            max_value,
410            edges,
411            initial_values,
412        );
413        Ok(Self { inner })
414    }
415
416    /// Number of admitted nodes.
417    pub fn num_nodes(&self) -> usize {
418        self.inner.num_nodes
419    }
420
421    /// Maximum charged rounds.
422    pub fn max_iterations(&self) -> u64 {
423        self.inner.max_iterations
424    }
425
426    /// Largest admitted node value.
427    pub fn max_value(&self) -> u64 {
428        self.inner.max_value
429    }
430
431    /// Number of completed rounds.
432    pub fn iteration(&self) -> u64 {
433        self.inner.iteration
434    }
435
436    /// Current round phase.
437    pub fn round(&self) -> PropagationRound {
438        self.inner.round
439    }
440
441    /// Whether the previous completed round changed a value.
442    pub fn changed(&self) -> bool {
443        self.inner.changed
444    }
445
446    /// Read a current node value.
447    #[expect(clippy::indexing_slicing, reason = "the branch proves the node index is in bounds")]
448    pub fn value(&self, node: usize) -> Option<u64> {
449        if node < self.inner.values.len() {
450            Some(self.inner.values[node])
451        } else {
452            None
453        }
454    }
455
456    /// Read the round-start value for a node.
457    #[expect(clippy::indexing_slicing, reason = "the branch proves the snapshot index is in bounds")]
458    pub fn snapshot_value(&self, node: usize) -> Option<u64> {
459        if node < self.inner.snapshot.len() {
460            Some(self.inner.snapshot[node])
461        } else {
462            None
463        }
464    }
465
466    /// Whether a node has committed its update in the current round.
467    #[expect(clippy::indexing_slicing, reason = "the branch proves the update index is in bounds")]
468    pub fn node_updated(&self, node: usize) -> Option<bool> {
469        if node < self.inner.updated.len() {
470            Some(self.inner.updated[node])
471        } else {
472            None
473        }
474    }
475
476    /// Begin a new snapshot round.
477    pub fn start_round(&mut self) -> (result: Result<(), PropagationError>) {
478        proof { use_type_invariant(&*self); }
479        match self.inner.round {
480            PropagationRound::Running => {
481                return Err(PropagationError::RoundAlreadyRunning);
482            },
483            PropagationRound::Idle => {},
484        }
485        if !self.inner.changed || self.inner.iteration >= self.inner.max_iterations {
486            return Err(PropagationError::PassTerminated);
487        }
488        let mut carrier = propagation_sentinel();
489        core::mem::swap(&mut self.inner, &mut carrier);
490        carrier.start_round();
491        core::mem::swap(&mut self.inner, &mut carrier);
492        Ok(())
493    }
494
495    /// Commit one node's snapshot-local update.
496    #[expect(clippy::indexing_slicing, reason = "the branch proves the update index is in bounds")]
497    pub fn update_node(&mut self, node: usize) -> (result: Result<(), PropagationError>) {
498        proof { use_type_invariant(&*self); }
499        match self.inner.round {
500            PropagationRound::Idle => {
501                return Err(PropagationError::RoundNotRunning);
502            },
503            PropagationRound::Running => {},
504        }
505        if node >= self.inner.num_nodes {
506            return Err(PropagationError::NodeOutOfRange);
507        }
508        if self.inner.updated[node] {
509            return Err(PropagationError::NodeAlreadyUpdated);
510        }
511        let mut carrier = propagation_sentinel();
512        core::mem::swap(&mut self.inner, &mut carrier);
513        carrier.update_node(node);
514        core::mem::swap(&mut self.inner, &mut carrier);
515        Ok(())
516    }
517
518    /// Finish a fully updated round and charge one iteration.
519    pub fn end_round(&mut self) -> (result: Result<(), PropagationError>) {
520        proof { use_type_invariant(&*self); }
521        match self.inner.round {
522            PropagationRound::Idle => {
523                return Err(PropagationError::RoundNotRunning);
524            },
525            PropagationRound::Running => {},
526        }
527        if !self.inner.all_nodes_updated() {
528            return Err(PropagationError::RoundIncomplete);
529        }
530        let mut carrier = propagation_sentinel();
531        core::mem::swap(&mut self.inner, &mut carrier);
532        carrier.end_round();
533        core::mem::swap(&mut self.inner, &mut carrier);
534        Ok(())
535    }
536
537    /// Confirm the terminal self-loop at settlement or the iteration ceiling.
538    pub fn terminate(&mut self) -> (result: Result<(), PropagationError>) {
539        proof { use_type_invariant(&*self); }
540        match self.inner.round {
541            PropagationRound::Running => {
542                return Err(PropagationError::RoundAlreadyRunning);
543            },
544            PropagationRound::Idle => {},
545        }
546        if self.inner.changed && self.inner.iteration != self.inner.max_iterations {
547            return Err(PropagationError::PassStillRunning);
548        }
549        let mut carrier = propagation_sentinel();
550        core::mem::swap(&mut self.inner, &mut carrier);
551        carrier.terminate();
552        core::mem::swap(&mut self.inner, &mut carrier);
553        Ok(())
554    }
555}
556
557/// A disabled actuation transition.
558#[derive(Clone, Copy, Debug, Eq, PartialEq)]
559#[non_exhaustive]
560pub enum ActuationError {
561    /// A seat index is outside the admitted seat universe.
562    SeatOutOfRange,
563    /// The pass has already committed closure.
564    PassComplete,
565    /// The seat already holds a resource.
566    SeatAlreadyAllocated,
567    /// The seat holds no resource.
568    SeatUnallocated,
569    /// The seat has already committed its effect.
570    SeatAlreadyActuated,
571    /// At least one allocated seat has not committed its effect.
572    PassIncomplete,
573}
574
575/// A governed resource actuation pass.
576pub struct ActuationPass {
577    inner: ActuationPassCarrier,
578}
579
580impl ActuationPass {
581    #[verifier::type_invariant]
582    closed spec fn well_formed(&self) -> bool {
583        self.inner.invariant()
584    }
585
586    /// Construct a pass over the supplied allocation record.
587    pub fn new(allocation: Vec<Option<u64>>) -> (pass: Self) {
588        let num_seats = allocation.len();
589        let inner = ActuationPassCarrier::new(allocation, num_seats);
590        Self { inner }
591    }
592
593    /// Number of governed seats.
594    pub fn len(&self) -> usize {
595        self.inner.num_seats
596    }
597
598    /// Whether the pass has no seats.
599    pub fn is_empty(&self) -> bool {
600        self.inner.num_seats == 0
601    }
602
603    /// Whether closure has committed.
604    pub fn is_complete(&self) -> bool {
605        self.inner.complete
606    }
607
608    /// Read the current resource held by a seat.
609    #[expect(clippy::indexing_slicing, reason = "the branch proves the allocation index is in bounds")]
610    pub fn allocation(&self, seat: usize) -> Option<Option<u64>> {
611        if seat < self.inner.allocation.len() {
612            Some(self.inner.allocation[seat])
613        } else {
614            None
615        }
616    }
617
618    /// Read the resource whose effect has committed for a seat.
619    #[expect(clippy::indexing_slicing, reason = "the branch proves the effect index is in bounds")]
620    pub fn effect(&self, seat: usize) -> Option<Option<u64>> {
621        if seat < self.inner.effects.len() {
622            Some(self.inner.effects[seat])
623        } else {
624            None
625        }
626    }
627
628    /// Assign an unallocated seat.
629    pub fn allocate(&mut self, seat: usize, resource: u64) -> (result: Result<(), ActuationError>) {
630        proof { use_type_invariant(&*self); }
631        if seat >= self.inner.num_seats {
632            return Err(ActuationError::SeatOutOfRange);
633        }
634        if self.inner.complete {
635            return Err(ActuationError::PassComplete);
636        }
637        if !self.inner.can_allocate(seat) {
638            return Err(ActuationError::SeatAlreadyAllocated);
639        }
640        let mut carrier = actuation_sentinel();
641        core::mem::swap(&mut self.inner, &mut carrier);
642        carrier.allocate(seat, resource);
643        core::mem::swap(&mut self.inner, &mut carrier);
644        Ok(())
645    }
646
647    /// Withdraw a seat that has not committed an effect.
648    pub fn deallocate(&mut self, seat: usize) -> (result: Result<(), ActuationError>) {
649        proof { use_type_invariant(&*self); }
650        if seat >= self.inner.num_seats {
651            return Err(ActuationError::SeatOutOfRange);
652        }
653        if self.inner.complete {
654            return Err(ActuationError::PassComplete);
655        }
656        if !self.inner.is_allocated(seat) {
657            return Err(ActuationError::SeatUnallocated);
658        }
659        if !self.inner.can_deallocate(seat) {
660            return Err(ActuationError::SeatAlreadyActuated);
661        }
662        let mut carrier = actuation_sentinel();
663        core::mem::swap(&mut self.inner, &mut carrier);
664        carrier.deallocate(seat);
665        core::mem::swap(&mut self.inner, &mut carrier);
666        Ok(())
667    }
668
669    /// Commit the effect for an allocated seat.
670    pub fn actuate(&mut self, seat: usize) -> (result: Result<(), ActuationError>) {
671        proof { use_type_invariant(&*self); }
672        if seat >= self.inner.num_seats {
673            return Err(ActuationError::SeatOutOfRange);
674        }
675        if self.inner.complete {
676            return Err(ActuationError::PassComplete);
677        }
678        if !self.inner.is_allocated(seat) {
679            return Err(ActuationError::SeatUnallocated);
680        }
681        if !self.inner.can_actuate(seat) {
682            return Err(ActuationError::SeatAlreadyActuated);
683        }
684        let mut carrier = actuation_sentinel();
685        core::mem::swap(&mut self.inner, &mut carrier);
686        carrier.actuate(seat);
687        core::mem::swap(&mut self.inner, &mut carrier);
688        Ok(())
689    }
690
691    /// Whether every allocated seat has committed an effect.
692    pub fn ready_to_finish(&self) -> (ready: bool) {
693        proof { use_type_invariant(&*self); }
694        self.inner.ready_to_finish_exec()
695    }
696
697    /// Commit closure after every allocated seat has committed an effect.
698    pub fn finish(&mut self) -> (result: Result<(), ActuationError>) {
699        proof { use_type_invariant(&*self); }
700        if self.inner.complete {
701            return Err(ActuationError::PassComplete);
702        }
703        if !self.inner.ready_to_finish_exec() {
704            return Err(ActuationError::PassIncomplete);
705        }
706        let mut carrier = actuation_sentinel();
707        core::mem::swap(&mut self.inner, &mut carrier);
708        carrier.finish();
709        core::mem::swap(&mut self.inner, &mut carrier);
710        Ok(())
711    }
712}
713
714/// A disabled quality-hierarchy transition.
715#[derive(Clone, Copy, Debug, Eq, PartialEq)]
716#[non_exhaustive]
717pub enum QualityHierarchyError {
718    /// A node index is outside the admitted node set.
719    NodeOutOfRange,
720    /// A proposed parent index is outside the admitted node set.
721    ParentOutOfRange,
722    /// A proposed child index is outside the admitted node set.
723    ChildOutOfRange,
724    /// A proposed level exceeds the hierarchy ceiling.
725    LevelOutOfRange,
726    /// A proposed cost exceeds the hierarchy ceiling.
727    CostOutOfRange,
728    /// Node-property updates require an isolated node.
729    NodeNotIsolated,
730    /// A node cannot be its own child.
731    SelfEdge,
732    /// The exact parent-child edge already exists.
733    EdgeAlreadyExists,
734    /// The proposed child already has a parent.
735    ChildAlreadyParented,
736    /// Parent level must strictly exceed child level.
737    LevelOrderViolation,
738    /// Parent cost must not exceed child cost.
739    CostOrderViolation,
740}
741
742/// A checked refinement forest over levels, costs, parents, and child edges.
743pub struct QualityHierarchy {
744    inner: QualityHierarchyCarrier,
745}
746
747impl QualityHierarchy {
748    #[verifier::type_invariant]
749    closed spec fn well_formed(&self) -> bool {
750        self.inner.type_invariant()
751            && self.inner.strict_level_descent()
752            && self.inner.parent_edge_agreement()
753            && self.inner.cost_monotonicity()
754    }
755
756    /// Construct a discrete hierarchy with no parent-child edges.
757    pub fn new(num_nodes: usize, max_level: u64) -> (hierarchy: Self) {
758        let inner = QualityHierarchyCarrier::new(num_nodes, max_level);
759        Self { inner }
760    }
761
762    /// Number of admitted nodes.
763    pub fn len(&self) -> usize {
764        self.inner.num_nodes
765    }
766
767    /// Whether the hierarchy has no nodes.
768    pub fn is_empty(&self) -> bool {
769        self.inner.num_nodes == 0
770    }
771
772    /// Maximum admitted level and cost value.
773    pub fn max_level(&self) -> u64 {
774        self.inner.max_level
775    }
776
777    /// Read one node level.
778    pub fn level(&self, node: usize) -> Option<u64> {
779        proof { use_type_invariant(&*self); }
780        if node < self.inner.num_nodes {
781            Some(self.inner.level_of(node))
782        } else {
783            None
784        }
785    }
786
787    /// Read one node cost.
788    pub fn cost(&self, node: usize) -> Option<u64> {
789        proof { use_type_invariant(&*self); }
790        if node < self.inner.num_nodes {
791            Some(self.inner.cost_of(node))
792        } else {
793            None
794        }
795    }
796
797    /// Read one parent, returning `None` for a root or an invalid node.
798    pub fn parent(&self, node: usize) -> Option<usize> {
799        proof { use_type_invariant(&*self); }
800        if node >= self.inner.num_nodes {
801            return None;
802        }
803        let parent = self.inner.parent_of(node);
804        if parent == self.inner.num_nodes {
805            None
806        } else {
807            Some(parent)
808        }
809    }
810
811    /// Number of retained parent-child edges.
812    pub fn edge_count(&self) -> usize {
813        self.inner.edges.len()
814    }
815
816    /// Read one parent-child edge by deterministic carrier order.
817    #[expect(clippy::indexing_slicing, reason = "the branch proves the hierarchy edge index is in bounds")]
818    pub fn edge(&self, index: usize) -> Option<(usize, usize)> {
819        if index < self.inner.edges.len() {
820            Some(self.inner.edges[index])
821        } else {
822            None
823        }
824    }
825
826    /// Set the level and cost of an isolated node.
827    pub fn set_node_properties(
828        &mut self,
829        node: usize,
830        level: u64,
831        cost: u64,
832    ) -> (result: Result<(), QualityHierarchyError>) {
833        proof { use_type_invariant(&*self); }
834        if node >= self.inner.num_nodes {
835            return Err(QualityHierarchyError::NodeOutOfRange);
836        }
837        if level > self.inner.max_level {
838            return Err(QualityHierarchyError::LevelOutOfRange);
839        }
840        if cost > self.inner.max_level {
841            return Err(QualityHierarchyError::CostOutOfRange);
842        }
843        if !self.inner.can_set_node_properties(node, level, cost) {
844            return Err(QualityHierarchyError::NodeNotIsolated);
845        }
846        let mut carrier = quality_hierarchy_sentinel();
847        core::mem::swap(&mut self.inner, &mut carrier);
848        carrier.set_node_properties(node, level, cost);
849        core::mem::swap(&mut self.inner, &mut carrier);
850        Ok(())
851    }
852
853    /// Add one admitted parent-child relation.
854    pub fn add_child(
855        &mut self,
856        parent: usize,
857        child: usize,
858    ) -> (result: Result<(), QualityHierarchyError>) {
859        proof { use_type_invariant(&*self); }
860        if parent >= self.inner.num_nodes {
861            return Err(QualityHierarchyError::ParentOutOfRange);
862        }
863        if child >= self.inner.num_nodes {
864            return Err(QualityHierarchyError::ChildOutOfRange);
865        }
866        if self.inner.can_add_child(parent, child) {
867            let mut carrier = quality_hierarchy_sentinel();
868            core::mem::swap(&mut self.inner, &mut carrier);
869            carrier.add_child(parent, child);
870            core::mem::swap(&mut self.inner, &mut carrier);
871            return Ok(());
872        }
873        if parent == child {
874            Err(QualityHierarchyError::SelfEdge)
875        } else if self.inner.has_edge(parent, child) {
876            Err(QualityHierarchyError::EdgeAlreadyExists)
877        } else if self.inner.parent_of(child) != self.inner.num_nodes {
878            Err(QualityHierarchyError::ChildAlreadyParented)
879        } else if self.inner.level_of(parent) <= self.inner.level_of(child) {
880            Err(QualityHierarchyError::LevelOrderViolation)
881        } else {
882            Err(QualityHierarchyError::CostOrderViolation)
883        }
884    }
885}
886
887/// Invalid BacktrackingTraversal construction input.
888#[derive(Clone, Copy, Debug, Eq, PartialEq)]
889#[non_exhaustive]
890pub enum BacktrackingBuildError {
891    /// The initial auxiliary value must be in the canonical modulo-three domain.
892    InitialAuxOutOfRange,
893}
894
895/// A disabled BacktrackingTraversal transition.
896#[derive(Clone, Copy, Debug, Eq, PartialEq)]
897#[non_exhaustive]
898pub enum BacktrackingError {
899    /// Descent is disabled at a full-depth leaf.
900    AtLeaf,
901    /// The branch choice is outside the admitted branch set.
902    ChoiceOutOfRange,
903    /// The mutation delta is outside the canonical inverse-pair domain.
904    DeltaOutOfRange,
905    /// Visit requires a full-depth leaf.
906    NotLeaf,
907    /// The current leaf was already recorded.
908    AlreadyVisited,
909    /// Ascent is disabled at the root.
910    AtRoot,
911}
912
913/// A checked paired do-undo backtracking traversal.
914pub struct BacktrackingTraversal {
915    inner: BacktrackingTraversalCarrier,
916}
917
918impl BacktrackingTraversal {
919    #[verifier::type_invariant]
920    closed spec fn well_formed(&self) -> bool {
921        self.inner.inv()
922    }
923
924    /// Validate and construct an empty traversal.
925    pub fn new(
926        branch_factor: u64,
927        max_depth: usize,
928        initial_aux: u64,
929    ) -> (result: Result<Self, BacktrackingBuildError>) {
930        if initial_aux >= 3 {
931            return Err(BacktrackingBuildError::InitialAuxOutOfRange);
932        }
933        let inner = BacktrackingTraversalCarrier::new(branch_factor, max_depth, initial_aux);
934        Ok(Self { inner })
935    }
936
937    /// Maximum admitted traversal depth.
938    pub fn max_depth(&self) -> usize {
939        self.inner.max_depth
940    }
941
942    /// Current path depth.
943    pub fn depth(&self) -> usize {
944        self.inner.path.len()
945    }
946
947    /// Current auxiliary state.
948    pub fn auxiliary(&self) -> u64 {
949        self.inner.aux
950    }
951
952    /// Number of recorded leaves.
953    pub fn visited_count(&self) -> usize {
954        self.inner.visited.len()
955    }
956
957    /// Whether the current path is a full-depth leaf.
958    pub fn is_leaf(&self) -> bool {
959        self.inner.is_leaf_exec()
960    }
961
962    /// Read one current branch choice.
963    #[expect(clippy::indexing_slicing, reason = "the branch proves the path index is in bounds")]
964    pub fn choice(&self, depth: usize) -> Option<u64> {
965        if depth < self.inner.path.len() {
966            Some(self.inner.path[depth])
967        } else {
968            None
969        }
970    }
971
972    /// Descend one level and record the paired undo token.
973    pub fn descend(&mut self, choice: u64, delta: u64) -> (result: Result<(), BacktrackingError>) {
974        proof { use_type_invariant(&*self); }
975        if self.inner.is_leaf_exec() {
976            return Err(BacktrackingError::AtLeaf);
977        }
978        if choice < 1 || choice > self.inner.branch_factor {
979            return Err(BacktrackingError::ChoiceOutOfRange);
980        }
981        if delta < 1 || delta > 2 {
982            return Err(BacktrackingError::DeltaOutOfRange);
983        }
984        let mut carrier = backtracking_sentinel();
985        core::mem::swap(&mut self.inner, &mut carrier);
986        carrier.descend(choice, delta);
987        core::mem::swap(&mut self.inner, &mut carrier);
988        Ok(())
989    }
990
991    /// Record the current leaf when it has not been visited.
992    pub fn visit(&mut self) -> (result: Result<(), BacktrackingError>) {
993        proof { use_type_invariant(&*self); }
994        if !self.inner.is_leaf_exec() {
995            return Err(BacktrackingError::NotLeaf);
996        }
997        if !self.inner.can_visit() {
998            return Err(BacktrackingError::AlreadyVisited);
999        }
1000        let mut carrier = backtracking_sentinel();
1001        core::mem::swap(&mut self.inner, &mut carrier);
1002        carrier.visit();
1003        core::mem::swap(&mut self.inner, &mut carrier);
1004        Ok(())
1005    }
1006
1007    /// Ascend one level and restore the paired auxiliary state.
1008    pub fn ascend(&mut self) -> (result: Result<(), BacktrackingError>) {
1009        proof { use_type_invariant(&*self); }
1010        if !self.inner.can_ascend() {
1011            return Err(BacktrackingError::AtRoot);
1012        }
1013        let mut carrier = backtracking_sentinel();
1014        core::mem::swap(&mut self.inner, &mut carrier);
1015        carrier.ascend();
1016        core::mem::swap(&mut self.inner, &mut carrier);
1017        Ok(())
1018    }
1019}
1020
1021/// Invalid competitive-selection configuration or input.
1022#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1023#[non_exhaustive]
1024pub enum CompetitiveSelectionError {
1025    /// At least one candidate is required by this selection mode.
1026    NoCandidates,
1027    /// A candidate index is outside the admitted candidate set.
1028    CandidateOutOfRange,
1029    /// A seat index is outside the admitted seat set.
1030    SeatOutOfRange,
1031    /// The selected seat already holds an allocation.
1032    SeatAlreadyAllocated,
1033    /// Every candidate is currently allocated to another seat.
1034    NoCandidateAvailable,
1035    /// A score is outside the admitted score domain.
1036    ScoreOutOfRange,
1037    /// A score replacement has a different candidate count.
1038    ScoreCountMismatch,
1039    /// The weight total is smaller than the reserved unit per candidate.
1040    WeightTotalBelowReservedFloor,
1041    /// The weight total exceeds the verified arithmetic ceiling.
1042    WeightTotalOutOfRange,
1043    /// The declared maximum score exceeds the verified arithmetic ceiling.
1044    MaxScoreOutOfRange,
1045    /// Every available soft-selection unit has already been assigned.
1046    AllocationComplete,
1047}
1048
1049/// Lowest-index argmax selection for one set of candidate scores.
1050pub struct CompetitiveSelectionHard {
1051    inner: CompetitiveSelectionHardCarrier,
1052}
1053
1054impl CompetitiveSelectionHard {
1055    #[verifier::type_invariant]
1056    closed spec fn well_formed(&self) -> bool {
1057        self.inner.inv() && self.inner.scores.len() >= 1
1058    }
1059
1060    /// Construct a selection over `num_candidates` initially zero-valued scores.
1061    pub fn new(num_candidates: usize) -> (result: Result<Self, CompetitiveSelectionError>) {
1062        if num_candidates == 0 {
1063            return Err(CompetitiveSelectionError::NoCandidates);
1064        }
1065        let inner = CompetitiveSelectionHardCarrier::new(num_candidates);
1066        Ok(Self { inner })
1067    }
1068
1069    /// Number of admitted candidates.
1070    pub fn len(&self) -> usize {
1071        self.inner.scores.len()
1072    }
1073
1074    /// Whether no candidates are admitted. Checked construction makes this always false.
1075    pub fn is_empty(&self) -> bool {
1076        false
1077    }
1078
1079    /// Read one candidate score.
1080    #[expect(clippy::indexing_slicing, reason = "the branch proves the candidate index is in bounds")]
1081    pub fn score(&self, candidate: usize) -> Option<u64> {
1082        if candidate < self.inner.scores.len() {
1083            Some(self.inner.scores[candidate])
1084        } else {
1085            None
1086        }
1087    }
1088
1089    /// Read the current winner, if the scores have been evaluated.
1090    pub fn winner(&self) -> Option<usize> {
1091        self.inner.allocation
1092    }
1093
1094    /// Replace one candidate score and invalidate the previous winner.
1095    pub fn update_score(
1096        &mut self,
1097        candidate: usize,
1098        score: u64,
1099    ) -> (result: Result<(), CompetitiveSelectionError>) {
1100        proof { use_type_invariant(&*self); }
1101        if candidate >= self.inner.scores.len() {
1102            return Err(CompetitiveSelectionError::CandidateOutOfRange);
1103        }
1104        let mut carrier = hard_selection_sentinel();
1105        core::mem::swap(&mut self.inner, &mut carrier);
1106        carrier.update_score(candidate, score);
1107        core::mem::swap(&mut self.inner, &mut carrier);
1108        Ok(())
1109    }
1110
1111    /// Select the lowest-index candidate among those with the maximum score.
1112    #[expect(clippy::manual_unwrap_or_default, reason = "the explicit match is supported by the Verus boundary")]
1113    pub fn evaluate(&mut self) -> (winner: usize) {
1114        proof { use_type_invariant(&*self); }
1115        let mut carrier = hard_selection_sentinel();
1116        core::mem::swap(&mut self.inner, &mut carrier);
1117        carrier.evaluate();
1118        let winner = match carrier.allocation {
1119            Some(value) => value,
1120            None => 0,
1121        };
1122        core::mem::swap(&mut self.inner, &mut carrier);
1123        winner
1124    }
1125}
1126
1127/// Lowest-index argmax selection across seats with exclusive candidates.
1128pub struct CompetitiveSelectionHardExclusive {
1129    inner: CompetitiveSelectionHardExclusiveCarrier,
1130}
1131
1132impl CompetitiveSelectionHardExclusive {
1133    #[verifier::type_invariant]
1134    closed spec fn well_formed(&self) -> bool {
1135        self.inner.inv()
1136    }
1137
1138    /// Construct `num_seats` empty allocations over a nonempty candidate set.
1139    pub fn new(
1140        num_seats: usize,
1141        num_candidates: usize,
1142        max_score: u64,
1143    ) -> (result: Result<Self, CompetitiveSelectionError>) {
1144        if num_candidates == 0 {
1145            return Err(CompetitiveSelectionError::NoCandidates);
1146        }
1147        let inner = CompetitiveSelectionHardExclusiveCarrier::new(
1148            num_seats,
1149            num_candidates,
1150            max_score,
1151        );
1152        Ok(Self { inner })
1153    }
1154
1155    /// Number of allocation seats.
1156    pub fn seat_count(&self) -> usize {
1157        self.inner.num_seats
1158    }
1159
1160    /// Number of candidates.
1161    pub fn candidate_count(&self) -> usize {
1162        self.inner.num_candidates
1163    }
1164
1165    /// Maximum admitted score.
1166    pub fn max_score(&self) -> u64 {
1167        self.inner.max_score
1168    }
1169
1170    /// Read one seat allocation, or `None` when the seat is invalid or unallocated.
1171    #[expect(clippy::indexing_slicing, reason = "the branch proves the seat index is in bounds")]
1172    #[expect(clippy::manual_map, reason = "the explicit match is supported by the Verus boundary")]
1173    pub fn allocation(&self, seat: usize) -> Option<usize> {
1174        proof { use_type_invariant(&*self); }
1175        if seat >= self.inner.num_seats {
1176            return None;
1177        }
1178        match self.inner.allocation[seat] {
1179            Some(candidate) => Some(candidate as usize),
1180            None => None,
1181        }
1182    }
1183
1184    /// Read one seat-candidate score.
1185    #[expect(clippy::indexing_slicing, reason = "the branches prove both score indices are in bounds")]
1186    pub fn score(&self, seat: usize, candidate: usize) -> Option<u64> {
1187        proof { use_type_invariant(&*self); }
1188        if seat >= self.inner.num_seats || candidate >= self.inner.num_candidates {
1189            None
1190        } else {
1191            Some(self.inner.scores[seat][candidate])
1192        }
1193    }
1194
1195    /// Whether a candidate is free for one seat.
1196    pub fn candidate_available(&self, seat: usize, candidate: usize) -> Option<bool> {
1197        proof { use_type_invariant(&*self); }
1198        if seat >= self.inner.num_seats || candidate >= self.inner.num_candidates {
1199            return None;
1200        }
1201        Some(self.inner.candidate_available(seat, candidate))
1202    }
1203
1204    /// Replace one score and invalidate every coupled seat allocation.
1205    pub fn update_score(
1206        &mut self,
1207        seat: usize,
1208        candidate: usize,
1209        score: u64,
1210    ) -> (result: Result<(), CompetitiveSelectionError>) {
1211        proof { use_type_invariant(&*self); }
1212        if seat >= self.inner.num_seats {
1213            return Err(CompetitiveSelectionError::SeatOutOfRange);
1214        }
1215        if candidate >= self.inner.num_candidates {
1216            return Err(CompetitiveSelectionError::CandidateOutOfRange);
1217        }
1218        if score > self.inner.max_score {
1219            return Err(CompetitiveSelectionError::ScoreOutOfRange);
1220        }
1221        let mut carrier = hard_exclusive_selection_sentinel();
1222        core::mem::swap(&mut self.inner, &mut carrier);
1223        carrier.update_score(seat, candidate, score);
1224        core::mem::swap(&mut self.inner, &mut carrier);
1225        Ok(())
1226    }
1227
1228    /// Select the lowest-index available argmax for one empty seat.
1229    #[expect(clippy::indexing_slicing, reason = "the guards prove the seat index is in bounds")]
1230    pub fn evaluate(
1231        &mut self,
1232        seat: usize,
1233    ) -> (result: Result<usize, CompetitiveSelectionError>) {
1234        proof { use_type_invariant(&*self); }
1235        if seat >= self.inner.num_seats {
1236            return Err(CompetitiveSelectionError::SeatOutOfRange);
1237        }
1238        if self.inner.allocation[seat].is_some() {
1239            return Err(CompetitiveSelectionError::SeatAlreadyAllocated);
1240        }
1241        if !self.inner.has_available(seat) {
1242            return Err(CompetitiveSelectionError::NoCandidateAvailable);
1243        }
1244        let mut carrier = hard_exclusive_selection_sentinel();
1245        core::mem::swap(&mut self.inner, &mut carrier);
1246        carrier.evaluate(seat);
1247        let winner = match carrier.allocation[seat] {
1248            Some(candidate) => candidate as usize,
1249            None => 0,
1250        };
1251        core::mem::swap(&mut self.inner, &mut carrier);
1252        Ok(winner)
1253    }
1254}
1255
1256/// Reserved-floor sequential Webster apportionment over mutable scores.
1257pub struct CompetitiveSelectionSoft {
1258    inner: CompetitiveSelectionSoftCarrier,
1259}
1260
1261impl CompetitiveSelectionSoft {
1262    #[verifier::type_invariant]
1263    closed spec fn well_formed(&self) -> bool {
1264        self.inner.mutable_score_inv()
1265    }
1266
1267    /// Construct a complete apportionment for `weight_total` units.
1268    pub fn new(
1269        scores: Vec<u64>,
1270        weight_total: u64,
1271        max_score: u64,
1272    ) -> (result: Result<Self, CompetitiveSelectionError>) {
1273        if scores.is_empty() {
1274            return Err(CompetitiveSelectionError::NoCandidates);
1275        }
1276        if weight_total > 1_000_000_000 {
1277            return Err(CompetitiveSelectionError::WeightTotalOutOfRange);
1278        }
1279        if max_score > 1_000_000_000 {
1280            return Err(CompetitiveSelectionError::MaxScoreOutOfRange);
1281        }
1282        if weight_total < scores.len() as u64 {
1283            return Err(CompetitiveSelectionError::WeightTotalBelowReservedFloor);
1284        }
1285        if !positive_values_within_max(&scores, max_score) {
1286            return Err(CompetitiveSelectionError::ScoreOutOfRange);
1287        }
1288        let inner = CompetitiveSelectionSoftCarrier::new(scores, weight_total, max_score);
1289        Ok(Self { inner })
1290    }
1291
1292    /// Construct only the reserved floor so awards can be assigned incrementally.
1293    pub fn begin(
1294        scores: Vec<u64>,
1295        weight_total: u64,
1296        max_score: u64,
1297    ) -> (result: Result<Self, CompetitiveSelectionError>) {
1298        if scores.is_empty() {
1299            return Err(CompetitiveSelectionError::NoCandidates);
1300        }
1301        if weight_total > 1_000_000_000 {
1302            return Err(CompetitiveSelectionError::WeightTotalOutOfRange);
1303        }
1304        if max_score > 1_000_000_000 {
1305            return Err(CompetitiveSelectionError::MaxScoreOutOfRange);
1306        }
1307        if weight_total < scores.len() as u64 {
1308            return Err(CompetitiveSelectionError::WeightTotalBelowReservedFloor);
1309        }
1310        if !positive_values_within_max(&scores, max_score) {
1311            return Err(CompetitiveSelectionError::ScoreOutOfRange);
1312        }
1313        let inner = CompetitiveSelectionSoftCarrier::init(scores, weight_total, max_score);
1314        Ok(Self { inner })
1315    }
1316
1317    /// Number of candidates.
1318    pub fn len(&self) -> usize {
1319        self.inner.scores.len()
1320    }
1321
1322    /// Whether no candidates are admitted. Checked construction makes this always false.
1323    pub fn is_empty(&self) -> bool {
1324        false
1325    }
1326
1327    /// Total weight to apportion.
1328    pub fn weight_total(&self) -> u64 {
1329        self.inner.weight_total
1330    }
1331
1332    /// Maximum admitted score.
1333    pub fn max_score(&self) -> u64 {
1334        self.inner.max_score
1335    }
1336
1337    /// Read one candidate score.
1338    #[expect(clippy::indexing_slicing, reason = "the branch proves the candidate index is in bounds")]
1339    pub fn score(&self, candidate: usize) -> Option<u64> {
1340        if candidate < self.inner.scores.len() {
1341            Some(self.inner.scores[candidate])
1342        } else {
1343            None
1344        }
1345    }
1346
1347    /// Read one candidate's current derived weight.
1348    pub fn weight(&self, candidate: usize) -> Option<u64> {
1349        proof { use_type_invariant(&*self); }
1350        if candidate < self.inner.extra.len() {
1351            Some(self.inner.weight_at(candidate))
1352        } else {
1353            None
1354        }
1355    }
1356
1357    /// Number of units assigned so far.
1358    pub fn assigned_weight(&self) -> u64 {
1359        proof { use_type_invariant(&*self); }
1360        self.inner.assigned_weight()
1361    }
1362
1363    /// Whether every unit has been assigned.
1364    pub fn is_complete(&self) -> bool {
1365        self.assigned_weight() == self.inner.weight_total
1366    }
1367
1368    /// Award the next available unit to the current lowest-index priority winner.
1369    pub fn assign_next(&mut self) -> (result: Result<usize, CompetitiveSelectionError>) {
1370        proof { use_type_invariant(&*self); }
1371        if self.inner.assigned_weight() >= self.inner.weight_total {
1372            return Err(CompetitiveSelectionError::AllocationComplete);
1373        }
1374        let mut carrier = soft_selection_sentinel();
1375        core::mem::swap(&mut self.inner, &mut carrier);
1376        let winner = carrier.assign_next();
1377        core::mem::swap(&mut self.inner, &mut carrier);
1378        Ok(winner)
1379    }
1380
1381    /// Replace one score and reset every candidate to its reserved unit.
1382    pub fn update_score(
1383        &mut self,
1384        candidate: usize,
1385        score: u64,
1386    ) -> (result: Result<(), CompetitiveSelectionError>) {
1387        proof { use_type_invariant(&*self); }
1388        if candidate >= self.inner.scores.len() {
1389            return Err(CompetitiveSelectionError::CandidateOutOfRange);
1390        }
1391        if score < 1 || score > self.inner.max_score {
1392            return Err(CompetitiveSelectionError::ScoreOutOfRange);
1393        }
1394        let mut carrier = soft_selection_sentinel();
1395        core::mem::swap(&mut self.inner, &mut carrier);
1396        carrier.update_score(candidate, score);
1397        core::mem::swap(&mut self.inner, &mut carrier);
1398        Ok(())
1399    }
1400}
1401
1402/// Stable top-k selection by descending score and ascending candidate index.
1403pub struct CompetitiveSelectionRanked {
1404    inner: CompetitiveSelectionRankedCarrier,
1405}
1406
1407impl CompetitiveSelectionRanked {
1408    #[verifier::type_invariant]
1409    closed spec fn well_formed(&self) -> bool {
1410        self.inner.inv()
1411    }
1412
1413    /// Construct an empty selection over the supplied scores.
1414    pub fn new(
1415        scores: Vec<u64>,
1416        k: usize,
1417        max_score: u64,
1418    ) -> (result: Result<Self, CompetitiveSelectionError>) {
1419        if !values_within_max(&scores, max_score) {
1420            return Err(CompetitiveSelectionError::ScoreOutOfRange);
1421        }
1422        let inner = CompetitiveSelectionRankedCarrier::new(scores, k, max_score);
1423        Ok(Self { inner })
1424    }
1425
1426    /// Number of candidates.
1427    pub fn len(&self) -> usize {
1428        self.inner.scores.len()
1429    }
1430
1431    /// Whether the ranked candidate set is empty.
1432    pub fn is_empty(&self) -> bool {
1433        self.inner.scores.is_empty()
1434    }
1435
1436    /// Requested maximum number of selected candidates.
1437    pub fn limit(&self) -> usize {
1438        self.inner.k
1439    }
1440
1441    /// Maximum admitted score.
1442    pub fn max_score(&self) -> u64 {
1443        self.inner.max_score
1444    }
1445
1446    /// Read one candidate score.
1447    #[expect(clippy::indexing_slicing, reason = "the branch proves the candidate index is in bounds")]
1448    pub fn score(&self, candidate: usize) -> Option<u64> {
1449        if candidate < self.inner.scores.len() {
1450            Some(self.inner.scores[candidate])
1451        } else {
1452            None
1453        }
1454    }
1455
1456    /// Whether one candidate is currently selected.
1457    #[expect(clippy::indexing_slicing, reason = "the branch proves the candidate index is in bounds")]
1458    pub fn is_selected(&self, candidate: usize) -> Option<bool> {
1459        if candidate < self.inner.selected.len() {
1460            Some(self.inner.selected[candidate])
1461        } else {
1462            None
1463        }
1464    }
1465
1466    /// Recompute the stable top-k selection.
1467    pub fn select(&mut self) {
1468        proof { use_type_invariant(&*self); }
1469        let mut carrier = ranked_selection_sentinel();
1470        core::mem::swap(&mut self.inner, &mut carrier);
1471        carrier.select();
1472        core::mem::swap(&mut self.inner, &mut carrier);
1473    }
1474
1475    /// Replace all scores and clear the current selection.
1476    pub fn update_scores(
1477        &mut self,
1478        scores: Vec<u64>,
1479    ) -> (result: Result<(), CompetitiveSelectionError>) {
1480        proof { use_type_invariant(&*self); }
1481        if scores.len() != self.inner.scores.len() {
1482            return Err(CompetitiveSelectionError::ScoreCountMismatch);
1483        }
1484        if !values_within_max(&scores, self.inner.max_score) {
1485            return Err(CompetitiveSelectionError::ScoreOutOfRange);
1486        }
1487        let mut carrier = ranked_selection_sentinel();
1488        core::mem::swap(&mut self.inner, &mut carrier);
1489        carrier.update_scores(scores);
1490        core::mem::swap(&mut self.inner, &mut carrier);
1491        Ok(())
1492    }
1493}
1494
1495/// Invalid convergence-governor construction input.
1496#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1497#[non_exhaustive]
1498pub enum ConvergenceBuildError {
1499    /// Doubling the convergence threshold would overflow the carrier arithmetic.
1500    ThresholdOutOfRange,
1501    /// A moving-average window must retain at least one delta.
1502    EmptyWindow,
1503    /// The largest admitted window sum would overflow `u64`.
1504    WindowSumOutOfRange,
1505}
1506
1507/// A disabled convergence-governor transition.
1508#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1509#[non_exhaustive]
1510pub enum ConvergenceError {
1511    /// The submitted delta exceeds the configured maximum.
1512    DeltaOutOfRange,
1513}
1514
1515/// A moving-window convergence state machine with peak-aware phases.
1516pub struct ConvergenceGovernor {
1517    inner: ConvergenceGovernorCarrier,
1518}
1519
1520impl ConvergenceGovernor {
1521    #[verifier::type_invariant]
1522    closed spec fn well_formed(&self) -> bool {
1523        self.inner.inv()
1524    }
1525
1526    /// Validate arithmetic bounds and construct an active governor.
1527    pub fn new(
1528        threshold: u64,
1529        awaken_threshold: u64,
1530        window: usize,
1531        max_delta: u64,
1532    ) -> (result: Result<Self, ConvergenceBuildError>) {
1533        if threshold > u64::MAX / 2 {
1534            return Err(ConvergenceBuildError::ThresholdOutOfRange);
1535        }
1536        if window == 0 {
1537            return Err(ConvergenceBuildError::EmptyWindow);
1538        }
1539        if window > 1_000_000_000 || max_delta > 1_000_000_000 {
1540            return Err(ConvergenceBuildError::WindowSumOutOfRange);
1541        }
1542        proof {
1543            assert(window as int * max_delta as int <= u64::MAX as int) by (nonlinear_arith)
1544                requires
1545                    window <= 1_000_000_000,
1546                    max_delta <= 1_000_000_000,
1547                    u64::MAX >= 1_000_000_000 * 1_000_000_000;
1548        }
1549        let inner = ConvergenceGovernorCarrier::new(
1550            threshold,
1551            awaken_threshold,
1552            window,
1553            max_delta,
1554        );
1555        Ok(Self { inner })
1556    }
1557
1558    /// Convergence threshold used by the state transition.
1559    pub fn threshold(&self) -> u64 {
1560        self.inner.threshold
1561    }
1562
1563    /// Activity threshold that awakens a converged governor.
1564    pub fn awaken_threshold(&self) -> u64 {
1565        self.inner.awaken_threshold
1566    }
1567
1568    /// Maximum retained history length.
1569    pub fn window(&self) -> usize {
1570        self.inner.window
1571    }
1572
1573    /// Maximum admitted delta.
1574    pub fn max_delta(&self) -> u64 {
1575        self.inner.max_delta
1576    }
1577
1578    /// Current convergence state.
1579    pub fn state(&self) -> ConvergenceState {
1580        self.inner.state
1581    }
1582
1583    /// Current peak-aware gradient phase.
1584    pub fn phase(&self) -> ConvergencePhase {
1585        self.inner.gradient_phase
1586    }
1587
1588    /// Whether a threshold event has ever been observed.
1589    pub fn peak_observed(&self) -> bool {
1590        self.inner.peak_observed
1591    }
1592
1593    /// Number of retained deltas.
1594    pub fn history_len(&self) -> usize {
1595        self.inner.delta_history.len()
1596    }
1597
1598    /// Read one retained delta from oldest to newest.
1599    #[expect(clippy::indexing_slicing, reason = "the branch proves the history index is in bounds")]
1600    pub fn history(&self, index: usize) -> Option<u64> {
1601        if index < self.inner.delta_history.len() {
1602            Some(self.inner.delta_history[index])
1603        } else {
1604            None
1605        }
1606    }
1607
1608    /// Submit one delta, returning the resulting moving-window average.
1609    pub fn update(&mut self, delta: u64) -> (result: Result<u64, ConvergenceError>) {
1610        proof { use_type_invariant(&*self); }
1611        if delta > self.inner.max_delta {
1612            return Err(ConvergenceError::DeltaOutOfRange);
1613        }
1614        let mut carrier = convergence_sentinel();
1615        core::mem::swap(&mut self.inner, &mut carrier);
1616        let average = carrier.update(delta);
1617        core::mem::swap(&mut self.inner, &mut carrier);
1618        Ok(average)
1619    }
1620}
1621
1622fn budget_sentinel() -> (carrier: BudgetCarrier)
1623    ensures carrier.safety_invariant(),
1624{
1625    BudgetCarrier::new(0)
1626}
1627
1628fn registry_sentinel() -> (carrier: RegistryCarrier)
1629    ensures carrier.unique_mapping(),
1630{
1631    RegistryCarrier::new()
1632}
1633
1634fn audit_sentinel() -> (carrier: AuditSinkCarrier)
1635    ensures carrier.inv(),
1636{
1637    AuditSinkCarrier::new(0)
1638}
1639
1640fn propagation_sentinel() -> (carrier: PropagationPassCarrier)
1641    ensures carrier.inv(),
1642{
1643    let edges: Vec<(usize, usize)> = Vec::new();
1644    let values: Vec<u64> = Vec::new();
1645    PropagationPassCarrier::new(0, 0, 0, edges, values)
1646}
1647
1648fn actuation_sentinel() -> (carrier: ActuationPassCarrier)
1649    ensures carrier.invariant(),
1650{
1651    let allocation: Vec<Option<u64>> = Vec::new();
1652    ActuationPassCarrier::new(allocation, 0)
1653}
1654
1655fn quality_hierarchy_sentinel() -> (carrier: QualityHierarchyCarrier)
1656    ensures
1657        carrier.type_invariant(),
1658        carrier.strict_level_descent(),
1659        carrier.parent_edge_agreement(),
1660        carrier.cost_monotonicity(),
1661{
1662    QualityHierarchyCarrier::new(0, 0)
1663}
1664
1665fn backtracking_sentinel() -> (carrier: BacktrackingTraversalCarrier)
1666    ensures carrier.inv(),
1667{
1668    BacktrackingTraversalCarrier::new(0, 0, 0)
1669}
1670
1671fn hard_selection_sentinel() -> (carrier: CompetitiveSelectionHardCarrier)
1672    ensures
1673        carrier.inv(),
1674        carrier.scores.len() >= 1,
1675{
1676    CompetitiveSelectionHardCarrier::new(1)
1677}
1678
1679fn hard_exclusive_selection_sentinel() -> (carrier: CompetitiveSelectionHardExclusiveCarrier)
1680    ensures carrier.inv(),
1681{
1682    CompetitiveSelectionHardExclusiveCarrier::new(0, 1, 0)
1683}
1684
1685fn soft_selection_sentinel() -> (carrier: CompetitiveSelectionSoftCarrier)
1686    ensures carrier.mutable_score_inv(),
1687{
1688    let mut scores: Vec<u64> = Vec::new();
1689    scores.push(1);
1690    CompetitiveSelectionSoftCarrier::init(scores, 1, 1)
1691}
1692
1693fn ranked_selection_sentinel() -> (carrier: CompetitiveSelectionRankedCarrier)
1694    ensures carrier.inv(),
1695{
1696    let scores: Vec<u64> = Vec::new();
1697    CompetitiveSelectionRankedCarrier::new(scores, 0, 0)
1698}
1699
1700fn convergence_sentinel() -> (carrier: ConvergenceGovernorCarrier)
1701    ensures carrier.inv(),
1702{
1703    ConvergenceGovernorCarrier::new(0, 0, 1, 0)
1704}
1705
1706#[expect(clippy::indexing_slicing, reason = "the loop proves the value index is in bounds")]
1707#[expect(clippy::arithmetic_side_effects, reason = "the loop proves the cursor remains within the vector")]
1708#[expect(clippy::ptr_arg, reason = "Verus sequence-view contracts are stated over Vec in this checked boundary")]
1709pub(crate) fn values_within_max(values: &Vec<u64>, max_value: u64) -> (valid: bool)
1710    ensures
1711        valid == (forall|i: int| 0 <= i < values.len() ==> values@[i] <= max_value),
1712{
1713    let mut index: usize = 0;
1714    while index < values.len()
1715        invariant
1716            index <= values.len(),
1717            forall|i: int| 0 <= i < index ==> values@[i] <= max_value,
1718        decreases values.len() - index,
1719    {
1720        if values[index] > max_value {
1721            assert(!(forall|i: int| 0 <= i < values.len() ==> values@[i] <= max_value));
1722            return false;
1723        }
1724        index += 1;
1725    }
1726    true
1727}
1728
1729#[expect(clippy::indexing_slicing, reason = "the loop proves the value index is in bounds")]
1730#[expect(clippy::arithmetic_side_effects, reason = "the loop proves the cursor remains within the vector")]
1731#[expect(clippy::ptr_arg, reason = "Verus sequence-view contracts are stated over Vec in this checked boundary")]
1732fn positive_values_within_max(values: &Vec<u64>, max_value: u64) -> (valid: bool)
1733    ensures
1734        valid == (forall|i: int| 0 <= i < values.len()
1735            ==> 1 <= #[trigger] values@[i] <= max_value),
1736{
1737    let mut index: usize = 0;
1738    while index < values.len()
1739        invariant
1740            index <= values.len(),
1741            forall|i: int| 0 <= i < index ==> 1 <= #[trigger] values@[i] <= max_value,
1742        decreases values.len() - index,
1743    {
1744        if values[index] < 1 || values[index] > max_value {
1745            assert(!(forall|i: int| 0 <= i < values.len()
1746                ==> 1 <= #[trigger] values@[i] <= max_value));
1747            return false;
1748        }
1749        index += 1;
1750    }
1751    true
1752}
1753
1754#[expect(clippy::indexing_slicing, reason = "the loop proves the edge index is in bounds")]
1755#[expect(clippy::arithmetic_side_effects, reason = "the loop proves the cursor remains within the vector")]
1756#[expect(clippy::ptr_arg, reason = "Verus sequence-view contracts are stated over Vec in this checked boundary")]
1757fn edges_within_nodes(edges: &Vec<(usize, usize)>, num_nodes: usize) -> (valid: bool)
1758    ensures
1759        valid == (forall|i: int| 0 <= i < edges.len()
1760            ==> edges@[i].0 < num_nodes && edges@[i].1 < num_nodes),
1761{
1762    let mut index: usize = 0;
1763    while index < edges.len()
1764        invariant
1765            index <= edges.len(),
1766            forall|i: int| 0 <= i < index
1767                ==> edges@[i].0 < num_nodes && edges@[i].1 < num_nodes,
1768        decreases edges.len() - index,
1769    {
1770        if edges[index].0 >= num_nodes || edges[index].1 >= num_nodes {
1771            assert(!(forall|i: int| 0 <= i < edges.len()
1772                ==> edges@[i].0 < num_nodes && edges@[i].1 < num_nodes));
1773            return false;
1774        }
1775        index += 1;
1776    }
1777    true
1778}
1779
1780} // verus!
1781
1782impl Default for ResourceRegistry {
1783    fn default() -> Self {
1784        Self::new()
1785    }
1786}
1787
1788macro_rules! impl_observational_debug {
1789    ($type:ty, $name:literal, $($field:literal => $method:ident),+ $(,)?) => {
1790        impl core::fmt::Debug for $type {
1791            fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1792                let mut state = formatter.debug_struct($name);
1793                $(state.field($field, &self.$method());)+
1794                state.finish()
1795            }
1796        }
1797    };
1798}
1799
1800impl_observational_debug!(Budget, "Budget",
1801    "capacity" => capacity,
1802    "allocated" => allocated,
1803    "reserved" => reserved,
1804    "pending_eviction" => pending_eviction,
1805    "available" => available,
1806);
1807impl_observational_debug!(ResourceRegistry, "ResourceRegistry", "len" => len);
1808impl_observational_debug!(AuditSink, "AuditSink",
1809    "capacity" => capacity,
1810    "len" => len,
1811    "last_hash" => last_hash,
1812    "valid" => validate,
1813);
1814impl_observational_debug!(Cursor, "Cursor", "position" => position);
1815impl_observational_debug!(PropagationPass, "PropagationPass",
1816    "num_nodes" => num_nodes,
1817    "max_iterations" => max_iterations,
1818    "iteration" => iteration,
1819    "round" => round,
1820    "changed" => changed,
1821);
1822impl_observational_debug!(ActuationPass, "ActuationPass",
1823    "len" => len,
1824    "complete" => is_complete,
1825    "ready_to_finish" => ready_to_finish,
1826);
1827impl_observational_debug!(QualityHierarchy, "QualityHierarchy",
1828    "len" => len,
1829    "max_level" => max_level,
1830    "edge_count" => edge_count,
1831);
1832impl_observational_debug!(BacktrackingTraversal, "BacktrackingTraversal",
1833    "max_depth" => max_depth,
1834    "depth" => depth,
1835    "auxiliary" => auxiliary,
1836    "visited_count" => visited_count,
1837    "leaf" => is_leaf,
1838);
1839impl_observational_debug!(CompetitiveSelectionHard, "CompetitiveSelectionHard",
1840    "len" => len,
1841    "winner" => winner,
1842);
1843impl_observational_debug!(CompetitiveSelectionHardExclusive, "CompetitiveSelectionHardExclusive",
1844    "seat_count" => seat_count,
1845    "candidate_count" => candidate_count,
1846    "max_score" => max_score,
1847);
1848impl_observational_debug!(CompetitiveSelectionSoft, "CompetitiveSelectionSoft",
1849    "len" => len,
1850    "weight_total" => weight_total,
1851    "assigned_weight" => assigned_weight,
1852    "max_score" => max_score,
1853    "complete" => is_complete,
1854);
1855impl_observational_debug!(CompetitiveSelectionRanked, "CompetitiveSelectionRanked",
1856    "len" => len,
1857    "limit" => limit,
1858    "max_score" => max_score,
1859);
1860impl_observational_debug!(ConvergenceGovernor, "ConvergenceGovernor",
1861    "threshold" => threshold,
1862    "awaken_threshold" => awaken_threshold,
1863    "window" => window,
1864    "max_delta" => max_delta,
1865    "state" => state,
1866    "phase" => phase,
1867    "peak_observed" => peak_observed,
1868    "history_len" => history_len,
1869);
1870
1871impl core::fmt::Display for BudgetError {
1872    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1873        formatter.write_str(match self {
1874            Self::AmountExceedsReservation => "amount exceeds the held reservation",
1875            Self::AmountExceedsAllocation => "amount exceeds the committed allocation",
1876            Self::AmountExceedsPendingEviction => "amount exceeds pending eviction",
1877        })
1878    }
1879}
1880
1881impl std::error::Error for BudgetError {}
1882
1883impl core::fmt::Display for CursorError {
1884    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1885        formatter.write_str(match self {
1886            Self::Regression => "cursor movement would regress the retained position",
1887        })
1888    }
1889}
1890
1891impl std::error::Error for CursorError {}
1892
1893impl core::fmt::Display for PropagationBuildError {
1894    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1895        formatter.write_str(match self {
1896            Self::InitialValueOutOfRange => "an initial value exceeds the declared value ceiling",
1897            Self::EdgeEndpointOutOfRange => "an edge endpoint is outside the admitted node set",
1898        })
1899    }
1900}
1901
1902impl std::error::Error for PropagationBuildError {}
1903
1904impl core::fmt::Display for PropagationError {
1905    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1906        formatter.write_str(match self {
1907            Self::NodeOutOfRange => "node is outside the admitted graph",
1908            Self::RoundAlreadyRunning => "a propagation round is already running",
1909            Self::RoundNotRunning => "no propagation round is running",
1910            Self::NodeAlreadyUpdated => "node already committed an update in this round",
1911            Self::RoundIncomplete => "not every node committed an update",
1912            Self::PassTerminated => "propagation pass is settled or exhausted",
1913            Self::PassStillRunning => "propagation pass has not reached a terminal state",
1914        })
1915    }
1916}
1917
1918impl std::error::Error for PropagationError {}
1919
1920impl core::fmt::Display for ActuationError {
1921    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1922        formatter.write_str(match self {
1923            Self::SeatOutOfRange => "seat is outside the admitted seat set",
1924            Self::PassComplete => "actuation pass is already complete",
1925            Self::SeatAlreadyAllocated => "seat already holds a resource",
1926            Self::SeatUnallocated => "seat holds no resource",
1927            Self::SeatAlreadyActuated => "seat already committed its effect",
1928            Self::PassIncomplete => "an allocated seat has not committed its effect",
1929        })
1930    }
1931}
1932
1933impl std::error::Error for ActuationError {}
1934
1935impl core::fmt::Display for QualityHierarchyError {
1936    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1937        formatter.write_str(match self {
1938            Self::NodeOutOfRange => "node is outside the admitted hierarchy",
1939            Self::ParentOutOfRange => "parent is outside the admitted hierarchy",
1940            Self::ChildOutOfRange => "child is outside the admitted hierarchy",
1941            Self::LevelOutOfRange => "level exceeds the hierarchy ceiling",
1942            Self::CostOutOfRange => "cost exceeds the hierarchy ceiling",
1943            Self::NodeNotIsolated => "node properties may change only while the node is isolated",
1944            Self::SelfEdge => "a hierarchy node cannot be its own child",
1945            Self::EdgeAlreadyExists => "the parent-child edge already exists",
1946            Self::ChildAlreadyParented => "the child already has a parent",
1947            Self::LevelOrderViolation => "parent level must strictly exceed child level",
1948            Self::CostOrderViolation => "parent cost must not exceed child cost",
1949        })
1950    }
1951}
1952
1953impl std::error::Error for QualityHierarchyError {}
1954
1955impl core::fmt::Display for BacktrackingBuildError {
1956    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1957        formatter.write_str(match self {
1958            Self::InitialAuxOutOfRange => {
1959                "initial auxiliary value is outside the modulo-three domain"
1960            }
1961        })
1962    }
1963}
1964
1965impl std::error::Error for BacktrackingBuildError {}
1966
1967impl core::fmt::Display for BacktrackingError {
1968    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1969        formatter.write_str(match self {
1970            Self::AtLeaf => "descent is disabled at a leaf",
1971            Self::ChoiceOutOfRange => "branch choice is outside the admitted branch set",
1972            Self::DeltaOutOfRange => "mutation delta must be one or two",
1973            Self::NotLeaf => "visit requires a full-depth leaf",
1974            Self::AlreadyVisited => "the current leaf was already visited",
1975            Self::AtRoot => "ascent is disabled at the root",
1976        })
1977    }
1978}
1979
1980impl std::error::Error for BacktrackingError {}
1981
1982impl core::fmt::Display for CompetitiveSelectionError {
1983    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1984        formatter.write_str(match self {
1985            Self::NoCandidates => "at least one candidate is required",
1986            Self::CandidateOutOfRange => "candidate is outside the admitted candidate set",
1987            Self::SeatOutOfRange => "seat is outside the admitted seat set",
1988            Self::SeatAlreadyAllocated => "seat already holds an allocation",
1989            Self::NoCandidateAvailable => "no candidate is available for the seat",
1990            Self::ScoreOutOfRange => "score is outside the admitted score domain",
1991            Self::ScoreCountMismatch => "replacement scores have a different candidate count",
1992            Self::WeightTotalBelowReservedFloor => {
1993                "weight total is smaller than the reserved candidate floor"
1994            }
1995            Self::WeightTotalOutOfRange => "weight total exceeds the verified arithmetic ceiling",
1996            Self::MaxScoreOutOfRange => "maximum score exceeds the verified arithmetic ceiling",
1997            Self::AllocationComplete => "all soft-selection weight has been assigned",
1998        })
1999    }
2000}
2001
2002impl std::error::Error for CompetitiveSelectionError {}
2003
2004impl core::fmt::Display for ConvergenceBuildError {
2005    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
2006        formatter.write_str(match self {
2007            Self::ThresholdOutOfRange => "convergence threshold cannot be doubled safely",
2008            Self::EmptyWindow => "convergence history window must be nonempty",
2009            Self::WindowSumOutOfRange => "maximum convergence window sum exceeds u64",
2010        })
2011    }
2012}
2013
2014impl std::error::Error for ConvergenceBuildError {}
2015
2016impl core::fmt::Display for ConvergenceError {
2017    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
2018        formatter.write_str(match self {
2019            Self::DeltaOutOfRange => "delta exceeds the configured maximum",
2020        })
2021    }
2022}
2023
2024impl std::error::Error for ConvergenceError {}