From 0932ecde37f2c11aa554d3f6d9847432afb2f3fc Mon Sep 17 00:00:00 2001 From: David Snelling Date: Tue, 14 Jul 2026 11:51:51 -0700 Subject: [PATCH] docs: RELEASES.md entry for 8.3.2 (honest counters) --- RELEASES.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/RELEASES.md b/RELEASES.md index 0db09513..ca57a1ef 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -10,6 +10,36 @@ Full auto-generated changelog: `CHANGELOG.md` · Releases: https://github.com/so --- +## v8.3.2 — 2026-07-14 (honest counters — the recount + removal-without-re-reading) + +Completes 8.3.1's delete-hygiene story at the counter layer, from a production proof chain reported +by a downstream deployment: persisted entity totals were permanently **inflated** — deletes whose +count decrement was silently skipped — and because paginated `totalCount` serves +`Math.max(persistedTotal, scanned)`, the inflated number always won and **no disk cleanup could ever +lower it**. + +- **A removal's count decrement no longer requires re-reading the record being removed.** The + decrement was sourced from re-reading the entity's metadata inside the delete; if that read + returned `null` (a replace race, or a ghost left by a pre-8.3.1 partial delete) the decrement was + silently skipped while the paired add had counted — minting drift on every write→delete→re-create + cycle. The caller's pre-delete read now rides through the whole delete path + (`remove()`/`removeMany()` → the delete operation → `deleteNoun`/`deleteVerb` → + `deleteNounMetadata`/`deleteVerbMetadata`, both sides symmetric): a null internal read falls back + to the known prior record instead of skipping. The `StorageAdapter` signatures gain an optional + `priorMetadata` parameter (additive; existing adapters unaffected). + +- **`repairIndex()` is the sanctioned counter recount — unconditional, and it actually persists.** + `rebuildTypeCounts()` previously rebuilt only the type-statistics arrays and computed the total + *just to log it* — the persisted scalar (`counts.json`) survived every "rebuild" untouched, so an + already-inflated brain could never be corrected. One canonical walk now rebuilds **every** counter + rollup — scalar totals, per-type maps, and type statistics — and persists them, and `repairIndex()` + runs it unconditionally (not only when orphan directories are found: counters can be inflated over + perfectly clean shelves). Brains with delete history should run `brain.repairIndex()` once after + upgrading; the correction survives reopen. + +No API breaks (optional-parameter additions only). Regression tests cover the drift cycle, the +null-read decrement fallback, and the persisted recount across reopen. + ## v8.3.1 — 2026-07-14 (full-removal deletes + family-scoped migration gate) Two production-reported fixes in the write/index spine, plus an operator repair path. No API changes;