# When does a monotonically increasing counter wrap and silently regress? **Date of Record:** 2026 November 2 **Scope:** Applies to fixed-width counters used as monotonic sequence numbers, offsets, or tiebreakers (sequence ids, log offsets, fencing tokens) where the reader assumes monotonicity. Excludes 128-bit or arbitrary-precision counters. ## Core Distinction **Predicate.** A fixed-width counter is monotonic only while `observed_rate * lifetime < 2^bit_width`. At wrap, the counter returns to zero and the next value is *less than* the last, breaking every ordering guarantee built on it. **Failure.** A 64-bit counter at 10^9 increments per second reaches 2^64 in roughly 585 years — safe. A 32-bit counter at 10^6 per second wraps in roughly 71 minutes. A 32-bit microseconds counter at real time wraps in 71 minutes. The choice of bit-width determines the safety margin, and the margin is invisible at the type. **Asymmetry.** A comparison against a single value never reveals wrap. Only a comparison between two adjacent observations reveals it, and only if the reader checks direction. ## What Was Observed **Signature.** Sequence comparisons jumping backward. Sort-by-sequence returning recent items at the top of the list, or dropped entirely from a "greater than cursor" query. Fencing tokens appearing to be stale. **Trace.** A 32-bit microsecond timer wraps at 2^32 µs = 71.6 min. A job scheduled before the wrap writes offset 100; after the wrap, the same job writes offset 5. A cursor of "greater than 100" returns nothing for the next 71 minutes. **Why monitoring stays green.** Both writes succeed. The counter is doing what it does. No error appears until a reader compares two values and gets a surprising result — often in a distant system, far from the writer. ## Condition of Validity Monotonicity holds only while at least one applies: 1. **Bit-width margin.** `rate * expected_lifetime * safety_factor < 2^bit_width`. 2. **Wrap-aware comparison.** Readers compare modulo the bit width (RFC 1982 serial number arithmetic) rather than with plain less-than. 3. **Extended representation.** The counter is widened (to 64 or 128 bits) before wrap becomes plausible on the deployment timescale. Condition 2 alone makes wrap harmless; 1 and 3 prevent it. ## Decision Boundary - Width with margin > 10x expected lifetime: safe by construction. - Width with margin < 1x expected lifetime: wrap is guaranteed before decommission. - Wrap-aware reader regardless of width: safe, but every reader must be wrap-aware, not just some. - Widened and non-wrap-aware: safe, provided the widening was applied before any wrap occurred in production. ## Operational Effect The defect couples a storage decision (bit width) to a reader assumption (monotonicity) that is rarely written down. Remediation is widening or wrap-aware comparison. The trap is to widen without auditing readers. Widening does not break the old data, but it silently converts the wrap point into a new one and leaves wrap-aware code doing unnecessary math. ```python from dataclasses import dataclass class CounterWrapError(RuntimeError): """Raised when a counter's width cannot absorb expected lifetime traffic.""" @dataclass(frozen=True) class CounterGate: bit_width: int observed_rate: int expected_lifetime_s: int safety_factor: float = 10.0 def verify(self) -> bool: capacity = 2 ** self.bit_width expected = self.observed_rate * self.expected_lifetime_s * self.safety_factor if expected >= capacity: raise CounterWrapError( f"expected {int(expected)} >= capacity {capacity}: " f"wrap is guaranteed within lifetime for width {self.bit_width}" ) return True def dispatch_counter_mode(gate: CounterGate) -> str: try: gate.verify() except CounterWrapError: return "wrap-aware-reader" return "plain" ```