Skip to main content

automation_structures/modalities/
sequential.rs

1//! Fixed-sequence execution carrier.
2
3use vstd::prelude::*;
4
5verus! {
6
7/// Transition for beginning one sequential position.
8pub open spec fn begin_step_action(
9    before_position: nat,
10    after_position: nat,
11    steps: nat,
12    selected: bool,
13    accepted: bool,
14) -> bool {
15    let enabled = selected && before_position < steps;
16    &&& accepted == enabled
17    &&& after_position == before_position
18}
19
20/// Transition for completing one sequential position.
21pub open spec fn complete_step_action(
22    before_position: nat,
23    after_position: nat,
24    steps: nat,
25    selected: bool,
26    value_admitted: bool,
27    accepted: bool,
28) -> bool {
29    let enabled = selected
30        && value_admitted
31        && before_position < steps;
32    &&& accepted == enabled
33    &&& after_position == if accepted {
34        before_position + 1
35    } else {
36        before_position
37    }
38}
39
40/// Fixed-sequence execution owner.
41pub struct Sequential {
42    /// Total number of steps.
43    pub steps: usize,
44    /// Exclusive upper bound of carried values.
45    pub value_domain_size: u64,
46    /// Program counter identifying the next step.
47    pub pc: usize,
48    /// Current carried value.
49    pub value: u64,
50    /// Whether the current step is active.
51    pub active: bool,
52    /// Values committed by completed steps.
53    pub history: Vec<u64>,
54}
55
56impl Sequential {
57    /// Whether every retained value lies within the configured value domain.
58    pub open spec fn values_valid(&self) -> bool {
59        forall|i: int| 0 <= i < self.history@.len()
60            ==> #[trigger] self.history@[i] < self.value_domain_size
61    }
62
63    /// Whether cursor, activity, history, and values have consistent shape.
64    pub open spec fn type_invariant(&self) -> bool {
65        &&& self.steps > 0
66        &&& self.value_domain_size > 0
67        &&& self.pc <= self.steps
68        &&& self.value < self.value_domain_size
69        &&& self.values_valid()
70    }
71
72    /// Whether committed-history length agrees with the next execution position.
73    pub open spec fn history_position_agreement(&self) -> bool {
74        self.history@.len() == self.pc
75    }
76
77    /// Compatibility alias for [`Self::history_position_agreement`].
78    ///
79    /// This predicate does not characterize a general total-order relation.
80    pub open spec fn total_order(&self) -> bool {
81        self.history_position_agreement()
82    }
83
84    /// Whether an active step always precedes terminal completion.
85    pub open spec fn active_before_done(&self) -> bool {
86        self.active ==> self.pc < self.steps
87    }
88
89    /// Whether all fixed-sequence execution contract clauses hold.
90    pub open spec fn inv(&self) -> bool {
91        self.type_invariant() && self.history_position_agreement() && self.active_before_done()
92    }
93
94    /// Construct an inactive execution at the first step.
95    pub fn new(steps: usize, value_domain_size: u64, initial_value: u64) -> (s: Sequential)
96        requires
97            steps > 0,
98            value_domain_size > 0,
99            initial_value < value_domain_size,
100        ensures
101            s.steps == steps,
102            s.value_domain_size == value_domain_size,
103            s.pc == 0,
104            s.value == initial_value,
105            !s.active,
106            s.history@.len() == 0,
107            s.inv(),
108    {
109        Sequential {
110            steps,
111            value_domain_size,
112            pc: 0,
113            value: initial_value,
114            active: false,
115            history: Vec::new(),
116        }
117    }
118
119    /// `BeginStep`, with disabled calls exposed as a stuttering rejection.
120    pub fn begin_step(&mut self) -> (accepted: bool)
121        requires old(self).inv(),
122        ensures
123            accepted == (old(self).pc < old(self).steps && !old(self).active),
124            begin_step_action(
125                old(self).pc as nat,
126                final(self).pc as nat,
127                old(self).steps as nat,
128                !old(self).active,
129                accepted,
130            ),
131            crate::connectives::marker::set_if(
132                old(self).active,
133                final(self).active,
134                accepted,
135            ),
136            final(self).steps == old(self).steps,
137            final(self).value_domain_size == old(self).value_domain_size,
138            if accepted {
139                final(self).active
140                    && final(self).pc == old(self).pc
141                    && final(self).value == old(self).value
142                    && final(self).history@ == old(self).history@
143            } else {
144                final(self).pc == old(self).pc
145                    && final(self).value == old(self).value
146                    && final(self).active == old(self).active
147                    && final(self).history@ == old(self).history@
148            },
149            final(self).inv(),
150    {
151        if self.pc < self.steps && !self.active {
152            self.active = true;
153            true
154        } else {
155            false
156        }
157    }
158
159    /// `CompleteStep`, including its nondeterministic ValueDomain choice as an
160    /// explicit caller value. An out-of-domain choice is not an enabled TLA+
161    /// action and therefore stutters with `false`.
162    #[expect(clippy::arithmetic_side_effects, reason = "Verus proves pc remains within the step bound")]
163    pub fn complete_step(&mut self, next_value: u64) -> (accepted: bool)
164        requires old(self).inv(),
165        ensures
166            accepted == (old(self).active && next_value < old(self).value_domain_size),
167            complete_step_action(
168                old(self).pc as nat,
169                final(self).pc as nat,
170                old(self).steps as nat,
171                old(self).active,
172                next_value < old(self).value_domain_size,
173                accepted,
174            ),
175            crate::connectives::marker::clear_if(
176                old(self).active,
177                final(self).active,
178                accepted,
179            ),
180            final(self).steps == old(self).steps,
181            final(self).value_domain_size == old(self).value_domain_size,
182            if accepted {
183                !final(self).active
184                    && final(self).pc == old(self).pc + 1
185                    && final(self).value == next_value
186                    && final(self).history@ == old(self).history@.push(next_value)
187            } else {
188                final(self).pc == old(self).pc
189                    && final(self).value == old(self).value
190                    && final(self).active == old(self).active
191                    && final(self).history@ == old(self).history@
192            },
193            final(self).inv(),
194    {
195        if self.active && next_value < self.value_domain_size {
196            let ghost old_history = self.history@;
197            self.value = next_value;
198            self.history.push(next_value);
199            self.pc += 1;
200            self.active = false;
201            assert(self.values_valid()) by {
202                assert forall|i: int| 0 <= i < self.history@.len()
203                    implies #[trigger] self.history@[i] < self.value_domain_size by {
204                    if i < old_history.len() {
205                        assert(self.history@[i] == old_history[i]);
206                    } else {
207                        assert(i == old_history.len());
208                        assert(self.history@[i] == next_value);
209                    }
210                }
211            }
212            true
213        } else {
214            false
215        }
216    }
217
218    /// `DoneStuttering`: enabled exactly at the terminal inactive state and
219    /// never changes carrier state.
220    pub fn done_stuttering(&mut self) -> (enabled: bool)
221        requires old(self).inv(),
222        ensures
223            enabled == (old(self).pc == old(self).steps && !old(self).active),
224            final(self).steps == old(self).steps,
225            final(self).value_domain_size == old(self).value_domain_size,
226            final(self).pc == old(self).pc,
227            final(self).value == old(self).value,
228            final(self).active == old(self).active,
229            final(self).history@ == old(self).history@,
230            final(self).inv(),
231    {
232        self.pc == self.steps && !self.active
233    }
234}
235
236}