P11 slice 173 — msgid from unix-nanosecond time, not a monotonic counter#
Goal#
Change leveva::msgid::generate so the counter half of a msgid (<sid>-<n>) is
derived from the unix-nanosecond wall clock instead of a process-global monotonic
AtomicU64 that starts at 0 on every boot. This better satisfies the IRCv3
message-ids spec's restart-collision guidance — the spec
explicitly warns against "simple numeric counters … being reset if the server restarts" and
suggests prefixing "the server startup timestamp at a suitable precision." A whole-value
nanosecond timestamp goes one better: every issued id encodes wall-clock time, so a restart
keeps advancing rather than rewinding to 0.
Locked decisions#
- The id stays
<sid>-<nanos:x>(sid prefix → cross-server uniqueness; hex of the nanosecond value, same lowercase-hex shape the old counter used). No format break for clients (the spec says treat the id as an opaque case-sensitive string). - The clock source is the existing
crate::clock::unixnano()(leveva's native time unit) — no new clock plumbing. - Uniqueness is enforced, not assumed. A coarse clock (or two calls in the same
nanosecond, or a backward NTP step) could otherwise mint two equal values. A process-global
AtomicU64 LAST_NANOSmakes the issued valuemax(now, last+1)via a CAS loop, so the per-process id stream is strictly increasing ⇒ no two ids this server mints ever collide, regardless of clock behaviour.
Design#
leveva/src/msgid.rs:
- Pure core
bump_to_unique(prev: u64, now: u64) -> u64 = now.max(prev.saturating_add(1)). next_nanos(last: &AtomicU64, now: u64) -> u64— CAS loop overbump_to_unique, takes the atomic by ref so tests drive it with a fresh atomic (the global static would otherwise leak state across#[test]s and across proptest cases).generate(sid)=format!("{sid}-{nanos:x}")wherenanos = next_nanos(&LAST_NANOS, unixnano()).is_validunchanged.
No call-site changes: generate/is_valid keep their signatures (callers in
command/message.rs, command/tagmsg.rs, s2s/relay.rs).
Tests (write first — RED)#
- Unit:
generateisis_valid+ sid-prefixed; two generates differ; distinct sids → distinct prefixes;is_validrejects the forbidden bytes (carried over). - Unit: the numeric half of a generated id is a plausible recent nanosecond value
(> 1.5e18), proving it's time-derived, not a counter starting at
0— this is the slice's point. - Unit (pure):
bump_to_unique(prev, now)is always> prev, and== nowwhennow > prev. - Proptest fuzz (the required "fuzzing"): over an arbitrary sequence of
nowvalues fed tonext_nanoswith a fresh atomic, the issued sequence is strictly increasing (⇒ all ids distinct — the uniqueness invariant) and each issued value== max(now_i, prev_issued+1)(tracks the clock when it advances, only synthesises forward when it doesn't). Inverse covered: a non-advancing/backwardnowdoes not produce a duplicate or a rewind.
Divergences#
Update the module doc: drop the "restart resets the counter → reissue risk" divergence; replace with the nanosecond stance (restart keeps advancing; intra-process strict-monotonic enforcement removes same-nanosecond/back-step collisions; residual risk is only a wall-clock step backward across a restart, vanishingly small at nanosecond precision and bounded by the sid prefix).
Gate#
cargo test -p leveva green + cargo clippy -p leveva --tests clean + cargo build --workspace
0 warnings. Recorded per [[leveva-slice-recording]]: this plan file + p11.md heredoc entry +
PLAN.md P11 row bump (Done: 173).