Skip to main content

automation_structures/
composition_api.rs

1//! Checked public entry points for reusable named compositions.
2
3use crate::api::values_within_max;
4use crate::compositions::allocation_snapshot::AllocationSnapshot as AllocationSnapshotCarrier;
5use crate::compositions::bisection::Bisection as BisectionCarrier;
6use crate::compositions::equivalence_class::EquivalenceClass as EquivalenceClassCarrier;
7use crate::compositions::federated_budget::FederatedBudget as FederatedBudgetCarrier;
8use crate::compositions::rate_limit::RateLimit as RateLimitCarrier;
9use crate::compositions::reduction::Reducer as ReductionCarrier;
10use crate::compositions::relationship_graph::RelationshipGraph as RelationshipGraphCarrier;
11use crate::compositions::sampler::Sampler as SamplerCarrier;
12use crate::compositions::signal::Signal as SignalCarrier;
13use crate::compositions::traversal_engine::TraversalEngine as TraversalEngineCarrier;
14use vstd::prelude::*;
15
16verus! {
17
18/// A disabled allocation-snapshot transition.
19#[derive(Clone, Copy, Debug, Eq, PartialEq)]
20#[non_exhaustive]
21pub enum AllocationSnapshotError {
22    /// The node is outside the configured node universe.
23    NodeOutOfRange,
24    /// The node is already present in the snapshot.
25    NodeAlreadyAccepted,
26    /// Accepted nodes must have a positive cost.
27    ZeroCost,
28    /// The node cost exceeds the remaining budget.
29    InsufficientBudget,
30}
31
32/// A reusable accepted-node snapshot coupled to one capacity budget.
33pub struct AllocationSnapshot {
34    inner: AllocationSnapshotCarrier,
35}
36
37impl AllocationSnapshot {
38    #[verifier::type_invariant]
39    closed spec fn well_formed(&self) -> bool {
40        self.inner.type_invariant() && self.inner.budget_consistency()
41    }
42
43    /// Construct an empty snapshot.
44    pub fn new(capacity: u64, num_nodes: u64) -> (snapshot: Self) {
45        Self { inner: AllocationSnapshotCarrier::new(capacity, num_nodes) }
46    }
47
48    /// Fixed capacity ceiling.
49    pub fn capacity(&self) -> u64 { self.inner.capacity }
50
51    /// Size of the admitted node universe.
52    pub fn num_nodes(&self) -> u64 { self.inner.num_nodes }
53
54    /// Cost accepted into the snapshot.
55    pub fn total_cost(&self) -> u64 { self.inner.total_cost }
56
57    /// Capacity not yet consumed.
58    pub fn budget_remaining(&self) -> u64 { self.inner.budget_remaining }
59
60    /// Number of accepted nodes.
61    pub fn len(&self) -> usize { self.inner.accepted.len() }
62
63    /// Whether no nodes have been accepted.
64    pub fn is_empty(&self) -> bool { self.inner.accepted.is_empty() }
65
66    /// Whether a node has been accepted.
67    pub fn contains(&self, node: u64) -> bool { self.inner.contains_exec(node) }
68
69    /// Read one accepted node by insertion order.
70    #[expect(clippy::indexing_slicing, reason = "the branch proves the accepted-node index is in bounds")]
71    pub fn accepted(&self, index: usize) -> Option<u64> {
72        if index < self.inner.accepted.len() { Some(self.inner.accepted[index]) } else { None }
73    }
74
75    /// Accept a fresh node whose positive cost fits the remaining capacity.
76    pub fn accept(&mut self, node: u64, cost: u64) -> (result: Result<(), AllocationSnapshotError>) {
77        proof { use_type_invariant(&*self); }
78        if node >= self.inner.num_nodes { return Err(AllocationSnapshotError::NodeOutOfRange); }
79        if self.inner.contains_exec(node) { return Err(AllocationSnapshotError::NodeAlreadyAccepted); }
80        if cost == 0 { return Err(AllocationSnapshotError::ZeroCost); }
81        if cost > self.inner.budget_remaining { return Err(AllocationSnapshotError::InsufficientBudget); }
82        let mut carrier = allocation_snapshot_sentinel();
83        core::mem::swap(&mut self.inner, &mut carrier);
84        carrier.accept_node(node, cost);
85        core::mem::swap(&mut self.inner, &mut carrier);
86        Ok(())
87    }
88}
89
90/// A master capacity pool divided into reusable sub-pools.
91pub struct FederatedBudget {
92    inner: FederatedBudgetCarrier,
93}
94
95impl FederatedBudget {
96    #[verifier::type_invariant]
97    closed spec fn well_formed(&self) -> bool { self.inner.inv() }
98
99    /// Construct an empty federation with `num_pools` sub-pools.
100    pub fn new(master_capacity: u64, num_pools: usize) -> (budget: Self) {
101        Self { inner: FederatedBudgetCarrier::new(master_capacity, num_pools) }
102    }
103
104    /// Fixed master capacity.
105    pub fn master_capacity(&self) -> u64 { self.inner.master_capacity }
106
107    /// Master capacity currently delegated to sub-pools.
108    pub fn master_allocated(&self) -> u64 { self.inner.master_allocated }
109
110    /// Number of sub-pools.
111    pub fn len(&self) -> usize { self.inner.sub_capacities.len() }
112
113    /// Whether no sub-pools are configured.
114    pub fn is_empty(&self) -> bool { self.inner.sub_capacities.is_empty() }
115
116    /// Read one sub-pool capacity.
117    #[expect(clippy::indexing_slicing, reason = "the branch proves the pool index is in bounds")]
118    pub fn pool_capacity(&self, pool: usize) -> Option<u64> {
119        if pool < self.inner.sub_capacities.len() { Some(self.inner.sub_capacities[pool]) } else { None }
120    }
121
122    /// Read one sub-pool allocation.
123    #[expect(clippy::indexing_slicing, reason = "the branch proves the pool index is in bounds")]
124    pub fn pool_allocated(&self, pool: usize) -> Option<u64> {
125        if pool < self.inner.sub_allocated.len() { Some(self.inner.sub_allocated[pool]) } else { None }
126    }
127
128    /// Try to delegate master capacity to a sub-pool.
129    #[must_use]
130    pub fn try_delegate(&mut self, pool: usize, amount: u64) -> (accepted: bool) {
131        proof { use_type_invariant(&*self); }
132        let mut carrier = federated_budget_sentinel();
133        core::mem::swap(&mut self.inner, &mut carrier);
134        let accepted = carrier.allocate_sub_pool(pool, amount);
135        core::mem::swap(&mut self.inner, &mut carrier);
136        accepted
137    }
138
139    /// Try to consume capacity within a sub-pool.
140    #[must_use]
141    pub fn try_allocate(&mut self, pool: usize, amount: u64) -> (accepted: bool) {
142        proof { use_type_invariant(&*self); }
143        let mut carrier = federated_budget_sentinel();
144        core::mem::swap(&mut self.inner, &mut carrier);
145        let accepted = carrier.allocate_from_sub_pool(pool, amount);
146        core::mem::swap(&mut self.inner, &mut carrier);
147        accepted
148    }
149
150    /// Try to release capacity consumed within a sub-pool.
151    #[must_use]
152    pub fn try_release(&mut self, pool: usize, amount: u64) -> (accepted: bool) {
153        proof { use_type_invariant(&*self); }
154        let mut carrier = federated_budget_sentinel();
155        core::mem::swap(&mut self.inner, &mut carrier);
156        let accepted = carrier.release_from_sub_pool(pool, amount);
157        core::mem::swap(&mut self.inner, &mut carrier);
158        accepted
159    }
160}
161
162/// Invalid bisection configuration.
163#[derive(Clone, Copy, Debug, Eq, PartialEq)]
164#[non_exhaustive]
165pub enum BisectionBuildError {
166    /// The ordered domain must contain at least two points.
167    DomainTooSmall,
168    /// The threshold must be inside `1..domain_size`.
169    ThresholdOutOfRange,
170}
171
172/// A disabled bisection transition.
173#[derive(Clone, Copy, Debug, Eq, PartialEq)]
174#[non_exhaustive]
175pub enum BisectionError {
176    /// The candidate interval is already converged.
177    AlreadyConverged,
178}
179
180/// A bounded monotone-boundary bisection machine.
181pub struct Bisection {
182    inner: BisectionCarrier,
183}
184
185impl Bisection {
186    #[verifier::type_invariant]
187    closed spec fn well_formed(&self) -> bool { self.inner.invariant() }
188
189    /// Construct a full-domain bisection using the complete `u64` probe budget.
190    pub fn new(domain_size: u64, threshold: u64) -> (result: Result<Self, BisectionBuildError>) {
191        if domain_size < 2 { return Err(BisectionBuildError::DomainTooSmall); }
192        if threshold < 1 || threshold >= domain_size {
193            return Err(BisectionBuildError::ThresholdOutOfRange);
194        }
195        proof { lemma_u64_domain_fits_64(domain_size); }
196        let inner = BisectionCarrier::new(0, domain_size, threshold, domain_size, 64);
197        Ok(Self { inner })
198    }
199
200    /// Current lower bound.
201    pub fn lower(&self) -> u64 { self.inner.lo }
202
203    /// Current upper bound.
204    pub fn upper(&self) -> u64 { self.inner.hi }
205
206    /// Hidden monotone boundary used by this executable carrier.
207    pub fn threshold(&self) -> u64 { self.inner.threshold }
208
209    /// Number of probes taken.
210    pub fn probes_taken(&self) -> u64 { self.inner.probes_taken }
211
212    /// Maximum number of probes.
213    pub fn max_probes(&self) -> u64 { self.inner.max_probes }
214
215    /// Whether the candidate interval has width less than two.
216    pub fn is_converged(&self) -> bool {
217        proof { use_type_invariant(&*self); }
218        self.inner.converged()
219    }
220
221    /// Perform one midpoint probe.
222    pub fn probe(&mut self) -> (result: Result<(), BisectionError>) {
223        proof { use_type_invariant(&*self); }
224        if self.inner.hi - self.inner.lo < 2 { return Err(BisectionError::AlreadyConverged); }
225        let mut carrier = bisection_sentinel();
226        core::mem::swap(&mut self.inner, &mut carrier);
227        carrier.probe();
228        core::mem::swap(&mut self.inner, &mut carrier);
229        Ok(())
230    }
231
232    /// Drive midpoint probes until the interval converges.
233    pub fn converge(&mut self) {
234        proof { use_type_invariant(&*self); }
235        let mut carrier = bisection_sentinel();
236        core::mem::swap(&mut self.inner, &mut carrier);
237        carrier.bisect();
238        core::mem::swap(&mut self.inner, &mut carrier);
239    }
240}
241
242/// An invalid equivalence-class element index.
243#[derive(Clone, Copy, Debug, Eq, PartialEq)]
244#[non_exhaustive]
245pub enum EquivalenceClassError {
246    /// The element is outside the configured universe.
247    ElementOutOfRange,
248}
249
250/// A bounded union-by-rank equivalence-class partition.
251pub struct EquivalenceClass {
252    inner: EquivalenceClassCarrier,
253}
254
255impl EquivalenceClass {
256    #[verifier::type_invariant]
257    closed spec fn well_formed(&self) -> bool { self.inner.inv() }
258
259    /// Construct a singleton partition with a merge-operation ceiling.
260    pub fn new(elements: usize, max_unions: u64) -> (classes: Self) {
261        Self { inner: EquivalenceClassCarrier::new(elements, max_unions) }
262    }
263
264    /// Number of elements in the partition.
265    pub fn len(&self) -> usize { self.inner.n }
266
267    /// Whether the partition contains no elements.
268    pub fn is_empty(&self) -> bool { self.inner.n == 0 }
269
270    /// Successful union operations performed.
271    pub fn unions_performed(&self) -> u64 { self.inner.ops_done }
272
273    /// Configured union-operation ceiling.
274    pub fn max_unions(&self) -> u64 { self.inner.max_ops }
275
276    /// Find an element's representative.
277    pub fn representative(&self, element: usize) -> (result: Result<usize, EquivalenceClassError>) {
278        proof { use_type_invariant(&*self); }
279        if element >= self.inner.n { return Err(EquivalenceClassError::ElementOutOfRange); }
280        Ok(self.inner.find(element))
281    }
282
283    /// Merge two classes, returning false if equal or the operation ceiling is exhausted.
284    pub fn union(&mut self, left: usize, right: usize) -> (result: Result<bool, EquivalenceClassError>) {
285        proof { use_type_invariant(&*self); }
286        if left >= self.inner.n || right >= self.inner.n {
287            return Err(EquivalenceClassError::ElementOutOfRange);
288        }
289        let mut carrier = equivalence_class_sentinel();
290        core::mem::swap(&mut self.inner, &mut carrier);
291        let merged = carrier.union(left, right);
292        core::mem::swap(&mut self.inner, &mut carrier);
293        Ok(merged)
294    }
295
296    /// Test whether two elements have the same representative.
297    pub fn equivalent(&self, left: usize, right: usize) -> (result: Result<bool, EquivalenceClassError>) {
298        proof { use_type_invariant(&*self); }
299        if left >= self.inner.n || right >= self.inner.n {
300            return Err(EquivalenceClassError::ElementOutOfRange);
301        }
302        Ok(self.inner.same(left, right))
303    }
304}
305
306/// Invalid rate-limit configuration.
307#[derive(Clone, Copy, Debug, Eq, PartialEq)]
308#[non_exhaustive]
309pub enum RateLimitBuildError {
310    /// A rate limit must admit at least one operation per window.
311    ZeroLimit,
312}
313
314/// A disabled rate-limit transition.
315#[derive(Clone, Copy, Debug, Eq, PartialEq)]
316#[non_exhaustive]
317pub enum RateLimitError {
318    /// The bounded logical clock has reached its configured maximum.
319    ClockExhausted,
320}
321
322/// A logical-clock, fixed-window rate limit.
323pub struct RateLimit {
324    inner: RateLimitCarrier,
325}
326
327impl RateLimit {
328    #[verifier::type_invariant]
329    closed spec fn well_formed(&self) -> bool {
330        self.inner.type_invariant() && self.inner.window_start_not_future()
331    }
332
333    /// Construct a rate limit at logical clock zero.
334    pub fn new(max_per_window: u64, window_duration: u64, max_clock: u64)
335        -> (result: Result<Self, RateLimitBuildError>) {
336        if max_per_window == 0 { return Err(RateLimitBuildError::ZeroLimit); }
337        Ok(Self { inner: RateLimitCarrier::new(max_per_window, window_duration, max_clock) })
338    }
339
340    /// Per-window admission ceiling.
341    pub fn max_per_window(&self) -> u64 { self.inner.max_per_window }
342
343    /// Window duration in logical-clock units.
344    pub fn window_duration(&self) -> u64 { self.inner.window_duration }
345
346    /// Acquisitions admitted in the current window.
347    pub fn count(&self) -> u64 { self.inner.count }
348
349    /// Current logical clock.
350    pub fn clock(&self) -> u64 { self.inner.clock }
351
352    /// Current window anchor.
353    pub fn window_start(&self) -> u64 { self.inner.window_start }
354
355    /// Try to acquire one unit in the current or newly rolled window.
356    #[must_use]
357    pub fn try_acquire(&mut self) -> (accepted: bool) {
358        proof { use_type_invariant(&*self); }
359        let mut carrier = rate_limit_sentinel();
360        core::mem::swap(&mut self.inner, &mut carrier);
361        let accepted = carrier.try_acquire();
362        core::mem::swap(&mut self.inner, &mut carrier);
363        accepted
364    }
365
366    /// Advance the bounded logical clock by one.
367    pub fn tick(&mut self) -> (result: Result<(), RateLimitError>) {
368        proof { use_type_invariant(&*self); }
369        if self.inner.clock >= self.inner.max_clock { return Err(RateLimitError::ClockExhausted); }
370        let mut carrier = rate_limit_sentinel();
371        core::mem::swap(&mut self.inner, &mut carrier);
372        carrier.tick();
373        core::mem::swap(&mut self.inner, &mut carrier);
374        Ok(())
375    }
376}
377
378/// Invalid reduction input.
379#[derive(Clone, Copy, Debug, Eq, PartialEq)]
380#[non_exhaustive]
381pub enum ReductionBuildError {
382    /// The input exceeds the verified one-billion-item ceiling.
383    TooManyItems,
384    /// An input value exceeds the verified one-billion-unit ceiling.
385    ValueOutOfRange,
386}
387
388/// A disabled incremental reduction transition.
389#[derive(Clone, Copy, Debug, Eq, PartialEq)]
390#[non_exhaustive]
391pub enum ReductionError {
392    /// Every input item has already been consumed.
393    Complete,
394}
395
396/// An incremental additive ordered-prefix reduction.
397pub struct Reduction {
398    inner: ReductionCarrier,
399}
400
401impl Reduction {
402    #[verifier::type_invariant]
403    closed spec fn well_formed(&self) -> bool {
404        self.inner.partition() && self.inner.aggregate() && self.inner.bounded()
405    }
406
407    /// Validate and construct an incremental sum reduction.
408    pub fn new(items: Vec<u64>) -> (result: Result<Self, ReductionBuildError>) {
409        if items.len() > 1_000_000_000 { return Err(ReductionBuildError::TooManyItems); }
410        if !values_within_max(&items, 1_000_000_000) {
411            return Err(ReductionBuildError::ValueOutOfRange);
412        }
413        Ok(Self { inner: ReductionCarrier::new(items) })
414    }
415
416    /// Current additive result.
417    pub fn result(&self) -> u64 { self.inner.result }
418
419    /// Number of consumed items.
420    pub fn processed_len(&self) -> usize { self.inner.processed.len() }
421
422    /// Number of pending items.
423    pub fn remaining_len(&self) -> usize { self.inner.remaining.len() }
424
425    /// Whether the whole input has been consumed.
426    pub fn is_complete(&self) -> bool { self.inner.done() }
427
428    /// Consume the next item in original order.
429    pub fn process_next(&mut self) -> (result: Result<(), ReductionError>) {
430        proof { use_type_invariant(&*self); }
431        if self.inner.remaining.is_empty() { return Err(ReductionError::Complete); }
432        let mut carrier = reduction_sentinel();
433        core::mem::swap(&mut self.inner, &mut carrier);
434        carrier.process();
435        core::mem::swap(&mut self.inner, &mut carrier);
436        Ok(())
437    }
438}
439
440/// A disabled relationship-graph transition.
441#[derive(Clone, Copy, Debug, Eq, PartialEq)]
442#[non_exhaustive]
443pub enum RelationshipGraphError {
444    /// A source or destination node is outside the configured graph.
445    NodeOutOfRange,
446    /// The edge weight exceeds the configured maximum.
447    WeightOutOfRange,
448    /// Self-loops are not admitted.
449    SelfLoop,
450}
451
452/// A weighted directed graph with a consistent adjacency projection.
453pub struct RelationshipGraph {
454    inner: RelationshipGraphCarrier,
455}
456
457impl RelationshipGraph {
458    #[verifier::type_invariant]
459    closed spec fn well_formed(&self) -> bool { self.inner.inv() }
460
461    /// Construct an empty graph.
462    pub fn new(num_nodes: usize, max_weight: u64) -> (graph: Self) {
463        Self { inner: RelationshipGraphCarrier::new(num_nodes, max_weight) }
464    }
465
466    /// Number of nodes.
467    pub fn num_nodes(&self) -> usize { self.inner.num_nodes }
468
469    /// Maximum admitted edge weight.
470    pub fn max_weight(&self) -> u64 { self.inner.max_weight }
471
472    /// Number of concrete weighted edges.
473    pub fn edge_count(&self) -> usize { self.inner.edges.len() }
474
475    /// Read a concrete weighted edge by insertion order.
476    #[expect(clippy::indexing_slicing, reason = "the branch proves the edge index is in bounds")]
477    pub fn edge(&self, index: usize) -> Option<(usize, usize, u64)> {
478        if index < self.inner.edges.len() { Some(self.inner.edges[index]) } else { None }
479    }
480
481    /// Whether any weighted edge exists for a source-destination pair.
482    pub fn contains(&self, source: usize, destination: usize) -> bool {
483        self.inner.contains_pair(source, destination)
484    }
485
486    /// Add one concrete weighted edge if it is not already present.
487    pub fn add_edge(&mut self, source: usize, destination: usize, weight: u64)
488        -> (result: Result<bool, RelationshipGraphError>) {
489        proof { use_type_invariant(&*self); }
490        if source >= self.inner.num_nodes || destination >= self.inner.num_nodes {
491            return Err(RelationshipGraphError::NodeOutOfRange);
492        }
493        if weight > self.inner.max_weight { return Err(RelationshipGraphError::WeightOutOfRange); }
494        if source == destination { return Err(RelationshipGraphError::SelfLoop); }
495        let mut carrier = relationship_graph_sentinel();
496        core::mem::swap(&mut self.inner, &mut carrier);
497        let added = carrier.add_edge(source, destination, weight);
498        core::mem::swap(&mut self.inner, &mut carrier);
499        Ok(added)
500    }
501
502    /// Remove every weighted edge for one source-destination pair.
503    pub fn remove_edges(&mut self, source: usize, destination: usize) {
504        proof { use_type_invariant(&*self); }
505        let mut carrier = relationship_graph_sentinel();
506        core::mem::swap(&mut self.inner, &mut carrier);
507        carrier.remove_edge(source, destination);
508        core::mem::swap(&mut self.inner, &mut carrier);
509    }
510}
511
512/// A disabled sampler transition.
513#[derive(Clone, Copy, Debug, Eq, PartialEq)]
514#[non_exhaustive]
515pub enum SamplerError {
516    /// The item index is outside the distribution.
517    ItemOutOfRange,
518    /// The bounded sample is full.
519    SampleFull,
520    /// The item has zero support weight.
521    OutsideSupport,
522    /// The item has already been selected.
523    AlreadySelected,
524}
525
526/// A bounded without-replacement sampler over caller-supplied proposals.
527pub struct Sampler {
528    inner: SamplerCarrier,
529}
530
531impl Sampler {
532    #[verifier::type_invariant]
533    closed spec fn well_formed(&self) -> bool { self.inner.inv() }
534
535    /// Construct an empty sample over a weight distribution.
536    pub fn new(distribution: Vec<u64>, sample_size: usize) -> (sampler: Self) {
537        Self { inner: SamplerCarrier::new(distribution, sample_size) }
538    }
539
540    /// Number of distribution items.
541    pub fn len(&self) -> usize { self.inner.num_items }
542
543    /// Whether the distribution contains no items.
544    pub fn is_empty(&self) -> bool { self.inner.num_items == 0 }
545
546    /// Maximum selected cardinality.
547    pub fn sample_size(&self) -> usize { self.inner.sample_size }
548
549    /// Number of selected items.
550    pub fn selected_len(&self) -> usize { self.inner.selected.len() }
551
552    /// Read one distribution weight.
553    #[expect(clippy::indexing_slicing, reason = "the branch proves the item index is in bounds")]
554    pub fn weight(&self, item: usize) -> Option<u64> {
555        if item < self.inner.distribution.len() { Some(self.inner.distribution[item]) } else { None }
556    }
557
558    /// Whether an item has already been selected.
559    pub fn contains(&self, item: usize) -> bool { self.inner.contains_exec(item) }
560
561    /// Admit one supported item directly.
562    pub fn sample(&mut self, item: usize) -> (result: Result<(), SamplerError>) {
563        proof { use_type_invariant(&*self); }
564        if item >= self.inner.num_items { return Err(SamplerError::ItemOutOfRange); }
565        if self.inner.selected.len() >= self.inner.sample_size { return Err(SamplerError::SampleFull); }
566        if self.inner.distribution[item] == 0 { return Err(SamplerError::OutsideSupport); }
567        if self.inner.contains_exec(item) { return Err(SamplerError::AlreadySelected); }
568        let mut carrier = sampler_sentinel();
569        core::mem::swap(&mut self.inner, &mut carrier);
570        carrier.sample(item);
571        core::mem::swap(&mut self.inner, &mut carrier);
572        Ok(())
573    }
574
575    /// Remove an unselected item from the live support.
576    #[must_use]
577    pub fn zero(&mut self, item: usize) -> (accepted: bool) {
578        proof { use_type_invariant(&*self); }
579        let mut carrier = sampler_sentinel();
580        core::mem::swap(&mut self.inner, &mut carrier);
581        let accepted = carrier.zero(item);
582        core::mem::swap(&mut self.inner, &mut carrier);
583        accepted
584    }
585
586    /// Apply weighted rejection to an externally proposed item and entropy value.
587    pub fn draw_weighted(&mut self, item: usize, entropy: u64) -> (result: Result<bool, SamplerError>) {
588        proof { use_type_invariant(&*self); }
589        if item >= self.inner.num_items { return Err(SamplerError::ItemOutOfRange); }
590        let mut carrier = sampler_sentinel();
591        core::mem::swap(&mut self.inner, &mut carrier);
592        let accepted = carrier.draw_weighted(item, entropy);
593        core::mem::swap(&mut self.inner, &mut carrier);
594        Ok(accepted)
595    }
596
597    /// Apply uniform-support admission to an externally proposed item.
598    pub fn draw_uniform(&mut self, item: usize) -> (result: Result<bool, SamplerError>) {
599        proof { use_type_invariant(&*self); }
600        if item >= self.inner.num_items { return Err(SamplerError::ItemOutOfRange); }
601        let mut carrier = sampler_sentinel();
602        core::mem::swap(&mut self.inner, &mut carrier);
603        let accepted = carrier.draw_uniform(item);
604        core::mem::swap(&mut self.inner, &mut carrier);
605        Ok(accepted)
606    }
607}
608
609/// Invalid signal configuration.
610#[derive(Clone, Copy, Debug, Eq, PartialEq)]
611#[non_exhaustive]
612pub enum SignalBuildError {
613    /// The initial value is outside the configured value universe.
614    InitialValueOutOfRange,
615}
616
617/// A disabled signal transition.
618#[derive(Clone, Copy, Debug, Eq, PartialEq)]
619#[non_exhaustive]
620pub enum SignalError {
621    /// A value is outside the configured value universe.
622    ValueOutOfRange,
623    /// A listener is outside the configured listener universe.
624    ListenerOutOfRange,
625    /// The listener has no pending notification.
626    ListenerNotPending,
627}
628
629/// A change-detecting signal with per-listener notification provenance.
630pub struct Signal {
631    inner: SignalCarrier,
632}
633
634impl Signal {
635    #[verifier::type_invariant]
636    closed spec fn well_formed(&self) -> bool {
637        self.inner.type_invariant()
638            && self.inner.pending_notified_disjointness()
639            && self.inner.notification_provenance()
640    }
641
642    /// Construct a signal with no pending notification.
643    pub fn new(initial_value: u64, num_values: u64, num_listeners: usize)
644        -> (result: Result<Self, SignalBuildError>) {
645        if initial_value >= num_values { return Err(SignalBuildError::InitialValueOutOfRange); }
646        Ok(Self { inner: SignalCarrier::new(initial_value, num_values, num_listeners) })
647    }
648
649    /// Current retained value.
650    pub fn value(&self) -> u64 { self.inner.current_value }
651
652    /// Number of listeners.
653    pub fn listener_count(&self) -> usize { self.inner.num_listeners }
654
655    /// Whether any actual value change has occurred.
656    pub fn change_observed(&self) -> bool { self.inner.change_observed }
657
658    /// Whether one listener has a pending notification.
659    pub fn is_pending(&self, listener: usize) -> Option<bool> {
660        proof { use_type_invariant(&*self); }
661        if listener < self.inner.num_listeners { Some(self.inner.is_pending(listener)) } else { None }
662    }
663
664    /// Whether one listener has received the latest notification.
665    pub fn is_notified(&self, listener: usize) -> Option<bool> {
666        proof { use_type_invariant(&*self); }
667        if listener < self.inner.num_listeners { Some(self.inner.is_notified(listener)) } else { None }
668    }
669
670    /// Set a value, returning false for an unchanged value.
671    pub fn set_value(&mut self, value: u64) -> (result: Result<bool, SignalError>) {
672        proof { use_type_invariant(&*self); }
673        if value >= self.inner.num_values { return Err(SignalError::ValueOutOfRange); }
674        let mut carrier = signal_sentinel();
675        core::mem::swap(&mut self.inner, &mut carrier);
676        let changed = carrier.set_value(value);
677        core::mem::swap(&mut self.inner, &mut carrier);
678        Ok(changed)
679    }
680
681    /// Move one listener's pending notification into delivered state.
682    pub fn notify(&mut self, listener: usize) -> (result: Result<(), SignalError>) {
683        proof { use_type_invariant(&*self); }
684        if listener >= self.inner.num_listeners { return Err(SignalError::ListenerOutOfRange); }
685        if !self.inner.is_pending(listener) { return Err(SignalError::ListenerNotPending); }
686        let mut carrier = signal_sentinel();
687        core::mem::swap(&mut self.inner, &mut carrier);
688        carrier.notify_listener(listener);
689        core::mem::swap(&mut self.inner, &mut carrier);
690        Ok(())
691    }
692}
693
694/// Invalid traversal-engine configuration.
695#[derive(Clone, Copy, Debug, Eq, PartialEq)]
696#[non_exhaustive]
697pub enum TraversalBuildError {
698    /// At least one node is required.
699    NoNodes,
700    /// The root is outside the node universe.
701    RootOutOfRange,
702}
703
704/// A disabled traversal-engine transition.
705#[derive(Clone, Copy, Debug, Eq, PartialEq)]
706#[non_exhaustive]
707pub enum TraversalError {
708    /// The node is outside the configured universe.
709    NodeOutOfRange,
710    /// The node is not queued.
711    NodeNotQueued,
712    /// The node has already been visited.
713    NodeAlreadyVisited,
714    /// Termination is enabled only when the queue is empty.
715    QueueNotEmpty,
716}
717
718/// A budgeted star-graph traversal with accepted-subset tracking.
719pub struct TraversalEngine {
720    inner: TraversalEngineCarrier,
721}
722
723impl TraversalEngine {
724    #[verifier::type_invariant]
725    closed spec fn well_formed(&self) -> bool {
726        self.inner.type_invariant()
727            && self.inner.budget_invariant()
728            && self.inner.accepted_subset_visited()
729            && self.inner.root < self.inner.num_nodes
730    }
731
732    /// Construct a traversal rooted in the configured node universe.
733    pub fn new(num_nodes: usize, root: usize, budget: u64)
734        -> (result: Result<Self, TraversalBuildError>) {
735        if num_nodes == 0 { return Err(TraversalBuildError::NoNodes); }
736        if root >= num_nodes { return Err(TraversalBuildError::RootOutOfRange); }
737        Ok(Self { inner: TraversalEngineCarrier::new(num_nodes, root, budget) })
738    }
739
740    /// Number of nodes.
741    pub fn num_nodes(&self) -> usize { self.inner.num_nodes }
742
743    /// Traversal root.
744    pub fn root(&self) -> usize { self.inner.root }
745
746    /// Remaining traversal budget.
747    pub fn budget_remaining(&self) -> u64 { self.inner.budget_remaining }
748
749    /// Number of queued nodes.
750    pub fn queued_len(&self) -> usize { self.inner.queue.len() }
751
752    /// Number of visited nodes.
753    pub fn visited_len(&self) -> usize { self.inner.visited.len() }
754
755    /// Number of budget-accepted nodes.
756    pub fn accepted_len(&self) -> usize { self.inner.accepted.len() }
757
758    /// Whether a node is queued.
759    pub fn is_queued(&self, node: usize) -> bool { self.inner.queue_contains(node) }
760
761    /// Whether a node was visited.
762    pub fn is_visited(&self, node: usize) -> bool { self.inner.visited_contains(node) }
763
764    /// Whether a node was accepted under the budget.
765    pub fn is_accepted(&self, node: usize) -> bool { self.inner.accepted_contains(node) }
766
767    /// Visit one queued, unvisited node.
768    pub fn visit(&mut self, node: usize) -> (result: Result<(), TraversalError>) {
769        proof { use_type_invariant(&*self); }
770        if node >= self.inner.num_nodes { return Err(TraversalError::NodeOutOfRange); }
771        if !self.inner.queue_contains(node) { return Err(TraversalError::NodeNotQueued); }
772        if self.inner.visited_contains(node) { return Err(TraversalError::NodeAlreadyVisited); }
773        let mut carrier = traversal_engine_sentinel();
774        core::mem::swap(&mut self.inner, &mut carrier);
775        carrier.visit_node(node);
776        core::mem::swap(&mut self.inner, &mut carrier);
777        Ok(())
778    }
779
780    /// Remove one queued node without visiting it.
781    pub fn skip(&mut self, node: usize) -> (result: Result<(), TraversalError>) {
782        proof { use_type_invariant(&*self); }
783        if node >= self.inner.num_nodes { return Err(TraversalError::NodeOutOfRange); }
784        if !self.inner.queue_contains(node) { return Err(TraversalError::NodeNotQueued); }
785        let mut carrier = traversal_engine_sentinel();
786        core::mem::swap(&mut self.inner, &mut carrier);
787        carrier.skip(node);
788        core::mem::swap(&mut self.inner, &mut carrier);
789        Ok(())
790    }
791
792    /// Confirm the terminal stutter when no queued work remains.
793    pub fn terminate(&mut self) -> (result: Result<(), TraversalError>) {
794        proof { use_type_invariant(&*self); }
795        if !self.inner.queue.is_empty() { return Err(TraversalError::QueueNotEmpty); }
796        let mut carrier = traversal_engine_sentinel();
797        core::mem::swap(&mut self.inner, &mut carrier);
798        carrier.terminate();
799        core::mem::swap(&mut self.inner, &mut carrier);
800        Ok(())
801    }
802}
803
804proof fn lemma_u64_domain_fits_64(value: u64)
805    ensures value as int <= crate::compositions::bisection::pow2(64),
806{
807    assert(crate::compositions::bisection::pow2(64) == 18_446_744_073_709_551_616int) by (compute);
808}
809
810fn allocation_snapshot_sentinel() -> (carrier: AllocationSnapshotCarrier)
811    ensures carrier.type_invariant(), carrier.budget_consistency(),
812{ AllocationSnapshotCarrier::new(0, 0) }
813
814fn federated_budget_sentinel() -> (carrier: FederatedBudgetCarrier)
815    ensures carrier.inv(),
816{ FederatedBudgetCarrier::new(0, 0) }
817
818fn bisection_sentinel() -> (carrier: BisectionCarrier)
819    ensures carrier.invariant(),
820{
821    proof {
822        assert(crate::compositions::bisection::pow2(1) == 2) by (compute);
823    }
824    BisectionCarrier::new(0, 2, 1, 2, 1)
825}
826
827fn equivalence_class_sentinel() -> (carrier: EquivalenceClassCarrier)
828    ensures carrier.inv(),
829{ EquivalenceClassCarrier::new(0, 0) }
830
831fn rate_limit_sentinel() -> (carrier: RateLimitCarrier)
832    ensures carrier.type_invariant(), carrier.window_start_not_future(),
833{ RateLimitCarrier::new(1, 1, 0) }
834
835fn reduction_sentinel() -> (carrier: ReductionCarrier)
836    ensures carrier.partition(), carrier.aggregate(), carrier.bounded(),
837{
838    let values: Vec<u64> = Vec::new();
839    ReductionCarrier::new(values)
840}
841
842fn relationship_graph_sentinel() -> (carrier: RelationshipGraphCarrier)
843    ensures carrier.inv(),
844{ RelationshipGraphCarrier::new(0, 0) }
845
846fn sampler_sentinel() -> (carrier: SamplerCarrier)
847    ensures carrier.inv(),
848{
849    let distribution: Vec<u64> = Vec::new();
850    SamplerCarrier::new(distribution, 0)
851}
852
853fn signal_sentinel() -> (carrier: SignalCarrier)
854    ensures
855        carrier.type_invariant(),
856        carrier.pending_notified_disjointness(),
857        carrier.notification_provenance(),
858{ SignalCarrier::new(0, 1, 0) }
859
860fn traversal_engine_sentinel() -> (carrier: TraversalEngineCarrier)
861    ensures
862        carrier.type_invariant(),
863        carrier.budget_invariant(),
864        carrier.accepted_subset_visited(),
865        carrier.root < carrier.num_nodes,
866{ TraversalEngineCarrier::new(1, 0, 0) }
867
868}
869
870macro_rules! impl_error {
871    ($type:ty, { $($variant:path => $message:literal),+ $(,)? }) => {
872        impl core::fmt::Display for $type {
873            fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
874                formatter.write_str(match self { $($variant => $message),+ })
875            }
876        }
877        impl std::error::Error for $type {}
878    };
879}
880
881macro_rules! impl_observational_debug {
882    ($type:ty, $name:literal, $($field:literal => $method:ident),+ $(,)?) => {
883        impl core::fmt::Debug for $type {
884            fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
885                let mut state = formatter.debug_struct($name);
886                $(state.field($field, &self.$method());)+
887                state.finish()
888            }
889        }
890    };
891}
892
893impl_observational_debug!(AllocationSnapshot, "AllocationSnapshot",
894    "capacity" => capacity,
895    "num_nodes" => num_nodes,
896    "total_cost" => total_cost,
897    "budget_remaining" => budget_remaining,
898    "len" => len,
899);
900impl_observational_debug!(FederatedBudget, "FederatedBudget",
901    "master_capacity" => master_capacity,
902    "master_allocated" => master_allocated,
903    "len" => len,
904);
905impl_observational_debug!(Bisection, "Bisection",
906    "lower" => lower,
907    "upper" => upper,
908    "threshold" => threshold,
909    "probes_taken" => probes_taken,
910    "max_probes" => max_probes,
911    "converged" => is_converged,
912);
913impl_observational_debug!(EquivalenceClass, "EquivalenceClass",
914    "len" => len,
915    "unions_performed" => unions_performed,
916    "max_unions" => max_unions,
917);
918impl_observational_debug!(RateLimit, "RateLimit",
919    "max_per_window" => max_per_window,
920    "window_duration" => window_duration,
921    "count" => count,
922    "clock" => clock,
923    "window_start" => window_start,
924);
925impl_observational_debug!(Reduction, "Reduction",
926    "result" => result,
927    "processed_len" => processed_len,
928    "remaining_len" => remaining_len,
929    "complete" => is_complete,
930);
931impl_observational_debug!(RelationshipGraph, "RelationshipGraph",
932    "num_nodes" => num_nodes,
933    "max_weight" => max_weight,
934    "edge_count" => edge_count,
935);
936impl_observational_debug!(Sampler, "Sampler",
937    "len" => len,
938    "sample_size" => sample_size,
939    "selected_len" => selected_len,
940);
941impl_observational_debug!(Signal, "Signal",
942    "value" => value,
943    "listener_count" => listener_count,
944    "change_observed" => change_observed,
945);
946impl_observational_debug!(TraversalEngine, "TraversalEngine",
947    "num_nodes" => num_nodes,
948    "root" => root,
949    "budget_remaining" => budget_remaining,
950    "queued_len" => queued_len,
951    "visited_len" => visited_len,
952    "accepted_len" => accepted_len,
953);
954
955impl_error!(AllocationSnapshotError, {
956    Self::NodeOutOfRange => "node is outside the snapshot universe",
957    Self::NodeAlreadyAccepted => "node is already accepted",
958    Self::ZeroCost => "accepted node cost must be positive",
959    Self::InsufficientBudget => "node cost exceeds the remaining budget",
960});
961impl_error!(BisectionBuildError, {
962    Self::DomainTooSmall => "bisection domain must contain at least two points",
963    Self::ThresholdOutOfRange => "bisection threshold is outside the domain",
964});
965impl_error!(BisectionError, { Self::AlreadyConverged => "bisection is already converged" });
966impl_error!(EquivalenceClassError, { Self::ElementOutOfRange => "element is outside the partition" });
967impl_error!(RateLimitBuildError, { Self::ZeroLimit => "rate limit must admit at least one operation" });
968impl_error!(RateLimitError, { Self::ClockExhausted => "rate-limit logical clock is exhausted" });
969impl_error!(ReductionBuildError, {
970    Self::TooManyItems => "reduction input exceeds the verified item ceiling",
971    Self::ValueOutOfRange => "reduction input exceeds the verified value ceiling",
972});
973impl_error!(ReductionError, { Self::Complete => "reduction is already complete" });
974impl_error!(RelationshipGraphError, {
975    Self::NodeOutOfRange => "graph node is outside the configured universe",
976    Self::WeightOutOfRange => "edge weight exceeds the configured maximum",
977    Self::SelfLoop => "relationship graph does not admit self-loops",
978});
979impl_error!(SamplerError, {
980    Self::ItemOutOfRange => "sample item is outside the distribution",
981    Self::SampleFull => "bounded sample is full",
982    Self::OutsideSupport => "sample item has zero support weight",
983    Self::AlreadySelected => "sample item is already selected",
984});
985impl_error!(SignalBuildError, { Self::InitialValueOutOfRange => "initial signal value is outside its universe" });
986impl_error!(SignalError, {
987    Self::ValueOutOfRange => "signal value is outside its universe",
988    Self::ListenerOutOfRange => "listener is outside the signal universe",
989    Self::ListenerNotPending => "listener has no pending notification",
990});
991impl_error!(TraversalBuildError, {
992    Self::NoNodes => "traversal requires at least one node",
993    Self::RootOutOfRange => "traversal root is outside the node universe",
994});
995impl_error!(TraversalError, {
996    Self::NodeOutOfRange => "traversal node is outside the configured universe",
997    Self::NodeNotQueued => "traversal node is not queued",
998    Self::NodeAlreadyVisited => "traversal node was already visited",
999    Self::QueueNotEmpty => "traversal queue is not empty",
1000});