Skip to content

infra: textstack_v2 in backups, the pre-deploy dump, the restore drill and the health check; scripted baseline rollback - #1105

Merged
mrviduus merged 22 commits into
mainfrom
v2/1043-v2-in-backups
Nov 3, 2026
Merged

mrviduus merged 22 commits into
mainfrom
v2/1043-v2-in-backups

Conversation

@mrviduus

@mrviduus mrviduus commented Nov 2, 2026

Copy link
Copy Markdown
Owner

Summary

ADR-030's cost, paid: textstack_v2 is now dumped nightly (/db_v2.sql to R2, before forget/check so it is integrity-checked and inside the size guard), before every deploy, by make backup, restored by make restore-v2, restored and row-counted by the drill, and asserted by health-check.yml. A missing V2 database is skipped with a log line and never fails the legacy backup or the deploy (the 2026-10-30 rule). make migrate-v2-drop CONFIRM=<db> is the baseline's scripted rollback.

Fixes #1043 (the owner deferred the "backups before any V2 migration on prod" rule on 2026-11-02 while V2 holds nothing; this PR is the work itself, to be in place before M3.1's ETL rehearsal).

Changes

  • infra/scripts/pg-dump-checked.sh <db> <out> — one dump path for backup.yml, deploy.yml and make backup: existence query checked by exit status (a failed psql is an error, never "skipped"), pipefail, trailer check, .tmp → mv, .tmp removed on failure; the V2 name read from .env's V2_POSTGRES_DB in all three.
  • backup.yml: V2 dump + R2 upload after db.sql, before forget/check; a V2 failure is isolated and reported by a final "V2 backup failed" step (job red, email) while every legacy step still runs; cleanup keeps two of each V2 kind; V2 files are prefixed by database name so legacy patterns match exactly what they matched before.
  • deploy.yml: separate "Pre-deploy backup (V2)" step, continue-on-error; the legacy step has zero deleted lines.
  • restore-drill.yml: restores V2 (run.sh create — ICU, template0) and counts rows in every table a migration in the restored ledger creates (26 for 0000+0001), each count failing the step on a psql error; "no V2 in this backup" is green only while the restored .env has FEATURES_V2API off (case-insensitive); a V2 snapshot more than 24 h older than /db.sql fails with the flag on.
  • health-check.yml: the v2 component of /health/ready must be ok when it is not off; behind/unreachable/unconfigured alarm; a missing component warns.
  • Makefile: backup dumps both; restore refuses a V2 file and loads with ON_ERROR_STOP; restore-v2 FILE= (refuses an existing V2, checks the file first); migrate-v2-drop CONFIRM=<db> behind migrator-guard, run.sh drop refuses a name that is not its PGDATABASE; DB_CONTAINER knob for throwaway runs.
  • Docs: database/v2/README.md (rollback paragraph, backup table — note: docker compose down -v does not drop the bind-mounted data), docs/03-ops/backup.md, docs/claude/operations.md, .env.example, ADR-030 cost row.

Tests / proof

No .cs touched. Proved locally on two throwaway Postgres containers: backup with V2 absent (skipped, exit 0) and present (two files); a wrong container / wrong role → ::error::, exit 1; make restore refuses the V2 file; restore-v2 with a typo path creates nothing; restore both → 17 legacy ledger rows / 59 tables, V2 ICU und, 26 tables counted; a dump cut before the last CREATE TABLE and a dropped table both fail the drill count; a 3-week-old V2 snapshot fails with the flag on and warns with it off; the drop target refuses without CONFIRM, refuses a wrong name, drops, and run.sh up recreates. Workflow steps were extracted and run with the container/user swapped; bash -n on every run step, shellcheck -S warning on the script, run.sh and every changed step, Ruby YAML parse of the four workflows.

Not provable locally: real restic against R2 (upload order and the recorded failure shown with a fake restic), the drill's sparse-checkout on a runner, docker compose run migrator-v2 create|drop (shown via docker exec into the container; same shell logic). Watch the next nightly backup.yml and then restore-drill.yml after merging.

Rollback plan

Revert the merge commit; the legacy backup/deploy/drill/health paths are unchanged in behaviour.

Notes

  • /code-review high: 8 findings, all fixed (swallowed psql failures in the drill count; restore-v2 on a bad path; exists-query failure read as "no database"; make restore accepting a V2 dump; stale V2 snapshots; V2 uploaded after restic check; three copies of the dump block; case-sensitive flag grep).
  • Pre-existing, not changed: the legacy line of make backup has no pipefail (an empty .gz on a failed pg_dump); deploy.yml's "Snapshot SSG dir" step does not parse under macOS bash 3.2 (fine on the Linux runner).

🤖 Generated with Claude Code

mrviduus and others added 22 commits November 2, 2026 10:34
…ore-v2 (#1043)

V2 dump after legacy, skipped while the database does not exist, never
stops a legacy step. run.sh create: the empty ICU database restore-v2 loads.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ever fatal (#1043)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… migrated table (#1043)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e flag is on (#1043)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
run.sh drop <name> refuses unless <name> = PGDATABASE; Make needs CONFIRM=.
README: rollback paragraph and the V2 backup matrix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ion file name from both (#1043)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…me elsewhere (#1043)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ocs, .env.example, ADR-030 (#1043)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…as absent (#1043)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…name from .env everywhere (#1043)

infra/scripts/pg-dump-checked.sh: existence query checked before emptiness,
pipefail, trailer check, .tmp -> mv (tmp removed on failure).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s under pipefail (#1043)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…#1043)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s failure recorded, not fatal (#1043)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…#1043)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lowed, as the Api does (#1043)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… fails with the flag on (#1043)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…1043)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…nd the trailer check, no empty .gz behind a success line

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@mrviduus
mrviduus merged commit 03cebd7 into main Nov 3, 2026
11 checks passed
@mrviduus
mrviduus deleted the v2/1043-v2-in-backups branch November 3, 2026 04:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

V2 database must be in backups, the pre-deploy dump, the restore drill and health checks

1 participant