Ledger ids: persist a monotonic counter instead of allocating from a directory scan
Triage finding, 2026-09-02: internal/ledger/local/local.go Store.Create already does atomic exclusive-create (os.Link, fails if the target exists) and internal/ledger/write.go Add() already retries correctly on ErrExists, marking the id taken and advancing. This has been true since commit 4b7a0d9/b7c2f61 (2026-07-30), before v0.1.0/v0.1.1 and before every reported occurrence of issue #27. Git archaeology on hq's ledger (dec-0542/dec-0544, via macbook-chief) shows a single linear commit history with no merge and no discarded parent, ruling out a cross-branch merge collision and best fitting a single shared working tree. The most likely mechanism: an entry was created (successfully, atomically) but never committed, then deleted before commit (matching issue #35's exact bug), freeing its number for a later session's legitimate reuse -- not a live Add-vs-Add race. A persisted monotonic counter closes #35 unconditionally either way, and as a side effect turns two sessions independently bumping the same counter into a loud git merge conflict on the counter file, instead of a silent duplicate id.
Alternatives on record — 4
✗Keep scan-then-exclusive-create (current), only add a distill reject/tombstone dispositionrefused Does not stop deletion from freeing a number for reuse (issue #35); reuse-after-deletion is a categorically separate hole a persisted counter closes outright, independent of whether a live create-vs-create race also exists.
Does not stop deletion from freeing a number for reuse (issue #35); reuse-after-deletion is a categorically separate hole a persisted counter closes outright, independent of whether a live create-vs-create race also exists.
✗git-ref reservation before each write (claim a ref before writing)refused Correct ordering mechanism but adds a network round-trip to every dira log call and introduces an orphaned-lock failure mode this fleet already lives with elsewhere (a stale claim past its expiry, unable to tell if the holder is alive).
Rejected in GH issue #27's own comment thread, 2026-09-02.
✗Server-side atomic id allocation servicerefused Correct, but makes recording a decision depend on a service being up -- worse than the bug it fixes, and against dira's local-first design point (int-0002).
Rejected in the same #27 thread.
✗Switch to content-addressed or time-ordered ids (ULID/KSUID)refused Removes the race/reuse class entirely but costs the human-readable sequential ids people already cite by number in prose, a real usability property.
Issue #27's own body raised this as a fallback, not a first choice.
revisit ifa stress test on ledger.Add finds a genuine live create-vs-create collision the current exclusive-create retry does not catch, at which point the fix must also touch Add itself, not only allocation