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