From 6034c08badc67fb5997b64f4a45100ae70f313ea Mon Sep 17 00:00:00 2001 From: "n.siegfried" Date: Tue, 1 Sep 2026 15:35:36 -0700 Subject: [PATCH] Alembic runs one transaction PER MIGRATION, not one for the whole chain The 2026-08-21 crash at material_items printed 'Running upgrade' lines for location_nodes and wp_files and then rolled all three back together - env.py wrapped the entire run in a single transaction. The repair that followed trusted those lines: material_items was hand-created, the version stamped to head, and production ran for two days missing two tables it claimed to have. Found 2026-08-23 when the locations import 500'd on UndefinedTable. transaction_per_migration=True makes the log truthful: a crash keeps every step that completed, and a stamp-to-head repair after a crash repairs ONE migration, not an unknowable prefix of the chain. Verified: the full chain still applies on a fresh scratch SQLite; the offline --sql render is unchanged. The production surgery (creating the two rolled-back tables from the offline postgres render) is recorded in the session; no version stamp is needed there - it is already, now truthfully, at head. Items: BL-027's class, third finding; env.py infrastructure. Co-Authored-By: Claude Fable 5 --- server/alembic/env.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/server/alembic/env.py b/server/alembic/env.py index 23e8ad4..404bd1b 100644 --- a/server/alembic/env.py +++ b/server/alembic/env.py @@ -52,6 +52,13 @@ def run_migrations_online() -> None: connection=connection, target_metadata=target_metadata, compare_type=True, + # Each migration commits on its own. One transaction for the WHOLE + # run meant a crash at step N rolled back steps 1..N-1 while their + # "Running upgrade" lines stayed on screen claiming they ran - the + # 2026-08-21 outage's stamp-to-head repair trusted those lines and + # left production missing two tables (found 2026-08-23 when the + # locations import 500'd on a table that "had been created"). + transaction_per_migration=True, ) with context.begin_transaction(): context.run_migrations()