Restore Rollback

Backup-restore safety net — atomic dataDir swap with a versioned rollback path. The primitive b.restore calls to put a freshly-decrypted bundle into place: filesystem rename is atomic on POSIX (and on Windows when nothing has the dir open), so the swap either fully completes or the previous dataDir is recoverable through rollback.

Three steps frame every restore: pre-restore snapshot (the existing dataDir is renamed into // before the new bundle moves in), post-restore verify (operator runs integrity / audit-chain checks against the live framework), and rollback on failure (a single rollback({ rollbackPath }) call reverses the swap). A marker JSON file carries operator-supplied metadata (bundleId, reason, timestamps) so list and purge are informative without rifling through directory contents.

Layout after a successful swap:

./data <- freshly-restored bundle ./data.rollbacks/ 2026-04-27T17-46-36-075Z/ <- previous dataDir 2026-04-27T17-46-36-075Z.marker.json

Stop-framework-first contract: this primitive does NOT close the framework's open file handles. On Linux a directory rename succeeds with handles open, but the running process keeps reading stale data. Operators run restore as stop framework -> swap -> start framework, same shape as a database restore. Concurrency guard: swap refuses if another rollback for the same millisecond timestamp already exists — collisions are vanishingly rare but the check keeps a double-fire from corrupting state.

b.restoreRollback.swap(opts) #

stable0.1.89
{
  stagingDir:   string,                         // pre-decrypted bundle, must exist
  dataDir:      string,                         // live data dir to replace
  rollbackRoot: string,                         // optional; defaults to ".rollbacks"
  marker:       object,                         // operator metadata for the marker file
}

Pre-restore snapshot + atomic swap. Renames the existing dataDir into //, then renames stagingDir into dataDir. If step two fails, step one is undone so the operator's dataDir is intact. Writes a .marker.json carrying operator metadata for later list / rollback discovery.

var r = b.restoreRollback.swap({
  stagingDir: "./data.staging",
  dataDir:    "./data",
  marker:     { bundleId: "bk-2026-05-09", reason: "scheduled-restore" },
});
// → { rollbackPath: "./data.rollbacks/2026-05-09T...", markerPath, swappedAt, marker }

b.restoreRollback.rollback(opts) #

stable0.1.89
{
  dataDir:      string,                         // live dataDir to replace
  rollbackPath: string,                         // must exist; from swap() return
  rollbackRoot: string,                         // optional; defaults to ".rollbacks"
}

Reverse a prior swap. Moves the current dataDir aside as discarded-/ (so the rename target is empty), then renames the named rollbackPath back into dataDir. The marker JSON is removed best-effort. Operator must have stopped the framework first — open file handles on the live dataDir on Windows cause the rename to fail.

var r = b.restoreRollback.swap({
  stagingDir: "./data.staging", dataDir: "./data",
  marker: { reason: "test" },
});
// post-restore verify failed:
await b.restoreRollback.rollback({ dataDir: "./data", rollbackPath: r.rollbackPath });
// → { restoredFrom: "./data.rollbacks/2026-05-09T...", discardedAt: "..." }

b.restoreRollback.list(opts) #

stable0.1.89
{
  dataDir:      string,                         // optional, used to derive rollbackRoot
  rollbackRoot: string,                         // optional; defaults to ".rollbacks"
}

Enumerate available rollback points, newest first. Reads each marker file (capped at 64 KiB via b.safeJson to bound a tampered-marker DoS). Skips discarded-* directories — those are sweep-only and never restore points.

var points = b.restoreRollback.list({ dataDir: "./data" });
points.forEach(function (p) {
  console.log(p.swappedAt, p.marker && p.marker.operator);
});
// → [{ rollbackPath, swappedAt, marker }, ...]

b.restoreRollback.purge(opts) #

stable0.1.89
{
  dataDir:      string,
  rollbackRoot: string,
  keep:         number,                         // non-negative integer, default 0
}

Sweep stale rollback directories. Always removes every directory named discarded- (those are never restore points), then keeps the newest keep rollback points and removes the rest along with their marker files. opts.keep defaults to 0; pass a positive integer to retain a sliding window. Best-effort: a per-path unlink failure is logged via the deleted-list omission rather than thrown.

var r = b.restoreRollback.purge({ dataDir: "./data", keep: 3 });
// → { kept: 3, deleted: ["./data.rollbacks/2026-04-...", ...] }

Last updated 2026-08-08T16:39:49.652Z by seeder.