fix(restore): a restore is an unclean event — the swap runs quiesced and the snapshot's durability stamps never survive it
All checks were successful
CI / Node 22 (push) Successful in 12m13s
CI / Node 24 (push) Successful in 12m8s
CI / Bun (latest) (push) Successful in 12m20s

Two defects with one root, found by the fold-checkpoint work's first
integration gate. (1) THE RACE: restore() never quiesced the generation
store, so a background flush could write into _system/ while the swap was
removing it — observed as ENOTEMPTY mid-swap when a checkpoint stamp landed
between readdir and rmdir. The swap now runs inside the store's exclusive
section (runStateReplacement): flush timer disarmed, pending tier and
checkpoint accumulator discarded BEFORE any directory moves. (2) THE
INHERITED ASSERTION: a snapshot carries its source brain's clean-shutdown
marker and fold checkpoint, but the restored files were bulk-copied without
per-file fsync — the inherited stamps would suppress exactly the recovery
fold that cures a post-restore power cut. reopenAfterRestore now deletes
both stamps before reopening: the open treats the store as uncleanly shut,
folds the restored log into canonical, barrier-syncs what it re-applied,
and stamps fresh — the restored state is durably founded at restore time
instead of borrowing assertions about bytes this disk never synced.

Pinned: restore under in-flight traffic completes; the pre-restore stamp
does not survive; the post-restore stamp is the reopen fold's own, at the
restored watermark.
This commit is contained in:
David Snelling 2026-08-13 09:19:14 -07:00
parent ff43de1ada
commit 9ca80667c3
3 changed files with 85 additions and 1 deletions

View file

@ -9229,7 +9229,13 @@ export class Brainy<T = any> implements BrainyInterface<T> {
}
const floorGeneration = this.generationStore.generation()
await this.storage.restoreFromDirectory(path)
// The swap runs inside the generation store's exclusive section: pending
// flush timers are disarmed and buffers discarded BEFORE any directory is
// removed, so a background flush can never write into `_system/` mid-swap
// (the ENOTEMPTY race a checkpoint stamp once hit).
await this.generationStore.runStateReplacement(() =>
this.storage.restoreFromDirectory(path)
)
await this.generationStore.reopenAfterRestore(floorGeneration)
// If the entity-id mapper is a NATIVE provider with a `rebuild()`, reload it