3.1 KiB
Phase 9 migration and rollback runbook
This workflow is local and operator-driven. It adds no HTTP endpoint and does not start, stop, or reconfigure a service.
Safety rules
- Stop the API before restore activation. Managed open database connections cause activation to fail closed.
- Use only an explicitly selected legacy file. The command never searches for or reads a default/root
config.json. - Preview output contains only
credentialPresent; legacy password values are neither printed nor persisted. Instances with passwords remain disabled for password authentication (auth_mode=none) until the separate secret activation workflow succeeds. - Keep snapshots and candidates on trusted local storage. Files are created mode
0600.
Preview and confirm legacy import
corepack pnpm --filter @multi-simadmin/api migration -- \
preview --source /explicit/path/legacy.json --database /local/data/app.sqlite
Review every planned instance and ensure conflict is zero. Copy the printed planDigest, then confirm exactly that plan:
corepack pnpm --filter @multi-simadmin/api migration -- \
confirm --source /explicit/path/legacy.json --database /local/data/app.sqlite \
--plan-digest <64-hex-plan-digest>
Confirmation safely re-reads the file and database state. A changed file identity, changed source bytes, changed plan, or database conflict aborts without partial import. Repeating preview/confirm is idempotent. Each confirmation writes a release.legacy-import.* reconciliation record in app_settings, including only digests, counts, and pending-secret status.
Snapshot and restore foundation
Use backupDatabase(openManagedDatabase, snapshotPath) while the database is open. It uses SQLite's online backup API, so committed WAL pages are included in one consistent snapshot. The temporary snapshot is integrity checked, synced, and atomically renamed.
Restore is intentionally two-stage:
prepareRestoreCandidate(snapshotPath, candidatePath)copies to a separate candidate, verifies SQLite integrity, syncs it, and returns its SHA-256 digest.- Inspect/drill the candidate and explicitly retain that digest.
- Close all managed live connections.
activateRestoreCandidate(livePath, candidateArtifact, confirmedDigest)rechecks candidate bytes and requires the exact digest before atomic replacement. Stale WAL/SHM sidecars are removed only after replacement.
Never activate directly from an unverified snapshot.
Deterministic rollback drill
Run runRollbackDrill(snapshotPath, freshCandidatePath). It never activates the candidate. The returned record contains snapshot digest, integrity status, deterministic schema digest, and per-table row counts—no row values or secrets. Repeating against the same snapshot must produce the same schema digest and counts.
After an actual rollback, reconcile operationally by comparing the drill record and expected release digest, then record the operator decision in the deployment/release system. Secret material is outside SQLite and must be reconciled separately through SecretStore; do not copy secrets into logs or records.