docs: RELEASES.md entry for 8.2.5 (honest rollback-failure response)

This commit is contained in:
David Snelling 2026-07-12 12:19:09 -07:00
parent 711d2f046a
commit a7c7aa5102

View file

@ -10,6 +10,40 @@ Full auto-generated changelog: `CHANGELOG.md` · Releases: https://github.com/so
--- ---
## v8.2.5 — 2026-07-12 (honest response when a transaction rollback can't complete)
Data-integrity fix. When a transaction failed and its rollback then *also* failed to undo a
canonical write (retries exhausted), the old behavior logged, continued, and threw
`TransactionRollbackError` — while the record it couldn't undo stayed durable on disk, and the
transaction even reported its state as `'rolled_back'`. The caller got an error implying the write
was undone; a read-back showed the record. A failed rollback had no truthful response.
Rollback now tells the truth, with a two-branch contract:
- **Adopt-forward (safe case).** A single-op write (`add`/`update`/…) whose only damage is a
durably-present record — the write the caller asked for — is **adopted**: its generation is
committed, the write returns success, and a loud warning records that the derived index may be
incomplete for that id until the next rebuild/`repairIndex()`. No error, no double-write; the
record is immediately durable and retrievable by `get()`.
- **Fail loud + quarantine (unsafe case).** A multi-operation batch, or *any* case where a record
was lost (a remove/update whose restore-undo failed), throws the new **`StoreInconsistentError`**
naming every unreconciled record and its disposition (`orphan` vs `loss`), and puts the brain into
**write-quarantine**: reads keep working, but writes are refused until `repairIndex()` reconciles
the derived indexes against canonical storage and lifts the quarantine. The generation counter is
not advanced, and a transaction whose rollback failed is now `'inconsistent'`, never the
`'rolled_back'` lie.
The decision is made by *observation*, not guesswork: both commit paths already hold byte-identical
before-images, so after a failed rollback the store compares current canonical state to them to
classify exactly which records are orphaned or lost.
New export: `StoreInconsistentError` (with `.records` and `.cause`) and the `UnreconciledRecord` type.
No other API change; `repairIndex()` gains the quarantine-lift behavior. Regression
(`tests/integration/rollback-trapdoor.test.ts`) injects the exact failure (index add throws →
canonical delete-undo fails) and pins adopt-forward (durable, get-able, not quarantined), fail-loud
(`StoreInconsistentError` + quarantine + reads work + `repairIndex()` lifts it), and the error's
record naming.
## v8.2.4 — 2026-07-12 (restore can no longer destroy the store it's recovering) ## v8.2.4 — 2026-07-12 (restore can no longer destroy the store it's recovering)
Recovery-safety fix. `restore()` removed the entire live brain directory and THEN copied the Recovery-safety fix. `restore()` removed the entire live brain directory and THEN copied the