Two integration tests in test_integration_backup_rollback.py drive
grimoire.restore.run on the scheduled backup's tar, written from a full
export of this instance:
- without --yes it exits 4 and nothing changes;
- with --yes it takes the safety backup, then puts back the edited and the
deleted task and removes the new one.
The file-restore test that expected a refusal "because the database has
accounts" now names the reason it actually refuses: on a live instance, a
backup that records no instance id can't be rolled back to.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
One transaction:
- Refuse unless the backup is a full one and, on a live instance, carries
this instance's id. A fresh instance (no records, at most the restoring
account) takes any full backup and adopts its id.
- Clear first: search vectors and the CSS consumer map (derived); every
backed-up table nothing outside the backup points at, whole; the settings
the backup carries; and the identity-table rows the backup lacks. A share
granted by a removed user goes before the user.
- Write the backup through the same _build_* helpers as the copy-in restore,
now _load_v2(keep_ids=...). Identity tables (users, projects, milestones,
notes, rulebooks, topics, rules, systems, design systems, code shapes,
family decisions) go through _place. That keeps the backup's id and
merges, so API keys, shares and forge connections of a surviving row stay.
- Reset id sequences past the restored max, and sign every session out.
reindex_after_restore runs the four embedding backfills afterwards. A unit
guard checks that no row kept for outside references cascades from a table
replaced whole. Integration tests cover edits, deletions and new rows put
back, a surviving API key, sessions ending, fresh ids afterwards, foreign or
id-less backups refused, a user export refused, a failure part-way changing
nothing, and a fresh instance adopting the id.
Milestone 582, step 2.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>