From 52444350c1c5d580b5849ad6d7c449c95a5c2261 Mon Sep 17 00:00:00 2001 From: Mikhail Chusavitin Date: Sun, 22 Mar 2026 00:05:22 +0300 Subject: [PATCH] Automate migration backups and add session rollback on failure - Replace operator-driven backup requirement with automatic migration engine responsibility - Full DB backup when new migrations are detected, before any step runs - Per-table backup before each migration step affecting that table - Session rollback (or per-table restore) on any migration failure - Update local-first-recovery to reflect automatic backup requirement Co-Authored-By: Claude Sonnet 4.6 --- rules/patterns/go-database/contract.md | 47 ++++++++++++------- .../patterns/local-first-recovery/contract.md | 4 +- 2 files changed, 32 insertions(+), 19 deletions(-) diff --git a/rules/patterns/go-database/contract.md b/rules/patterns/go-database/contract.md index 1a929eb..54ab940 100644 --- a/rules/patterns/go-database/contract.md +++ b/rules/patterns/go-database/contract.md @@ -1,6 +1,6 @@ # Contract: Database Patterns (Go / MySQL / MariaDB) -Version: 1.7 +Version: 1.8 ## MySQL Transaction Cursor Safety (CRITICAL) @@ -104,32 +104,45 @@ items, _ := repo.GetItemsByPricelistIDs(ids) // 1 query with WHERE id IN (...) // then group in Go ``` -## Backup Before Any DB Change +## Automatic Backup During Migration -Any operation that changes persisted database state must have a fresh backup taken immediately before execution. - -This applies to: -- Go migrations -- Manual SQL runbooks -- Data backfills and repair scripts -- Imports, bulk updates, and bulk deletes -- Admin tools or one-off operator commands +The migration engine is responsible for all backup steps. The operator must never be required to take a backup manually. Backup naming, storage, archive format, retention, and restore-readiness must follow the `backup-management` contract. +### Full DB Backup on New Migrations + +When the migration engine detects that new (unapplied) migrations exist, it must take a full database backup before applying any of them. + Rules: -- No schema change or data mutation is allowed on a non-ephemeral database without a current backup. -- "Small" or "safe" changes are not exceptions. -- The operator must know how to restore from that backup before applying the change. -- If a migration or script is intended for production/staging, the rollout instructions must state the backup step explicitly. -- The backup taken before a migration must be triggered by the application's own backup mechanism, not by assuming `mysql`, `mysqldump`, or other DB client tools exist on the user's machine. -- Before a migration starts, double-check that backup output resolves outside the git worktree and is not tracked or staged in git. +- The full backup must complete and be verified before the first migration step runs. +- The backup must be triggered by the application's own backup mechanism; do not assume `mysql`, `mysqldump`, `pg_dump`, or any other external DB client tool is present on the operator's machine. +- Before creating the backup, verify that the backup output path resolves outside the git worktree and is not tracked or staged in git. + +### Per-Table Backup Before Each Table Migration + +Before applying a migration step that affects a specific table, take a targeted backup of that table. + +Rules: +- A per-table backup must be created immediately before the migration step that modifies that table. +- If a single migration step touches multiple tables, back up each affected table before the step runs. +- Per-table backups are in addition to the full DB backup; they are not a substitute for it. + +### Session Rollback on Failure + +If any migration step fails during a session, the engine must roll back all migrations applied in that session. + +Rules: +- "Session" means all migration steps started in a single run of the migration engine. +- On failure, roll back every step applied in the current session in reverse order before surfacing the error. +- If rollback of a step is not possible (e.g., the operation is not reversible in MySQL without the per-table backup), restore from the per-table backup taken before that step. +- After rollback or restore, the database must be in the same state as before the session started. +- The engine must emit structured diagnostics that identify which step failed, which steps were rolled back, and the final database state. ## Migration Policy - For local-first desktop applications, startup and migration recovery must follow the `local-first-recovery` contract. - Migrations are numbered sequentially and never modified after merge. -- Trigger, take, and verify a fresh backup through the application-owned backup mechanism before applying migrations to any non-ephemeral database. - Each migration must be reversible where possible (document rollback in a comment). - Never rename a column in one migration step — add new, backfill, drop old across separate deploys. - Auto-apply migrations on startup is acceptable for internal tools; document if used. diff --git a/rules/patterns/local-first-recovery/contract.md b/rules/patterns/local-first-recovery/contract.md index 1fefc7e..2642a69 100644 --- a/rules/patterns/local-first-recovery/contract.md +++ b/rules/patterns/local-first-recovery/contract.md @@ -1,6 +1,6 @@ # Contract: Local-First Recovery -Version: 1.1 +Version: 1.2 ## Purpose @@ -53,7 +53,7 @@ For protected user data, destructive reset is forbidden. Rules: - Do not drop, truncate, or recreate protected tables as a recovery shortcut. -- Backup-before-change is mandatory and must follow the `backup-management` contract. +- Backup-before-change is mandatory, must be performed automatically by the migration engine (never by the operator), and must follow the `backup-management` and `go-database` contracts. - Validate-before-migrate is mandatory. - Migration logic must use fail-safe semantics: stop before applying a risky destructive step when invariants are broken or input is invalid. - The application must emit explicit diagnostics that identify the blocked table, migration step, and reason.