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