Backup and restore

Persistence stores snapshots as layout-v4 JSON plus per-store indexes. The backup story is the store backend’s own, because that is where the durable copy lives.

What is stored

Each store implements the same StateStore contract (load, save with expected_version, delete, forget, list_keys, lock, health, due_keys), plus the durable-timer deadlines, the idempotency inbox, the transition log, and (in xstate-eda) the outbox and dead-letter rows. The unit that matters is one machine instance (a key): its snapshot and its deadline.

Backing up per store

StoreFeatureBackup
FileStore::open(dir)alwayscopy the directory of .xsm.json records (plus .lock files); records are atomic-renamed
SqliteStore::open(path)sqlitecopy the database file — it uses WAL by default, so use SQLite’s online backup or a checkpointed copy, not a raw cp mid-write
RedisStoreredisthe backend’s own persistence (RDB/AOF), or SCAN the snap:{key} hashes
PostgresStorepostgrespg_dump of the xsm_* tables

The xsm snapshots command inspects and sanity-checks a store from the CLI:

xsm snapshots --store sqlite:///app.db checkout.json            # list records and their labels
xsm snapshots --store sqlite:///app.db checkout.json --stale    # records whose machine_version differs
xsm snapshots --store sqlite:///app.db checkout.json --stale --stale-by hash   # structure changed
xsm snapshots --store file:///var/lib/app/states --fail-if-stale checkout.json # CI / deploy gate

Only sqlite:/// and file:/// stores are readable by the CLI.

Restoring and verifying

Restore is SyncInterpreter::from_snapshot(...) or the persisted cycle; the snapshot’s machine_hash is checked against the machine before restore, so a record written for a different machine structure is refused rather than silently mis-restored. That check is your deploy gate: run xsm snapshots ... --stale --stale-by hash against the store before shipping a new machine version.

Migrating between machine versions

When a machine definition changes, SnapshotMigrator registers upgrade steps between versions (a breadth-first search finds the path), wired through PersistedOptions::on_version_mismatch as VersionMismatch::Migrate(f). The other policies are Error (the default) and Warn. from_state_ids builds a restorable layout-v4 snapshot from the bare state ids of an existing row, to move a hand-rolled state column onto a machine.

See the persistence guide for the full model.