Skip to content

Preserve ULID monotonicity when the system clock moves backward #56

@nficano

Description

@nficano

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingseverity:mediumMedium severity issue

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions