Skip to content

Cloud Vault: compress, discard history, download and restore from file - #2201

Closed
joepio wants to merge 3 commits into
claude/project-thread-lmbduifrom
claude/vault-management
Closed

joepio wants to merge 3 commits into
claude/project-thread-lmbduifrom
claude/vault-management

Conversation

@joepio

@joepio joepio commented Oct 10, 2026

Copy link
Copy Markdown
Member

Requested by Joep · project thread

Stacked on #2199 (Loro deltas in signed commits); retarget to develop once that merges.

Before: the Cloud Vault panel could only "Free up space" (prune what the retention rules already allow). A user could not compact the backup on demand, could not drop the undo window together with the old chain, and could not take the backup to their own device.

After: the panel has a "Backup history" block with Compress now (writes one fresh checkpoint, then frees the old chain) and Discard history (same, then also frees the undo window, behind a confirmation that names the space gained). There are also Download backup, which writes one sealed .atomic-vault file, and Restore from file.

What it does: the forced checkpoint reuses the exporter's existing "no anchor means checkpoint" path, so cursors, lane state and coverage are unchanged, and free-up never runs unless the checkpoint pass succeeded. The file stays sealed under the drive key, is validated on read (magic, version, truncation, per-object hash, drive and key epoch), and restores through the same restoreDrive import path with an injectable object source.

How: backupDrive({ forceCheckpoint }) and compactVaultBackup() in helpers/managed/vault.ts; new helpers/managed/vaultFile.ts; UI in components/Vault/VaultStorage.tsx and VaultPanel.tsx; strings in the four .po files.

Known limits: Discard history removes older copies, the delta chain and the undo window but the newest snapshot keeps its own edit history (a true current-state-only backup needs a shallow snapshot, a separate follow-up). Restore still holds all objects in memory. File restore only works for an enrolled drive. The panel has only been rendered standalone with mocked data, not against a live atomic-saas, and the save-file picker was only tested through a mock. Screenshots are in the project thread.

Tests run locally: vitest for vault, vaultFile, useVaultBackup and the Vault components (90 passed), a new Rust test for a forced checkpoint restoring alone, tsc, lint and clippy clean. CI has not run.


Generated by Claude Code

Compress now forces the next backup pass to be a full checkpoint, publishes
it, then frees what it replaced. Discard history does the same and also
frees the undo window, behind a confirmation dialog. Free-up never runs if
the checkpoint did not land. The forced pass reuses the exporter's
"no anchor in the vault" input, so cursors and lane state are unchanged.
A checkpoint still holds each resource's whole Loro history, so the old
copy about keeping only the current state over-promised. Discard history
removes older copies, the delta chain and the undo window.
"Download backup" saves the drive's sealed vault objects as one
.atomic-vault file (versioned header, then the objects in listing order,
each with a SHA-256). "Restore from file" validates it and feeds the
objects through the same import as a cloud restore, which now takes an
injectable object source. Nothing is uploaded; the file stays sealed and
needs the drive key.

joepio commented Oct 10, 2026

Copy link
Copy Markdown
Member Author

Complexity review (about 1,000 lines of production code, the rest .po and tests; I did not read VaultStorage/VaultPanel/useDriveVault, client-db*.ts, nodeVault.ts or wasm/tests/vault.rs). Nothing blocks merge.

  1. vaultFile.ts (477 lines) records more than restore reads. The header stores per-object kind, lane, segment, checkpointN and keyEpoch, but restore only uses objectKey, size and contentHash. checkpointNumberFromKey, the VaultObject to entry mapping and most of isObjectEntry serve data nothing consumes. Trim the header to key, size and hash plus the file-level keyEpoch and drivePseudonym.
  2. VaultFileProblem has 9 variants plus a switch of translated strings; about 4 would do (optional).
  3. Check that the runVaultBackup chaining (existing.then(() => runVaultBackup(args), …) in vault.ts) cannot loop forever.
  4. The Rust test a_forced_checkpoint_replaces_the_chain_and_restores_alone (lib/src/vault/sync.rs) hand-rolls a loop computing observed_lanes, and Vault: Discard history keeps only the current state in the backup #2202 copies it into discard_history. Extract one test helper.

Good: downloadVaultObjects<T> with RestoreSource removes a duplicate download loop, and forceCheckpoint reuses the existing path instead of adding a second one in Rust.


Generated by Claude Code

joepio commented Oct 11, 2026

Copy link
Copy Markdown
Member Author

Landed in develop through the batch #2210 (squash f39e460), tested together on Mancave run 5204.


Generated by Claude Code

@joepio joepio closed this Oct 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant