Backup & Restore
From spec-memo Wiki, the living architecture & domain knowledge base
Feature Overview [edit]
spec-memo ships a portable vault archive engine for off-site backups, air-gapped migrations, and disaster recovery, plus a safe reset that always snapshots first.
- Export:
exportVaultserializes project metadata (project.json), all durable markdown records, and vaultconfig.jsoninto a JSON manifest archive. With a password, the payload is encrypted. - Import / restore:
importVault(aliasedrestoreVault) restores records and metadata from a.jsonor.ziparchive, rewrites compiled views, and rebuilds the SQLite FTS index. - Reset:
resetVaultwrites a mandatory timestamped zip backup, then wipes records/database (all projects or one project) and reinitializes a clean structure. - Inventory:
listBackupsenumerates$SPEC_MEMO_ROOT/backups/with manifest metadata.
Surfaces: CLI memo export-vault, memo import-vault/memo restore, memo backups, memo reset; the Status Monitor Backups tab and POST /api/vaults/export|import|backups|restore|reset endpoints.
Business Rules & Logic [edit]
- Archive formats: plaintext
spec-memo-vault-v1(manifest +vaultConfig+projects[]with rawrelativePath/contentrecords) or encryptedspec-memo-encrypted-vault-v1. - Encryption: AES-256-GCM with PBKDF2-SHA256 key derivation, 100,000 iterations, random 16-byte salt, 12-byte IV, 32-byte key, and a GCM auth tag. A wrong password or tampered ciphertext fails with
Decryption failed: Incorrect password or corrupted backup archive.and aborts cleanly. exportVaultwalksRECORD_SUBDIRS=traps,decisions,specs,plans,logs,reviews,scratch,prompts,sessions;staterecords live underplans/. Conflict sidecars (*.conflict.*.md) are excluded. Each record passes the secret assertion before packing; aSafety violationaborts the export.manifest.scopeisproject(one project) orfull(all projects), andmanifest.recordsByKindcounts packed kinds.importVaultacceptsarchivePath(.zipmagic or extension is unpacked) orpayload. It preserves or overwrites (overwrite, default true) config and record files, validates frontmatter and path containment, and refuses unrecognized formats. Compiled views and FTS are rebuilt afterward.- Reset safety invariant: a full backup is written before any deletion. If backup generation fails (empty payload, disk/write error), reset aborts with no records or databases touched.
- Pre-wipe backup filename is
YYYY-MM-DD-HH-mm-ss-backup.zip(no colons; Windows-safe and chronologically sortable) under$SPEC_MEMO_ROOT/backups/, using an atomic write (.partialrename) plus a.meta.jsonsidecar. - Full reset removes all
projects/,memo.sqlite*,.sync/sync state, and a stray roothybrid-state.json; it never removesconfig.jsonor thebackups/directory. A project reset removes only that project. After deletionensureVaultStructureandrebuildIndexrun so the vault is immediately writable. memo reset [--all] [--project <id>] [--force] [--password <pwd>]requires--forcein non-interactive environments; otherwise it prompts on a TTY. Output prints the backup path and wipe counts.POST /api/vaults/resetrequires{ confirm: true }(else400) and returns{ ok, projectId?, backupFilename, backupPath, wipedProjectsCount, wipedRecordsCount, rebuiltFts }. It emits onetype: "system",kind: "write"activity event.listBackupsreturns items sorted newest first withfilename,size,createdAt,isZip,encrypted,scope,projectIds,recordCount, andrecordsByKind. Encrypted archives that cannot be inspected appear withencrypted: trueand null/empty counts. Filters:q,scope,projectId,encrypted,since,until, kind (repeatable), size bounds.resolveBackupPathispath.basenameonly, rejects/,\, and.., requires.zip/.json, and confirms containment underbackups/. Restore acceptsbackupFilename(resolved underbackups/),archivePath(must be inside the vault orbackups/), or a multipart file; the restore multipart cap is 50 MiB and the import multipart cap is 64 MiB.- Reset/restore/backup mutations take the vault lock, so they serialize with sync and other writers.
Technical Architecture [edit]
src/backup.ts: typesExportVaultOptions/ExportVaultResult,ImportVaultOptions/ImportVaultResult,ResetVaultOptions/ResetVaultResult,PersistBackupOptions/PersistBackupResult,InspectBackupResult; functionsexportVault,importVault,restoreVault,resetVault,listBackups,filterBackupList,persistVaultBackup,deleteBackup,inspectBackup,resolveBackupPath,formatBackupFilename; internalsencryptPayload,decryptPayload,writeBackupArchiveAtomic,readBackupSidecarMeta.src/status-backup.ts: zero-dependencypackVaultZip(jsonPayload, entryName='vault-backup.json'),unpackVaultZip(buffer), andparseMultipartFormData(uses Nodezlib).src/status.tsroutes:POST /api/vaults/export(browser zip download; full backup needsconfirmFullBackup: true),POST /api/vaults/import(multipartarchive+ optionalpassword; 64 MiB cap; ZIP magic check),GET /api/vaults/backups(filters),POST /api/vaults/backups(persist;confirmFullBackupguard),GET /api/vaults/backups/{filename}(download),GET/POST /api/vaults/backups/{filename}/inspect,DELETE /api/vaults/backups/{filename}(confirm: true),POST /api/vaults/reset,POST /api/vaults/restore.- 3-mode topology (
0030-vault-reset-and-proxy-monitor.spec.md): the status monitor reportstopology(local-vault,intermediary-proxy,final-remote) via the enrichedGET /api/status, and a proxy/hybrid instance can start its own companion with--status/--status-portwhile keeping its UI alive when the upstream daemon is unreachable. See Status Monitor. - CLI extras:
memo export-vault [--password] [-o <file>],memo import-vault/restore [<archive>|--backup <path>|--latest] [--password] [--overwrite],memo backups,memo reset.export-vaultandimport-vaultare refused in remote mode;restore,backups, andresetare not inREMOTE_MODE_RESTRICTIONand run unchecked there. - Provenance:
0021-vault-backup.spec.md(archive + AES-256-GCM/PBKDF2),0026-status-vault-backup-ui.spec.md(UI export/import),0030-vault-reset-and-proxy-monitor.spec.md(reset with pre-wipe backup, restore/list, topology),0032-status-backup-page.spec.md(complete kinds, dedicated Backups tab, inventory filters, delete/inspect). See Status Monitor and Vault Sync.