Ulid.next() documents strict process-local monotonicity at Sources/ARCP/Ids/Ulid.swift:8, but MonotonicGenerator.next() only increments the random tail when nowMs == lastTime at Sources/ARCP/Ids/Ulid.swift:34. If the system clock moves backward, the code takes the fresh-random branch, stores the lower timestamp, and can return a ULID that sorts before an id already emitted by the same process. The random-tail overflow path at Sources/ARCP/Ids/Ulid.swift:49 also wraps all bytes to zero without advancing the timestamp, which breaks the same monotonic ordering contract if the tail space is exhausted within one millisecond.
Fix prompt: Make the generator use max(nowMs, lastTime) for the encoded timestamp and increment the previous random tail whenever the observed clock is less than or equal to lastTime. If the random tail overflows, advance the timestamp by one millisecond or otherwise fail in a controlled way rather than wrapping to a lower tail under the same timestamp. Add unit tests with an injectable clock or generator state hook for equal-time, backward-clock, and tail-overflow cases so lexicographic ordering is enforced deterministically.
Ulid.next()documents strict process-local monotonicity atSources/ARCP/Ids/Ulid.swift:8, butMonotonicGenerator.next()only increments the random tail whennowMs == lastTimeatSources/ARCP/Ids/Ulid.swift:34. If the system clock moves backward, the code takes the fresh-random branch, stores the lower timestamp, and can return a ULID that sorts before an id already emitted by the same process. The random-tail overflow path atSources/ARCP/Ids/Ulid.swift:49also wraps all bytes to zero without advancing the timestamp, which breaks the same monotonic ordering contract if the tail space is exhausted within one millisecond.Fix prompt: Make the generator use
max(nowMs, lastTime)for the encoded timestamp and increment the previous random tail whenever the observed clock is less than or equal tolastTime. If the random tail overflows, advance the timestamp by one millisecond or otherwise fail in a controlled way rather than wrapping to a lower tail under the same timestamp. Add unit tests with an injectable clock or generator state hook for equal-time, backward-clock, and tail-overflow cases so lexicographic ordering is enforced deterministically.