1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
//! Protocol decision kernels — the load-bearing guards of the snapshot
//! export/import protocol, extracted as pure functions so the PRODUCTION
//! code and the exhaustive model checker (`tests/model.rs`) execute the
//! **same logic**, not two hand-synchronized copies of it.
//!
//! Every function here is a guard whose correctness the machine-checked
//! theorems depend on. The call sites:
//!
//! | kernel | production | model |
//! |----------------------------|-------------------------------------|--------------------|
//! | [`pointer_publish_allowed`]| `transport::swap_pointer` | `Act::Publish` |
//! | [`payload_prunable`] | `transport::ObjectStoreTransport::prune` | `Act::Prune` |
//! | [`resume_window_ok`] | `nats` resume paths (`check_resume_window`) | `Act::Resume` |
//!
//! Because the model transitions call these very functions, a change to any
//! guard is re-verified against the full bounded state space on the next
//! `cargo test --test model` — the guards cannot drift from the proof. The
//! mutation tests in `tests/model.rs` additionally prove each guard is
//! load-bearing: substituting a broken variant makes the checker produce a
//! counterexample.
//!
//! Kernels operate on plain `u64` ranks (a [`WatchCursor`](crate::WatchCursor)'s
//! revision, with revisionless cursors ranked 0 by the callers) so they stay
//! free of I/O types and usable from the checker's `u8`-bounded state space.
/// What the publisher observed at the pointer key before deciding.
/// THE monotonic pointer guard: may `candidate_rank` be published over the
/// observed `current` pointer?
///
/// `true` for an open slot, a corrupt pointer, or a candidate at or above
/// the existing cursor; `false` exactly when the existing pointer is
/// parseable and STRICTLY newer — the refusal that makes a slow exporter's
/// stale publish a no-op instead of a regression.
///
/// Soundness of deciding on a read (before the conditional put): every
/// writer uses this guard with a compare-and-swap, so the pointer's rank is
/// monotone non-decreasing — once "strictly newer" is observed, it can never
/// become false, so refusal needs no CAS. Machine-checked as `published
/// cursor never regresses` in `tests/model.rs`.
/// THE prune guard: may this payload object be deleted, given the current
/// pointer's rank?
///
/// A payload is prunable only when ALL hold:
/// - it is not the pointer's own target;
/// - its rank is parseable AND **strictly below** the pointer's (an
/// unparseable rank is never deleted — unknown objects are not ours);
/// - its age has cleared the grace period (`aged_out`; the model passes
/// `true`, checking the harshest zero-grace timing).
///
/// Strictly-below is what makes a dangling pointer impossible regardless of
/// timing: [`pointer_publish_allowed`] refuses any candidate below the
/// pointer, and the pointer is monotone — so anything this guard deletes
/// (rank < pointer-at-prune ≤ pointer-at-any-later-swap) can never be
/// successfully published afterward. The model checker FOUND the dangling
/// counterexample under the earlier age-only rule (a same-cursor payload
/// collected mid-publish, then published by the `>=` swap guard); this rule
/// is the structural fix, machine-checked as `pointer target always
/// fetchable` under zero-grace pruning.
/// THE cursor-expiry guard: is resuming from `revision` sound, given the
/// stream's first retained sequence?
///
/// A resume reads `revision + 1` onward; it is sound iff nothing at or below
/// `revision + 1`'s predecessor gap has been head-evicted — i.e.
/// `first_sequence <= revision + 1`. Interior (per-subject) eviction inside
/// the gap is safe for a last-write-wins fold (an overwrite-evicted revision
/// implies a later revision of the same subject exists and will be
/// delivered); lost DELETES come from head eviction, which is exactly what
/// advances `first_sequence`.
///
/// This check must be performed by US: NATS does not error on a below-head
/// start sequence — it silently clamps to the first retained message
/// (pinned live by `tests/resync.rs::nats_silently_clamps_resume_below_first_seq`),
/// which would skip the gap's evicted delete markers with no fallback and no
/// resync. Machine-checked as `bootstrap never silently diverges` in
/// `tests/model.rs` (where the model's retention floor is
/// `first_sequence - 1`).