docs: RELEASES.md entry for 8.2.4 (non-destructive restore)
This commit is contained in:
parent
a2f4f6a550
commit
457469593a
1 changed files with 26 additions and 0 deletions
26
RELEASES.md
26
RELEASES.md
|
|
@ -10,6 +10,32 @@ Full auto-generated changelog: `CHANGELOG.md` · Releases: https://github.com/so
|
|||
|
||||
---
|
||||
|
||||
## 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
|
||||
snapshot in — so any copy failure left the store destroyed with only a partial copy. The sharpest
|
||||
edge: the copy (`fs.cp`) materialized the holes of sparse mmap blob files, so a snapshot that fits
|
||||
on disk could balloon and `ENOSPC` mid-copy, and the recovery tool would have just destroyed the
|
||||
brain it was asked to recover. Reported from a downstream incident's recovery forensics.
|
||||
|
||||
Restore is now **non-destructive and crash-resumable**:
|
||||
|
||||
- The snapshot is copied into a staging area (`_restore_staging/`) **before any live data is
|
||||
touched**. The copy is **sparse-aware** — all-zero regions are left as holes, so a store of
|
||||
mostly-hole blobs restores at its true allocated size instead of its apparent size.
|
||||
- A copy failure (including `ENOSPC`) removes only the half-written staging area and throws; the
|
||||
live store is left **exactly as it was**.
|
||||
- Only after the copy succeeds and a completion marker is `fsync`'d does an **atomic per-entry
|
||||
swap** move the staged data into place — same-filesystem renames that cannot fail for disk space.
|
||||
- A crash mid-swap is finished **forward** on the next open: startup resumes a committed-but-
|
||||
incomplete swap, or discards an uncommitted staging area (live data still authoritative).
|
||||
|
||||
No API change — `restore(path, { confirm: true })` is unchanged. `persist()` was already safe
|
||||
(hard-link snapshot). Regression (`tests/integration/restore-nondestructive.test.ts`): a forced
|
||||
copy failure leaves live data fully intact, a normal restore round-trips, an interrupted-but-
|
||||
committed restore completes on reopen, an uncommitted staging area is discarded, and the sparse
|
||||
copy is byte-identical with allocation far below apparent size.
|
||||
|
||||
## v8.2.3 — 2026-07-12 (a committed transaction is durable on return)
|
||||
|
||||
Durability fix. A `transact()` reported "committed" while its canonical entity writes were still
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue