docs: RELEASES.md entry for 8.2.5 (honest rollback-failure response)
This commit is contained in:
parent
711d2f046a
commit
a7c7aa5102
1 changed files with 34 additions and 0 deletions
34
RELEASES.md
34
RELEASES.md
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue