Repository navigation
infra: textstack_v2 in backups, the pre-deploy dump, the restore drill and the health check; scripted baseline rollback - #1105
Merged
Merged
Conversation
…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>
# Conflicts: # .env.example # docs/STATUS.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
ADR-030's cost, paid:
textstack_v2is now dumped nightly (/db_v2.sqlto R2, beforeforget/checkso it is integrity-checked and inside the size guard), before every deploy, bymake backup, restored bymake restore-v2, restored and row-counted by the drill, and asserted byhealth-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 forbackup.yml,deploy.ymlandmake backup: existence query checked by exit status (a failedpsqlis an error, never "skipped"),pipefail, trailer check,.tmp→mv,.tmpremoved on failure; the V2 name read from.env'sV2_POSTGRES_DBin all three.backup.yml: V2 dump + R2 upload afterdb.sql, beforeforget/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 for0000+0001), each count failing the step on a psql error; "no V2 in this backup" is green only while the restored.envhasFEATURES_V2APIoff (case-insensitive); a V2 snapshot more than 24 h older than/db.sqlfails with the flag on.health-check.yml: thev2component of/health/readymust beokwhen it is notoff;behind/unreachable/unconfiguredalarm; a missing component warns.Makefile:backupdumps both;restorerefuses a V2 file and loads withON_ERROR_STOP;restore-v2 FILE=(refuses an existing V2, checks the file first);migrate-v2-drop CONFIRM=<db>behindmigrator-guard,run.sh droprefuses a name that is not itsPGDATABASE;DB_CONTAINERknob for throwaway runs.database/v2/README.md(rollback paragraph, backup table — note:docker compose down -vdoes not drop the bind-mounted data),docs/03-ops/backup.md,docs/claude/operations.md,.env.example, ADR-030 cost row.Tests / proof
No
.cstouched. 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 restorerefuses the V2 file;restore-v2with a typo path creates nothing; restore both → 17 legacy ledger rows / 59 tables, V2 ICUund, 26 tables counted; a dump cut before the lastCREATE TABLEand 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 withoutCONFIRM, refuses a wrong name, drops, andrun.sh uprecreates. Workflow steps were extracted and run with the container/user swapped;bash -non every run step, shellcheck-S warningon the script,run.shand 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-checkouton a runner,docker compose run migrator-v2 create|drop(shown viadocker execinto the container; same shell logic). Watch the next nightlybackup.ymland thenrestore-drill.ymlafter 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-v2on a bad path; exists-query failure read as "no database";make restoreaccepting a V2 dump; stale V2 snapshots; V2 uploaded afterrestic check; three copies of the dump block; case-sensitive flag grep).make backuphas nopipefail(an empty.gzon a failedpg_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