Skip to main content

tsoracle_core/
allocator.rs

1//
2//  ░▀█▀░█▀▀░█▀█░█▀▄░█▀█░█▀▀░█░░░█▀▀
3//  ░░█░░▀▀█░█░█░█▀▄░█▀█░█░░░█░░░█▀▀
4//  ░░▀░░▀▀▀░▀▀▀░▀░▀░▀░▀░▀▀▀░▀▀▀░▀▀▀
5//
6//  tsoracle — Distributed Timestamp Oracle
7//  https://www.tsoracle.rs
8//
9//  Copyright (c) 2026 Prisma Risk
10//
11//  Licensed under the Apache License, Version 2.0 (the "License");
12//  you may not use this file except in compliance with the License.
13//  You may obtain a copy of the License at
14//
15//      https://www.apache.org/licenses/LICENSE-2.0
16//
17//  Unless required by applicable law or agreed to in writing, software
18//  distributed under the License is distributed on an "AS IS" BASIS,
19//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20//  See the License for the specific language governing permissions and
21//  limitations under the License.
22//
23
24// #[PerformanceCriticalPath]
25//! The window allocator state machine. Sync, no I/O.
26
27use crate::{Epoch, LOGICAL_MAX, PHYSICAL_MS_MAX, Timestamp};
28
29/// A `u64` physical-millisecond value proven `<= PHYSICAL_MS_MAX` at
30/// construction.
31///
32/// The 46-bit physical field of [`Timestamp`] cannot represent any value above
33/// [`PHYSICAL_MS_MAX`]; every allocator entry point used to re-check that
34/// bound on bare `u64` parameters (`fence_floor`, `committed_ceiling`,
35/// `now_ms`, `persisted_high_water`) — three different methods, each carrying
36/// its own `if value > PHYSICAL_MS_MAX { ... }` line. `PhysicalMs` collapses
37/// those per-method runtime checks into one construction-site check, so:
38///
39/// * a method signature taking `PhysicalMs` is compile-time proof that the
40///   46-bit bound has already been validated for that argument; and
41/// * an accidental swap of `now_ms` and `committed_ceiling` at a call site no
42///   longer type-checks against bare `u64` clocks, durations, or counters.
43///
44/// Constructed via [`try_new`](Self::try_new) (or the equivalent
45/// [`TryFrom<u64>`] impl). The inner value can be recovered with
46/// [`get`](Self::get) for arithmetic; the result must be re-wrapped through
47/// `try_new` before crossing back into a `PhysicalMs`-typed boundary.
48#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
49pub struct PhysicalMs(u64);
50
51impl PhysicalMs {
52    /// The largest in-range value, `2^46 - 1` (equal to [`PHYSICAL_MS_MAX`]).
53    pub const MAX: PhysicalMs = PhysicalMs(PHYSICAL_MS_MAX);
54    /// The zero value. Available as `const`, matching `Duration::ZERO` style.
55    pub const ZERO: PhysicalMs = PhysicalMs(0);
56
57    /// Validate `value <= PHYSICAL_MS_MAX` and wrap. Returns
58    /// [`CoreError::PhysicalMsOutOfRange`] otherwise.
59    ///
60    /// Declared `const fn` so [`MAX`](Self::MAX) and any other compile-time
61    /// `PhysicalMs` constant can be built without unsafe direct-field
62    /// construction outside this module.
63    pub const fn try_new(value: u64) -> Result<Self, CoreError> {
64        if value > PHYSICAL_MS_MAX {
65            return Err(CoreError::PhysicalMsOutOfRange(value));
66        }
67        Ok(PhysicalMs(value))
68    }
69
70    /// Recover the underlying `u64`. `Copy`, so the receiver remains usable.
71    pub const fn get(self) -> u64 {
72        self.0
73    }
74}
75
76impl TryFrom<u64> for PhysicalMs {
77    type Error = CoreError;
78    fn try_from(value: u64) -> Result<Self, Self::Error> {
79        Self::try_new(value)
80    }
81}
82
83impl core::fmt::Display for PhysicalMs {
84    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
85        core::fmt::Display::fmt(&self.0, f)
86    }
87}
88
89/// A contiguous block of `count` timestamps starting at
90/// `(physical_ms, logical_start)`, all sharing one leadership `epoch`.
91///
92/// Fields are private and the only public constructor is
93/// [`try_new`](Self::try_new), which validates that every timestamp the grant
94/// covers fits the packed 46-bit physical / 18-bit logical layout. A value of
95/// this type is therefore proof that [`first`](Self::first) and
96/// [`last`](Self::last) can pack without panicking — the in-range invariant is
97/// guaranteed by the type, not by the constructors that happen to build it.
98/// The crate-internal back door [`new_unchecked`](Self::new_unchecked) skips
99/// the validation but documents the same invariant as a caller obligation.
100#[derive(Copy, Clone, Debug, PartialEq, Eq)]
101pub struct WindowGrant {
102    physical_ms: u64,
103    logical_start: u32,
104    count: u32,
105    epoch: Epoch,
106}
107
108impl WindowGrant {
109    /// Construct a grant, checking that every timestamp it covers packs
110    /// cleanly. This is the only *validating* constructor; the
111    /// crate-internal [`new_unchecked`](Self::new_unchecked) skips the
112    /// checks but requires the caller to have established the invariant
113    /// some other way. Either way, a constructed value witnesses that
114    /// `first`/`last` are infallible.
115    ///
116    /// Rejects `count == 0` (a grant covers at least one timestamp, and
117    /// `last`'s `logical_start + count - 1` would underflow). The range check
118    /// defers to [`Timestamp::try_pack`] on the *last* logical the grant emits:
119    /// it is the single source of truth for the bit layout, and since
120    /// `logical_start <= last_logical <= LOGICAL_MAX` validating the last
121    /// boundary validates the first by implication.
122    pub fn try_new(
123        physical_ms: u64,
124        logical_start: u32,
125        count: u32,
126        epoch: Epoch,
127    ) -> Result<Self, CoreError> {
128        if count == 0 {
129            return Err(CoreError::InvalidCount(0));
130        }
131        let last_logical =
132            logical_start
133                .checked_add(count - 1)
134                .ok_or(CoreError::LogicalRangeOutOfRange {
135                    logical_start,
136                    count,
137                })?;
138        Timestamp::try_pack(physical_ms, last_logical).map_err(|err| match err {
139            crate::TimestampError::PhysicalMsOutOfRange { physical_ms, .. } => {
140                CoreError::PhysicalMsOutOfRange(physical_ms)
141            }
142            crate::TimestampError::LogicalOutOfRange { .. } => CoreError::LogicalRangeOutOfRange {
143                logical_start,
144                count,
145            },
146        })?;
147        Ok(WindowGrant {
148            physical_ms,
149            logical_start,
150            count,
151            epoch,
152        })
153    }
154
155    /// Construct a grant without re-checking the packing invariant. The caller
156    /// must guarantee `count >= 1`, `physical_ms <= PHYSICAL_MS_MAX`, and
157    /// `logical_start + count - 1 <= LOGICAL_MAX` — i.e. every timestamp the
158    /// grant covers packs cleanly. Intended for `Allocator::try_grant`, whose
159    /// `project_grant` helper has already established these bounds; routing
160    /// that path through `try_new` would repeat four checked operations on the
161    /// hot path. The `debug_assert!`s catch a future drift between
162    /// `project_grant` and this constructor in test builds without paying any
163    /// cost in release.
164    pub(crate) fn new_unchecked(
165        physical_ms: u64,
166        logical_start: u32,
167        count: u32,
168        epoch: Epoch,
169    ) -> Self {
170        debug_assert!(count != 0);
171        debug_assert!(physical_ms <= PHYSICAL_MS_MAX);
172        debug_assert!((logical_start as u64) + (count as u64) <= (LOGICAL_MAX as u64) + 1);
173        WindowGrant {
174            physical_ms,
175            logical_start,
176            count,
177            epoch,
178        }
179    }
180
181    pub fn physical_ms(&self) -> u64 {
182        self.physical_ms
183    }
184    pub fn logical_start(&self) -> u32 {
185        self.logical_start
186    }
187    pub fn count(&self) -> u32 {
188        self.count
189    }
190    pub fn epoch(&self) -> Epoch {
191        self.epoch
192    }
193
194    /// The first timestamp in the grant. Infallible: [`try_new`](Self::try_new)
195    /// validated `(physical_ms, logical_start)` is in range, so `pack` cannot
196    /// trip its `assert!`.
197    pub fn first(&self) -> Timestamp {
198        Timestamp::pack(self.physical_ms, self.logical_start)
199    }
200    /// The last timestamp in the grant. Infallible: [`try_new`](Self::try_new)
201    /// validated `(physical_ms, logical_start + count - 1)` is in range (and
202    /// `count >= 1`, so the subtraction cannot underflow), so `pack` cannot
203    /// trip its `assert!`.
204    pub fn last(&self) -> Timestamp {
205        Timestamp::pack(self.physical_ms, self.logical_start + self.count - 1)
206    }
207}
208
209#[derive(Debug, thiserror::Error, PartialEq, Eq)]
210pub enum CoreError {
211    #[error("not leader")]
212    NotLeader,
213    #[error("window exhausted; caller must extend before retrying")]
214    WindowExhausted,
215    #[error("invalid count: {0}")]
216    InvalidCount(u32),
217    #[error("physical_ms {0} exceeds 46-bit maximum")]
218    PhysicalMsOutOfRange(u64),
219    #[error("logical range [{logical_start}, +{count}) exceeds the 18-bit logical field")]
220    LogicalRangeOutOfRange { logical_start: u32, count: u32 },
221    #[error(
222        "invalid leadership window: fence_floor {fence_floor} exceeds committed_ceiling {committed_ceiling}"
223    )]
224    InvalidLeadershipWindow {
225        fence_floor: u64,
226        committed_ceiling: u64,
227    },
228    #[error(
229        "window extension overflow: max(floor {floor}, now_ms {now_ms}) + ahead_ms {ahead_ms} exceeds u64::MAX"
230    )]
231    WindowExtensionOverflow {
232        floor: u64,
233        now_ms: u64,
234        ahead_ms: u64,
235    },
236}
237
238/// The result of a `try_commit_window_extension` that passed range validation.
239///
240/// A commit either raises the durable bound or is dropped for one of three
241/// benign, expected reasons. Collapsing both into `Ok(())` left a caller that
242/// just paid for a `persist_high_water` round-trip unable to tell "I raised the
243/// bound" from "I silently dropped your durably-persisted value." This type
244/// preserves the distinction so the server can log/metric the dropped commits —
245/// a leading indicator of epoch churn or persist reordering.
246#[derive(Copy, Clone, Debug, PartialEq, Eq)]
247pub enum CommitOutcome {
248    /// The durable bound advanced to `high_water`.
249    Applied { high_water: u64 },
250    /// The bound did not move; see [`IgnoreReason`] for why.
251    Ignored(IgnoreReason),
252}
253
254/// Why a [`CommitOutcome::Ignored`] commit left the durable bound unchanged.
255///
256/// All three are benign and expected under normal failover: the epoch-fencing
257/// design of `try_commit_window_extension` deliberately drops late commits from
258/// a superseded epoch rather than erroring, and the monotonic bound rejects a
259/// value that does not advance. They are kept apart so a caller can distinguish
260/// epoch churn ([`NotLeader`](Self::NotLeader) / [`EpochMismatch`](Self::EpochMismatch))
261/// from persist reordering ([`NotAdvanced`](Self::NotAdvanced)).
262#[derive(Copy, Clone, Debug, PartialEq, Eq)]
263pub enum IgnoreReason {
264    /// The allocator is no longer a leader, so the commit has no window to raise.
265    NotLeader,
266    /// The allocator leads a different epoch than the commit targeted; the
267    /// commit is a late persist from a superseded epoch, fenced out.
268    EpochMismatch { expected: Epoch, current: Epoch },
269    /// The allocator still leads the targeted epoch, but the persisted value did
270    /// not exceed the current bound, so the monotonic bound rejects it.
271    NotAdvanced { persisted: u64, committed: u64 },
272}
273
274#[derive(Debug)]
275enum State {
276    NotLeader,
277    Leader {
278        epoch: Epoch,
279        /// Persisted upper bound: the allocator will not issue any timestamp with
280        /// `physical_ms` greater than this without a fresh `try_commit_window_extension`.
281        committed_high_water: u64,
282        /// Next `physical_ms` we are willing to issue at. Initialized to
283        /// `fence_floor` on leadership gain, then advances monotonically — never
284        /// retreats below the fence even when `now_ms` is a past value.
285        next_physical_ms: u64,
286        /// Next logical counter within `next_physical_ms`.
287        next_logical: u32,
288    },
289}
290
291pub struct Allocator {
292    state: State,
293}
294
295impl Allocator {
296    pub fn new() -> Self {
297        Allocator {
298            state: State::NotLeader,
299        }
300    }
301
302    /// Seed the allocator once the failover fence has durably persisted both
303    /// the floor and the pre-extended ceiling.
304    ///
305    /// `fence_floor` is the first `physical_ms` the new leader may issue —
306    /// the server sets it to `prior_high_water + 1` so the new leader's
307    /// timestamps are strictly above any the prior leader could have issued.
308    ///
309    /// `committed_ceiling` is the pre-extended upper bound the server has
310    /// already persisted (typically `fence_floor + window_ms`). It must
311    /// satisfy `committed_ceiling >= fence_floor` so the allocator can serve
312    /// `try_grant` immediately without an additional extension round-trip.
313    pub fn become_leader(
314        &mut self,
315        fence_floor: PhysicalMs,
316        committed_ceiling: PhysicalMs,
317        epoch: Epoch,
318    ) -> Result<(), CoreError> {
319        if committed_ceiling < fence_floor {
320            return Err(CoreError::InvalidLeadershipWindow {
321                fence_floor: fence_floor.get(),
322                committed_ceiling: committed_ceiling.get(),
323            });
324        }
325        self.state = State::Leader {
326            epoch,
327            committed_high_water: committed_ceiling.get(),
328            next_physical_ms: fence_floor.get(),
329            next_logical: 0,
330        };
331        Ok(())
332    }
333
334    pub fn step_down(&mut self) {
335        self.state = State::NotLeader;
336    }
337
338    pub fn is_leader(&self) -> bool {
339        matches!(self.state, State::Leader { .. })
340    }
341
342    pub fn epoch(&self) -> Option<Epoch> {
343        match self.state {
344            State::Leader { epoch, .. } => Some(epoch),
345            State::NotLeader => None,
346        }
347    }
348
349    /// Current committed high-water in physical-millisecond units, or `None`
350    /// when not the leader. The high-water is the upper bound the allocator
351    /// will not exceed without a fresh `try_commit_window_extension`.
352    pub fn committed_high_water(&self) -> Option<u64> {
353        match self.state {
354            State::Leader {
355                committed_high_water,
356                ..
357            } => Some(committed_high_water),
358            State::NotLeader => None,
359        }
360    }
361
362    /// Shared count guard for `try_grant` and `would_grant`, kept here so the
363    /// two entry points cannot drift. The server's extension single-flight
364    /// relies on `would_grant(now_ms, count) == true` implying the retry
365    /// `try_grant(now_ms, count)` succeeds (see `service::extend_window`), so
366    /// the set of rejected counts must be identical on both paths — splitting
367    /// this check across both methods would let a future edit to one degrade
368    /// the recheck into a spurious consensus round-trip (or worse).
369    ///
370    /// `count == 0` reuses the oversized case's `InvalidCount(count)`; since
371    /// `count` is `0` there, the surfaced value matches the prior explicit
372    /// `InvalidCount(0)`.
373    fn validate_count(count: u32) -> Result<(), CoreError> {
374        if count == 0 || count > LOGICAL_MAX + 1 {
375            return Err(CoreError::InvalidCount(count));
376        }
377        Ok(())
378    }
379
380    /// Single source of truth for the window-advance simulation and its bounds
381    /// checks, shared by `try_grant` and `would_grant`. Pure: it takes the
382    /// relevant state fields by value and mutates nothing, so a failed
383    /// projection cannot leave allocator state advanced.
384    ///
385    /// On success returns the `(physical_ms, logical_start)` the grant would
386    /// occupy. The two failure variants are kept distinct so `try_grant` can
387    /// surface the precise error its callers (and tests) expect;
388    /// `would_grant` collapses both to `false` via `.is_ok()`.
389    fn project_grant(
390        next_physical_ms: u64,
391        next_logical: u32,
392        committed_high_water: u64,
393        now_ms: u64,
394        count: u32,
395    ) -> Result<(u64, u32), CoreError> {
396        let mut physical_ms = next_physical_ms;
397        let mut logical = next_logical;
398
399        // Advance physical_ms toward wall clock if ahead. next_physical_ms is
400        // already at or above fence_floor, so a low now_ms simply leaves it there.
401        if now_ms > physical_ms {
402            physical_ms = now_ms;
403            logical = 0;
404        }
405
406        // If the current physical_ms cannot fit the request in its remaining
407        // logical range, advance to the next physical_ms.
408        if logical as u64 + count as u64 > LOGICAL_MAX as u64 + 1 {
409            physical_ms += 1;
410            logical = 0;
411        }
412
413        if physical_ms > PHYSICAL_MS_MAX {
414            return Err(CoreError::PhysicalMsOutOfRange(physical_ms));
415        }
416
417        // The fence: never issue a timestamp at a physical_ms above the committed
418        // high-water. If we are at or past the bound, the caller must extend.
419        if physical_ms > committed_high_water {
420            return Err(CoreError::WindowExhausted);
421        }
422
423        Ok((physical_ms, logical))
424    }
425
426    /// Normalize the post-grant cursor into a packable `(physical_ms, logical)`
427    /// pair. `project_grant` admits a grant that exactly fills the millisecond,
428    /// so `logical_start + count` can reach `LOGICAL_MAX + 1` — a logical the
429    /// packed layout cannot hold. Roll that exact-fill case to the next
430    /// millisecond at logical 0: this is precisely the position the next
431    /// `project_grant` call would compute, so behavior is unchanged; the stored
432    /// state just no longer depends on the implicit "next call always wraps or
433    /// resets" invariant. (The returned `physical_ms` may equal
434    /// `PHYSICAL_MS_MAX + 1`, which is never packed directly and is rejected as
435    /// out-of-range by the next grant's `project_grant`.)
436    ///
437    /// Caller contract: `logical_start + count <= LOGICAL_MAX + 1`, which
438    /// `project_grant`'s logical-range bound already enforces for `try_grant`.
439    fn advance_cursor(physical_ms: u64, logical_start: u32, count: u32) -> (u64, u32) {
440        let next_logical = logical_start + count;
441        if next_logical > LOGICAL_MAX {
442            (physical_ms + 1, 0)
443        } else {
444            (physical_ms, next_logical)
445        }
446    }
447
448    /// Hot path. Issue `count` timestamps from the in-memory window.
449    ///
450    /// Returns `WindowExhausted` when the in-memory remainder cannot cover the request;
451    /// the caller (typically the server) then runs prepare → persist → commit and retries.
452    ///
453    /// State is written back only on success: a failed grant (out-of-range or
454    /// exhausted window) leaves `next_physical_ms`/`next_logical` untouched.
455    pub fn try_grant(&mut self, now_ms: u64, count: u32) -> Result<WindowGrant, CoreError> {
456        Self::validate_count(count)?;
457        let State::Leader {
458            epoch,
459            committed_high_water,
460            next_physical_ms,
461            next_logical,
462        } = &mut self.state
463        else {
464            return Err(CoreError::NotLeader);
465        };
466
467        let (physical_ms, logical_start) = Self::project_grant(
468            *next_physical_ms,
469            *next_logical,
470            *committed_high_water,
471            now_ms,
472            count,
473        )?;
474
475        let grant = WindowGrant::new_unchecked(physical_ms, logical_start, count, *epoch);
476        (*next_physical_ms, *next_logical) =
477            Self::advance_cursor(physical_ms, logical_start, count);
478        Ok(grant)
479    }
480
481    /// Non-mutating predicate: would `try_grant(now_ms, count)` succeed right
482    /// now? Used by the server's extension single-flight to decide whether a
483    /// peer extender has already added enough room, avoiding a redundant
484    /// `persist_high_water` round-trip. Delegates to the same `project_grant`
485    /// helper `try_grant` uses, so the exhaustion check cannot drift — a
486    /// coarser predicate would risk false positives (skip the extension, then
487    /// fail the outer retry) for requests whose `count` straddles the window edge.
488    pub fn would_grant(&self, now_ms: u64, count: u32) -> bool {
489        if Self::validate_count(count).is_err() {
490            return false;
491        }
492        let State::Leader {
493            committed_high_water,
494            next_physical_ms,
495            next_logical,
496            ..
497        } = &self.state
498        else {
499            return false;
500        };
501
502        Self::project_grant(
503            *next_physical_ms,
504            *next_logical,
505            *committed_high_water,
506            now_ms,
507            count,
508        )
509        .is_ok()
510    }
511
512    /// Compute the high-water value the caller should durably persist before
513    /// calling `try_commit_window_extension`. Does not mutate.
514    ///
515    /// Returns `max(committed_high_water + 1, now_ms) + ahead_ms`. The +1 on
516    /// `committed_high_water` guarantees forward progress when wall clock is
517    /// behind the persisted bound (rare, but possible after a clock-step-back).
518    ///
519    /// Returns `Err(CoreError::NotLeader)` off-leader, matching every other
520    /// mutating method. A `0` sentinel here would be indistinguishable from a
521    /// legitimately prepared bound, letting a caller that skipped `is_leader()`
522    /// proceed as if preparation had succeeded.
523    pub fn try_prepare_window_extension(
524        &self,
525        now_ms: PhysicalMs,
526        ahead_ms: u64,
527    ) -> Result<PhysicalMs, CoreError> {
528        match &self.state {
529            State::NotLeader => Err(CoreError::NotLeader),
530            State::Leader {
531                committed_high_water,
532                ..
533            } => {
534                debug_assert!(
535                    *committed_high_water <= PHYSICAL_MS_MAX,
536                    "committed_high_water > PHYSICAL_MS_MAX: \
537                     try_on_leadership_gained / try_commit_window_extension invariant",
538                );
539                let floor = *committed_high_water + 1;
540                let now_ms = now_ms.get();
541                let requested = core::cmp::max(floor, now_ms).checked_add(ahead_ms).ok_or(
542                    CoreError::WindowExtensionOverflow {
543                        floor,
544                        now_ms,
545                        ahead_ms,
546                    },
547                )?;
548                // Re-wrap via `PhysicalMs::try_new`: the only remaining bound
549                // check on this path is on the *derived* sum, no longer on
550                // each input parameter.
551                PhysicalMs::try_new(requested)
552            }
553        }
554    }
555
556    /// Apply a durably-persisted window extension. `persisted_high_water` is
557    /// the value returned by `ConsensusDriver::persist_high_water`, which is
558    /// monotonic — it may equal or exceed the value passed to prepare.
559    ///
560    /// The `expected_epoch` argument fences out late-arriving commits from a
561    /// prior leader epoch: if the allocator is no longer at this epoch (either
562    /// it has lost leadership or a new leader took over), the commit is
563    /// dropped. Combined with the server's drain barrier, this guarantees a
564    /// late persist from epoch N cannot raise the durable bound observed by
565    /// epoch N+M.
566    ///
567    /// Returns [`CommitOutcome`]: `Applied` when the bound advanced, or
568    /// `Ignored` (with the reason) for the three benign drop cases. The 46-bit
569    /// physical-ceiling invariant on `persisted_high_water` is now enforced by
570    /// the [`PhysicalMs`] parameter type itself ([`PhysicalMs::try_new`]);
571    /// the `Result<_, CoreError>` shape is retained for source compatibility
572    /// with [`become_leader`] / [`try_prepare_window_extension`],
573    /// so callers can stay uniform under `?`, but no current code path here
574    /// produces `Err`.
575    pub fn try_commit_window_extension(
576        &mut self,
577        persisted_high_water: PhysicalMs,
578        expected_epoch: Epoch,
579    ) -> Result<CommitOutcome, CoreError> {
580        let persisted_high_water = persisted_high_water.get();
581        let State::Leader {
582            epoch,
583            committed_high_water,
584            ..
585        } = &mut self.state
586        else {
587            return Ok(CommitOutcome::Ignored(IgnoreReason::NotLeader));
588        };
589        // Epoch fencing takes precedence over the monotonic check: a late
590        // persist from a superseded epoch must report EpochMismatch even when
591        // its value also fails to advance, so churn is not masked as reordering.
592        if *epoch != expected_epoch {
593            return Ok(CommitOutcome::Ignored(IgnoreReason::EpochMismatch {
594                expected: expected_epoch,
595                current: *epoch,
596            }));
597        }
598        if persisted_high_water <= *committed_high_water {
599            return Ok(CommitOutcome::Ignored(IgnoreReason::NotAdvanced {
600                persisted: persisted_high_water,
601                committed: *committed_high_water,
602            }));
603        }
604        *committed_high_water = persisted_high_water;
605        Ok(CommitOutcome::Applied {
606            high_water: persisted_high_water,
607        })
608    }
609}
610
611impl Default for Allocator {
612    fn default() -> Self {
613        Self::new()
614    }
615}
616
617#[cfg(test)]
618mod tests {
619    use super::*;
620
621    // Tiny helper to keep the bound-validated literals readable. Every
622    // pre-newtype `become_leader(1_000, 5_000, …)` call carried an
623    // implicit "these literals are within the 46-bit field" precondition;
624    // wrapping each in `PhysicalMs::try_new(_).unwrap()` would have buried
625    // every test in unwrap noise without adding coverage (the literals are
626    // tiny constants under static review). `pms()` keeps the precondition
627    // explicit at the type level while reading the same as the original.
628    fn pms(value: u64) -> PhysicalMs {
629        PhysicalMs::try_new(value).expect("test literal exceeds PHYSICAL_MS_MAX")
630    }
631
632    #[test]
633    fn new_allocator_is_not_leader() {
634        let allocator = Allocator::new();
635        assert!(!allocator.is_leader());
636        assert_eq!(allocator.epoch(), None);
637    }
638
639    #[test]
640    fn become_leader_sets_epoch() {
641        let mut allocator = Allocator::new();
642        allocator
643            .become_leader(pms(1000), pms(5000), Epoch(5))
644            .unwrap();
645        assert!(allocator.is_leader());
646        assert_eq!(allocator.epoch(), Some(Epoch(5)));
647    }
648
649    #[test]
650    fn become_leader_rejects_inverted_window() {
651        // The per-argument PHYSICAL_MS_MAX checks are now enforced one layer
652        // out at `PhysicalMs::try_new` (see the `physical_ms` test block below),
653        // so this method's only remaining error is the cross-argument
654        // `committed_ceiling < fence_floor` invariant.
655        let mut allocator = Allocator::new();
656        assert_eq!(
657            allocator.become_leader(pms(5000), pms(4000), Epoch(5)),
658            Err(CoreError::InvalidLeadershipWindow {
659                fence_floor: 5000,
660                committed_ceiling: 4000
661            })
662        );
663    }
664
665    #[test]
666    fn step_down_clears_state() {
667        let mut allocator = Allocator::new();
668        allocator
669            .become_leader(pms(1000), pms(5000), Epoch(5))
670            .unwrap();
671        allocator.step_down();
672        assert!(!allocator.is_leader());
673        assert_eq!(allocator.epoch(), None);
674    }
675
676    #[test]
677    fn committed_high_water_tracks_leader_state_and_extensions() {
678        let mut allocator = Allocator::new();
679        assert_eq!(allocator.committed_high_water(), None);
680
681        allocator
682            .become_leader(pms(1_000), pms(5_000), Epoch(1))
683            .unwrap();
684        assert_eq!(allocator.committed_high_water(), Some(5_000));
685
686        let target = allocator
687            .try_prepare_window_extension(pms(2_000), 3_000)
688            .unwrap();
689        allocator
690            .try_commit_window_extension(target, Epoch(1))
691            .unwrap();
692        assert_eq!(allocator.committed_high_water(), Some(target.get()));
693
694        allocator.step_down();
695        assert_eq!(allocator.committed_high_water(), None);
696    }
697
698    #[test]
699    fn try_grant_not_leader() {
700        let mut allocator = Allocator::new();
701        assert_eq!(allocator.try_grant(1000, 1), Err(CoreError::NotLeader));
702    }
703
704    #[test]
705    fn try_grant_zero_count() {
706        let mut allocator = Allocator::new();
707        allocator
708            .become_leader(pms(1000), pms(5000), Epoch(1))
709            .unwrap();
710        assert_eq!(
711            allocator.try_grant(1000, 0),
712            Err(CoreError::InvalidCount(0))
713        );
714    }
715
716    #[test]
717    fn try_grant_oversized_count() {
718        let mut allocator = Allocator::new();
719        allocator
720            .become_leader(pms(1000), pms(5000), Epoch(1))
721            .unwrap();
722        let oversized = LOGICAL_MAX + 2;
723        assert_eq!(
724            allocator.try_grant(1000, oversized),
725            Err(CoreError::InvalidCount(oversized))
726        );
727    }
728
729    #[test]
730    fn try_grant_above_committed_is_window_exhausted() {
731        // Advancing `now_ms` past `committed_high_water` correctly returns
732        // WindowExhausted; the server then extends.
733        let mut allocator = Allocator::new();
734        // fence_floor=5_000, ceiling=5_000 (tight window, no pre-extended gap).
735        allocator
736            .become_leader(pms(5_000), pms(5_000), Epoch(1))
737            .unwrap();
738        // now_ms below floor: clamps to floor=5_000, which equals the ceiling → succeeds.
739        allocator.try_grant(4_999, 1).unwrap();
740        // now_ms above ceiling: window exhausted.
741        assert_eq!(
742            allocator.try_grant(5_001, 1),
743            Err(CoreError::WindowExhausted)
744        );
745    }
746
747    #[test]
748    fn failed_try_grant_does_not_advance_state() {
749        // A grant that fails the exhaustion check must leave the allocator's
750        // advance state untouched, so a later grant at a lower `now_ms` is not
751        // pinned to the failed attempt's wall clock.
752        let mut allocator = Allocator::new();
753        // Tight initial window: fence_floor == ceiling == 1_000.
754        allocator
755            .become_leader(pms(1_000), pms(1_000), Epoch(1))
756            .unwrap();
757        // now_ms far past the ceiling exhausts the window.
758        assert_eq!(
759            allocator.try_grant(5_000, 1),
760            Err(CoreError::WindowExhausted)
761        );
762        // Extend the durable bound to exactly 2_000.
763        let target = allocator
764            .try_prepare_window_extension(pms(2_000), 0)
765            .unwrap();
766        assert_eq!(target, pms(2_000)); // max(committed+1=1_001, now=2_000) + 0
767        allocator
768            .try_commit_window_extension(target, Epoch(1))
769            .unwrap();
770        // The failed grant must not have pinned next_physical_ms at 5_000: a
771        // grant at now_ms=2_000 advances cleanly to physical_ms=2_000 (<= the
772        // committed 2_000). If state had advanced on the failure, next_physical_ms
773        // would still be 5_000 and this would exhaust the window again.
774        let grant = allocator.try_grant(2_000, 1).unwrap();
775        assert_eq!(grant.physical_ms, 2_000);
776        assert_eq!(grant.logical_start, 0);
777    }
778
779    #[test]
780    fn try_grant_after_gain_serves_immediately() {
781        // The fence has already persisted a pre-extended window, so the allocator
782        // can serve immediately. Grants start at fence_floor regardless of now_ms.
783        let mut allocator = Allocator::new();
784        allocator
785            .become_leader(pms(5_000), pms(10_000), Epoch(1))
786            .unwrap();
787        let grant = allocator.try_grant(1_000, 1).unwrap();
788        // now_ms=1_000 < fence_floor=5_000, so next_physical_ms stays at 5_000.
789        assert_eq!(grant.physical_ms, 5_000);
790        assert_eq!(grant.logical_start, 0);
791        assert_eq!(grant.epoch, Epoch(1));
792    }
793
794    #[test]
795    fn prepare_window_extension_not_leader() {
796        // Off-leader prepare must error like every other mutating method, not
797        // return a `0` that a caller could mistake for a prepared bound.
798        let allocator = Allocator::new();
799        assert_eq!(
800            allocator.try_prepare_window_extension(pms(1000), 3000),
801            Err(CoreError::NotLeader)
802        );
803    }
804
805    #[test]
806    fn prepare_window_extension_uses_now_ms_when_ahead_of_high_water() {
807        let mut allocator = Allocator::new();
808        allocator
809            .become_leader(pms(1000), pms(1000), Epoch(1))
810            .unwrap();
811        let target = allocator
812            .try_prepare_window_extension(pms(2000), 3000)
813            .unwrap();
814        assert_eq!(target, pms(5000)); // max(1001, 2000) + 3000
815    }
816
817    #[test]
818    fn prepare_window_extension_uses_high_water_floor_when_clock_behind() {
819        let mut allocator = Allocator::new();
820        allocator
821            .become_leader(pms(10_000), pms(10_000), Epoch(1))
822            .unwrap();
823        let target = allocator
824            .try_prepare_window_extension(pms(500), 3000)
825            .unwrap();
826        // floor = 10_001, clock = 500. max = 10_001. + 3000 = 13_001.
827        assert_eq!(target, pms(13_001));
828    }
829
830    #[test]
831    fn prepare_window_extension_rejects_out_of_range_target() {
832        let mut allocator = Allocator::new();
833        allocator
834            .become_leader(PhysicalMs::MAX, PhysicalMs::MAX, Epoch(1))
835            .unwrap();
836        assert_eq!(
837            allocator.try_prepare_window_extension(PhysicalMs::MAX, 1),
838            Err(CoreError::PhysicalMsOutOfRange(PHYSICAL_MS_MAX + 2))
839        );
840    }
841
842    #[test]
843    fn prepare_window_extension_overflow_names_all_operands() {
844        // Pre-newtype, the canonical overflow scenario was a saturated clock
845        // (SystemClock::now_ms saturates to u64::MAX) plus any non-zero
846        // ahead_ms. The PhysicalMs newtype now rejects that scenario at the
847        // boundary wrap (PhysicalMs::try_new(u64::MAX) → PhysicalMsOutOfRange),
848        // surfacing an earlier, more precise error.
849        //
850        // The internal overflow path is still reachable, but only via a
851        // pathologically large `ahead_ms` (a duration, not a physical-ms, so
852        // it stays an unbounded u64). The error must still name all three
853        // real operands so the log points at the offending duration, not a
854        // phantom "someone passed an absurd physical_ms".
855        let mut allocator = Allocator::new();
856        allocator
857            .become_leader(pms(1_000), pms(1_000), Epoch(1))
858            .unwrap();
859        assert_eq!(
860            allocator.try_prepare_window_extension(pms(1_000), u64::MAX),
861            Err(CoreError::WindowExtensionOverflow {
862                floor: 1_001,
863                now_ms: 1_000,
864                ahead_ms: u64::MAX,
865            })
866        );
867    }
868
869    #[test]
870    fn commit_then_try_grant_succeeds() {
871        let mut allocator = Allocator::new();
872        allocator
873            .become_leader(pms(1000), pms(1000), Epoch(7))
874            .unwrap();
875        let target = allocator
876            .try_prepare_window_extension(pms(1000), 3000)
877            .unwrap();
878        assert_eq!(
879            allocator.try_commit_window_extension(target, Epoch(7)),
880            Ok(CommitOutcome::Applied {
881                high_water: target.get()
882            })
883        );
884        let grant = allocator.try_grant(1000, 5).unwrap();
885        assert_eq!(grant.count, 5);
886        assert_eq!(grant.logical_start, 0);
887        assert_eq!(grant.epoch, Epoch(7));
888        // physical_ms should be at most the persisted high-water.
889        assert!(grant.physical_ms <= target.get());
890    }
891
892    #[test]
893    fn commit_with_lower_value_is_ignored() {
894        let mut allocator = Allocator::new();
895        allocator
896            .become_leader(pms(1000), pms(1000), Epoch(1))
897            .unwrap();
898        assert_eq!(
899            allocator.try_commit_window_extension(pms(5000), Epoch(1)),
900            Ok(CommitOutcome::Applied { high_water: 5000 })
901        );
902        // A non-advancing commit reports the values so the caller can tell a
903        // monotonic-bound regression (persist reordering) from epoch churn.
904        assert_eq!(
905            allocator.try_commit_window_extension(pms(3000), Epoch(1)),
906            Ok(CommitOutcome::Ignored(IgnoreReason::NotAdvanced {
907                persisted: 3000,
908                committed: 5000,
909            }))
910        );
911        // try_grant up to physical_ms=5000 should still work.
912        let grant = allocator.try_grant(4500, 1).unwrap();
913        assert_eq!(grant.physical_ms, 4500);
914    }
915
916    #[test]
917    fn commit_with_equal_value_is_ignored_not_applied() {
918        // persist_high_water is monotonic and may *equal* the prepared bound; an
919        // equal value moves nothing, so it is Ignored(NotAdvanced), not Applied.
920        let mut allocator = Allocator::new();
921        allocator
922            .become_leader(pms(1000), pms(5000), Epoch(1))
923            .unwrap();
924        assert_eq!(
925            allocator.try_commit_window_extension(pms(5000), Epoch(1)),
926            Ok(CommitOutcome::Ignored(IgnoreReason::NotAdvanced {
927                persisted: 5000,
928                committed: 5000,
929            }))
930        );
931    }
932
933    // (`commit_rejects_out_of_range_high_water` migrated to the `physical_ms`
934    // test block: the bound check is now at `PhysicalMs::try_new`, so the
935    // bad value can no longer reach `try_commit_window_extension`.)
936
937    #[test]
938    fn try_grant_rejects_out_of_range_clock() {
939        let mut allocator = Allocator::new();
940        allocator
941            .become_leader(pms(1000), PhysicalMs::MAX, Epoch(1))
942            .unwrap();
943        assert_eq!(
944            allocator.try_grant(PHYSICAL_MS_MAX + 1, 1),
945            Err(CoreError::PhysicalMsOutOfRange(PHYSICAL_MS_MAX + 1))
946        );
947    }
948
949    #[test]
950    fn commit_at_wrong_epoch_is_silently_dropped() {
951        let mut allocator = Allocator::new();
952        // fence_floor=1000, ceiling=1000: tight initial window.
953        allocator
954            .become_leader(pms(1000), pms(1000), Epoch(5))
955            .unwrap();
956        // A late persist from epoch 4 (the prior leader) — fenced out. The
957        // outcome names both epochs so the caller can metric epoch churn.
958        assert_eq!(
959            allocator.try_commit_window_extension(pms(9_999), Epoch(4)),
960            Ok(CommitOutcome::Ignored(IgnoreReason::EpochMismatch {
961                expected: Epoch(4),
962                current: Epoch(5),
963            }))
964        );
965        // The allocator's bound did not move; a grant at now=900 clamps to
966        // floor=1000, and a request with now=1_100 exhausts the window.
967        allocator.try_grant(900, 1).unwrap();
968        assert_eq!(
969            allocator.try_grant(1_100, 1),
970            Err(CoreError::WindowExhausted)
971        );
972    }
973
974    #[test]
975    fn commit_after_leadership_lost_is_ignored() {
976        let mut allocator = Allocator::new();
977        allocator
978            .become_leader(pms(1000), pms(5000), Epoch(1))
979            .unwrap();
980        allocator.step_down();
981        assert_eq!(
982            allocator.try_commit_window_extension(pms(9_999), Epoch(1)),
983            Ok(CommitOutcome::Ignored(IgnoreReason::NotLeader))
984        );
985        assert!(!allocator.is_leader());
986    }
987
988    #[test]
989    fn would_grant_matches_try_grant_outcome() {
990        let mut allocator = Allocator::new();
991        // Not leader: never grants.
992        assert!(!allocator.would_grant(1_000, 1));
993        // Invalid counts: never grants.
994        allocator
995            .become_leader(pms(1_000), pms(5_000), Epoch(1))
996            .unwrap();
997        assert!(!allocator.would_grant(1_000, 0));
998        assert!(!allocator.would_grant(1_000, LOGICAL_MAX + 2));
999        // Within-window: matches try_grant. now_ms below floor still grants
1000        // (clamped to floor=1_000, ceiling=5_000).
1001        assert!(allocator.would_grant(0, 1));
1002        // now_ms above ceiling: predicate refuses (would exhaust).
1003        assert!(!allocator.would_grant(5_001, 1));
1004        // Mid-window now_ms advances the predicate's internal physical_ms.
1005        assert!(allocator.would_grant(2_500, 1));
1006    }
1007
1008    #[test]
1009    fn would_grant_predicts_logical_wrap_advance() {
1010        // When (logical + count) overflows the per-ms logical range, the
1011        // predicate (like try_grant) advances physical_ms by 1. If that
1012        // advance leaves the window, would_grant must return false.
1013        let mut allocator = Allocator::new();
1014        allocator
1015            .become_leader(pms(1_000), pms(1_000), Epoch(1))
1016            .unwrap();
1017        // count >= LOGICAL_MAX + 1 forces the advance branch on a fresh
1018        // window: logical(0) + count(LOGICAL_MAX+1) doesn't overflow on its
1019        // own, but anything one bigger does. Use LOGICAL_MAX + 1 to land at
1020        // the edge, then any non-zero issue advances physical_ms.
1021        allocator.try_grant(1_000, LOGICAL_MAX + 1).unwrap();
1022        // Next grant of size 1 would advance to physical_ms = 1_001, which
1023        // exceeds the committed ceiling of 1_000.
1024        assert!(!allocator.would_grant(1_000, 1));
1025    }
1026
1027    #[test]
1028    fn would_grant_returns_false_when_advance_exceeds_physical_max() {
1029        // Construct an allocator at PHYSICAL_MS_MAX so the +1 advance
1030        // crosses the 46-bit ceiling and the predicate refuses.
1031        let mut allocator = Allocator::new();
1032        allocator
1033            .become_leader(PhysicalMs::MAX, PhysicalMs::MAX, Epoch(1))
1034            .unwrap();
1035        // Fill the logical range so the next would_grant call has to
1036        // advance physical_ms.
1037        allocator
1038            .try_grant(PHYSICAL_MS_MAX, LOGICAL_MAX + 1)
1039            .unwrap();
1040        assert!(!allocator.would_grant(PHYSICAL_MS_MAX, 1));
1041    }
1042
1043    #[test]
1044    fn default_constructs_not_leader_allocator() {
1045        let allocator = Allocator::default();
1046        assert!(!allocator.is_leader());
1047        assert_eq!(allocator.epoch(), None);
1048    }
1049
1050    #[test]
1051    fn logical_wraps_to_next_physical_ms() {
1052        let mut allocator = Allocator::new();
1053        // fence_floor=0, ceiling=0; extend to 10 before granting.
1054        allocator
1055            .become_leader(PhysicalMs::ZERO, PhysicalMs::ZERO, Epoch(1))
1056            .unwrap();
1057        allocator
1058            .try_commit_window_extension(pms(10), Epoch(1))
1059            .unwrap();
1060        // Issue LOGICAL_MAX+1 logicals at physical_ms=1, then one more should bump to 2.
1061        let first = allocator.try_grant(1, LOGICAL_MAX + 1).unwrap();
1062        assert_eq!(first.physical_ms, 1);
1063        assert_eq!(first.logical_start, 0);
1064        let second = allocator.try_grant(1, 1).unwrap();
1065        assert_eq!(second.physical_ms, 2);
1066        assert_eq!(second.logical_start, 0);
1067    }
1068
1069    #[test]
1070    fn exact_fill_grant_normalizes_stored_state_to_packable() {
1071        // A grant that consumes a millisecond's entire logical range
1072        // (logical_start + count == LOGICAL_MAX + 1) must not leave the
1073        // LOGICAL_MAX+1 sentinel in next_logical: that value cannot be packed
1074        // (Timestamp::pack asserts logical <= LOGICAL_MAX), so the stored state
1075        // would only be safe by the implicit "next call always wraps" invariant.
1076        // The write-back normalizes it to the already-rolled position
1077        // (physical_ms + 1, 0), so stored state is always directly packable.
1078        let mut allocator = Allocator::new();
1079        allocator
1080            .become_leader(PhysicalMs::ZERO, PhysicalMs::ZERO, Epoch(1))
1081            .unwrap();
1082        allocator
1083            .try_commit_window_extension(pms(10), Epoch(1))
1084            .unwrap();
1085        // Fill physical_ms=1 exactly: logical [0, LOGICAL_MAX].
1086        let grant = allocator.try_grant(1, LOGICAL_MAX + 1).unwrap();
1087        assert_eq!(grant.physical_ms, 1);
1088        assert_eq!(grant.logical_start, 0);
1089
1090        let State::Leader {
1091            next_physical_ms,
1092            next_logical,
1093            ..
1094        } = allocator.state
1095        else {
1096            panic!("expected leader state after a successful grant");
1097        };
1098        // The stored cursor rolled to the next millisecond at logical 0 …
1099        assert_eq!(next_physical_ms, 2);
1100        assert_eq!(next_logical, 0);
1101        // … and is, by construction, a packable timestamp.
1102        assert!(Timestamp::try_pack(next_physical_ms, next_logical).is_ok());
1103    }
1104
1105    #[test]
1106    fn try_new_accepts_valid_grant_and_packs_boundaries() {
1107        // A checked grant exposes its fields through accessors and its
1108        // boundary timestamps pack without panicking — first() at
1109        // logical_start, last() at logical_start + count - 1.
1110        let grant = WindowGrant::try_new(1_000, 5, 3, Epoch(7)).unwrap();
1111        assert_eq!(grant.physical_ms(), 1_000);
1112        assert_eq!(grant.logical_start(), 5);
1113        assert_eq!(grant.count(), 3);
1114        assert_eq!(grant.epoch(), Epoch(7));
1115        assert_eq!(grant.first(), Timestamp::pack(1_000, 5));
1116        assert_eq!(grant.last(), Timestamp::pack(1_000, 7));
1117    }
1118
1119    #[test]
1120    fn try_new_accepts_max_logical_boundary() {
1121        // logical_start + count - 1 == LOGICAL_MAX is the widest in-range grant.
1122        let grant = WindowGrant::try_new(1_000, 0, LOGICAL_MAX + 1, Epoch(1)).unwrap();
1123        assert_eq!(grant.last(), Timestamp::pack(1_000, LOGICAL_MAX));
1124    }
1125
1126    #[test]
1127    fn try_new_rejects_zero_count() {
1128        // count == 0 would underflow logical_start + count - 1 in last().
1129        assert_eq!(
1130            WindowGrant::try_new(1_000, 0, 0, Epoch(1)),
1131            Err(CoreError::InvalidCount(0))
1132        );
1133    }
1134
1135    #[test]
1136    fn try_new_rejects_out_of_range_physical_ms() {
1137        assert_eq!(
1138            WindowGrant::try_new(PHYSICAL_MS_MAX + 1, 0, 1, Epoch(1)),
1139            Err(CoreError::PhysicalMsOutOfRange(PHYSICAL_MS_MAX + 1))
1140        );
1141    }
1142
1143    #[test]
1144    fn try_new_rejects_logical_range_overflow() {
1145        // last logical (logical_start + count - 1) exceeds the 18-bit field.
1146        assert_eq!(
1147            WindowGrant::try_new(1_000, LOGICAL_MAX, 2, Epoch(1)),
1148            Err(CoreError::LogicalRangeOutOfRange {
1149                logical_start: LOGICAL_MAX,
1150                count: 2
1151            })
1152        );
1153    }
1154
1155    #[test]
1156    fn try_new_rejects_logical_count_u32_overflow() {
1157        // logical_start + (count - 1) overflows u32 before any range check.
1158        assert_eq!(
1159            WindowGrant::try_new(1_000, u32::MAX, 2, Epoch(1)),
1160            Err(CoreError::LogicalRangeOutOfRange {
1161                logical_start: u32::MAX,
1162                count: 2
1163            })
1164        );
1165    }
1166
1167    // ----------------------------------------------------------------
1168    // PhysicalMs newtype: the construction-site bound check that the
1169    // three Allocator entry points used to re-implement inline. Every
1170    // assertion below was previously expressed as a runtime check
1171    // *inside* become_leader, try_prepare_window_extension,
1172    // or try_commit_window_extension; they now belong to the type.
1173    // ----------------------------------------------------------------
1174
1175    #[test]
1176    fn physical_ms_accepts_zero() {
1177        assert_eq!(PhysicalMs::try_new(0).unwrap().get(), 0);
1178    }
1179
1180    #[test]
1181    fn physical_ms_accepts_max() {
1182        assert_eq!(
1183            PhysicalMs::try_new(PHYSICAL_MS_MAX).unwrap().get(),
1184            PHYSICAL_MS_MAX,
1185        );
1186    }
1187
1188    #[test]
1189    fn physical_ms_rejects_one_past_max() {
1190        // The previously-inline checks in become_leader and
1191        // try_commit_window_extension lived at this exact boundary;
1192        // they now live here.
1193        assert_eq!(
1194            PhysicalMs::try_new(PHYSICAL_MS_MAX + 1),
1195            Err(CoreError::PhysicalMsOutOfRange(PHYSICAL_MS_MAX + 1)),
1196        );
1197    }
1198
1199    #[test]
1200    fn physical_ms_rejects_u64_max() {
1201        // The saturated-clock scenario the old `try_prepare_window_extension`
1202        // overflow test probed by passing u64::MAX is now caught one layer
1203        // out, at construction.
1204        assert_eq!(
1205            PhysicalMs::try_new(u64::MAX),
1206            Err(CoreError::PhysicalMsOutOfRange(u64::MAX)),
1207        );
1208    }
1209
1210    #[test]
1211    fn physical_ms_max_const_matches_try_new() {
1212        assert_eq!(
1213            PhysicalMs::MAX,
1214            PhysicalMs::try_new(PHYSICAL_MS_MAX).unwrap(),
1215        );
1216    }
1217
1218    #[test]
1219    fn physical_ms_zero_const_matches_try_new_and_default() {
1220        // ZERO, Default::default(), and try_new(0) must all coincide so
1221        // callers can use any spelling without semantic difference.
1222        assert_eq!(PhysicalMs::ZERO, PhysicalMs::try_new(0).unwrap());
1223        assert_eq!(PhysicalMs::default(), PhysicalMs::ZERO);
1224    }
1225
1226    #[test]
1227    fn physical_ms_try_from_matches_try_new() {
1228        // TryFrom<u64> is required for generic conversion code; it must
1229        // produce identical Ok/Err to the inherent try_new on every input.
1230        let good: u64 = 1_234_567;
1231        let from_inherent = PhysicalMs::try_new(good).unwrap();
1232        let from_trait: PhysicalMs = good.try_into().unwrap();
1233        assert_eq!(from_inherent, from_trait);
1234
1235        let bad = PHYSICAL_MS_MAX + 1;
1236        let bad_inherent = PhysicalMs::try_new(bad);
1237        let bad_trait: Result<PhysicalMs, CoreError> = bad.try_into();
1238        assert_eq!(bad_inherent, bad_trait);
1239    }
1240
1241    #[test]
1242    fn physical_ms_display_passes_through_inner_value() {
1243        // Display is a thin passthrough — it must format identically to the
1244        // underlying u64 so log lines and error messages read the same after
1245        // the refactor.
1246        let v: u64 = 4_242_424_242;
1247        assert_eq!(
1248            format!("{}", PhysicalMs::try_new(v).unwrap()),
1249            format!("{v}"),
1250        );
1251    }
1252
1253    #[test]
1254    fn physical_ms_ordering_follows_inner_value() {
1255        // The wrapper exposes Ord/PartialOrd so the allocator can compare
1256        // bounds (e.g. committed_ceiling < fence_floor) without stripping to
1257        // u64 — this test pins that the derived ordering matches the inner.
1258        let a = PhysicalMs::try_new(5).unwrap();
1259        let b = PhysicalMs::try_new(10).unwrap();
1260        assert!(a < b);
1261        assert!(b > a);
1262        assert!(a <= a);
1263    }
1264
1265    #[test]
1266    fn physical_ms_is_copy_and_eq() {
1267        // Compile-time witness: if a future edit accidentally drops `Copy`
1268        // or `Eq`, several allocator call sites that consume the value
1269        // twice or compare it inside `assert_eq!` would silently break.
1270        fn assert_copy_eq<T: Copy + Eq>() {}
1271        assert_copy_eq::<PhysicalMs>();
1272    }
1273}