Skip to content

UUIDv7 ordering is millisecond-granular; document it and separate journal ordering from identity #2

Description

@June74

Document and section

docs/IDENTIFIERS.md — §2 Runtime identifiers, the event_id row and the UUIDv7 rationale paragraph.

What is wrong or unclear

Nothing currently written is incorrect. The document says UUIDv7 is "time-sortable, which gives index locality for the append-only journal," and that claim holds. The gap is what it does not say.

UUIDv7 (RFC 9562) encodes a Unix timestamp at millisecond resolution in its leading 48 bits. The remaining 74 bits are random. .NET's Guid.CreateVersion7() does not implement the optional monotonic-counter methods described in RFC 9562 §6.2, so:

  • identifiers generated in different milliseconds always sort in creation order;
  • identifiers generated in the same millisecond sort in essentially random order relative to each other.

Measured on .NET SDK 10.0.400 (Windows 11 x64) during Stage 0 Wave B toolchain verification: of 200 identifier pairs generated back to back within the same millisecond, 103 sorted in the wrong order — consistent with the ~50% that random low bits predict. Identifiers generated more than one millisecond apart sorted correctly in every trial.

Reproduction:

int outOfOrder = 0;
for (int t = 0; t < 200; t++) {
    var x = Guid.CreateVersion7().ToString();
    var y = Guid.CreateVersion7().ToString();
    if (x.Substring(0, 13) == y.Substring(0, 13) && string.CompareOrdinal(x, y) > 0) outOfOrder++;
}
// observed: 103

Why this matters beyond documentation wording. A reader can reasonably take "time-sortable" to mean event_id provides a total ordering of events. It does not. The append-only journal described in ARCHITECTURE.md ingests capture-hook events in bursts, so many events sharing a millisecond is the normal case rather than an edge case. If Stage 2 orders or replays the journal by sorting on event_id, ordering will be wrong for same-millisecond events roughly half the time, and the resulting defect would be intermittent and expensive to diagnose.

This also bears on idempotency and crash recovery: ARCHITECTURE.md requires that replayed events be idempotent and that a crash between capture and acknowledgement not duplicate memory. Recovery logic that resumes from "the highest event_id seen" would be unsound for the same reason.

Suggested correction

Two changes, both for a later wave — docs/IDENTIFIERS.md is on main, and CONTRIBUTING.md forbids direct commits there.

  1. Document the granularity in docs/IDENTIFIERS.md. State that UUIDv7 ordering is guaranteed only across millisecond boundaries, and that identifiers minted within the same millisecond have no defined relative order. Keep the existing index-locality rationale, which is unaffected.

  2. Separate identity from ordering in the Stage 2 journal contract. The journal needs its own monotonic sequence number for ordering and replay, with event_id retained for identity and index locality. These are two different jobs and should not share one field.

No change is proposed to the UUIDv7 decision itself. It remains the right choice for the reasons already recorded: a published standard, direct .NET support, and index locality for the journal.

Related

  • Discovered during Stage 0 toolchain verification after the .NET 10 SDK was installed, by probing the API that docs/IDENTIFIERS.md cites rather than assuming its behavior.
  • Companion carry-forward from the Wave A Codex review, also awaiting Stage 2/3: the source_record_id format {adapter}:{native_id} does not specify how to parse a native_id that itself contains a colon.

Metadata

Metadata

Assignees

No one assigned

    Labels

    documentationImprovements or additions to documentationneeds-triageNew report not yet reviewed by a maintainer

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions