docs: RELEASES.md entry for 8.0.16 (atomic ifAbsent/upsert + exact blob refCounts)

This commit is contained in:
David Snelling 2026-07-08 15:13:26 -07:00
parent 867939ed50
commit 54e7c0eced

View file

@ -10,6 +10,37 @@ Full auto-generated changelog: `CHANGELOG.md` · Releases: https://github.com/so
---
## v8.0.16 — 2026-07-08 (concurrency fast-follow: atomic `ifAbsent`/`upsert` + exact blob reference counts)
Closes the two remaining check-then-act races found in the sweep that followed v8.0.15's CAS fix
(disclosed in that release's notes). Same bug class — a check performed before the serialization
point that guards the apply — same cure.
**1 — `add({ ifAbsent })` and `add({ upsert })` are now atomic.** The absence check ran before the
commit mutex, so N concurrent same-id creates could all pass it and all write — the second
silently overwriting the first, violating ifAbsent's "returns the existing id **without writing**"
contract and upsert's "merge, never clobber" contract. The insert leg now carries a must-be-absent
precondition (the same conditional-commit primitive as `ifRev`), verified under the commit mutex:
exactly one concurrent create wins; every other caller takes its documented resolution — ifAbsent
returns the existing id with zero writes, upsert merges into the now-existing entity (with a
bounded retry if a concurrent delete intervenes). Verified: 8 concurrent `ifAbsent` creates
advance the store by exactly ONE generation; 8 concurrent upserts on an absent id produce one
create + seven merges (`_rev` lands at exactly 8). Note: `transact()`'s batch-level
ifAbsent/upsert keep planning-time semantics (the batch converges to a valid entity; the window is
documented, not silent).
**2 — Blob reference counts are exact under concurrency.** The content-addressed blob store's
`write()` (dedup: exists → add a reference, absent → create) and `delete()` (decrement → remove at
zero) were unserialized read-modify-writes over the blob's metadata. Concurrent writes of
identical content could lose references — making a later delete remove bytes **another file still
referenced** (data loss), or leak unreferenced blobs. All reference-count-bearing mutations are
now serialized per content hash (distinct content never contends): N concurrent identical writes
yield exactly N references, and a blob is physically removed only when the true last reference
drops. Verified with concurrent write/delete storms.
Both fixes are in-process complete by construction — storage enforces single-writer-per-directory,
so the process is the whole concurrency domain. No API changes.
## v8.0.15 — 2026-07-08 (`ifRev` CAS is now atomic — exactly one winner under concurrency)
Correctness fix for optimistic concurrency, reported from a production cutover rehearsal. N