refactor: remove _ensure_*() safety net, keep only Alembic

- Simplified init_db() to only call Base.metadata.create_all() on all
  three engines — no more _ensure_*() inline additive migrations
- run_alembic_migrations() now raises on failure (no safety net to
  fall back to)
- All schema changes (add/drop/rename columns, data migrations) must
  go through Alembic. create_all() handles new tables only.

@REJECTED _ensure_*() inline migrations removed because they:
  - Duplicated Alembic logic without versioning
  - Created hidden schema drift (columns existed in DB but had no
    corresponding Alembic migration)
  - Made audits and fresh DB provisioning unreliable
This commit is contained in:
2026-05-26 19:02:59 +03:00
parent 9337a4cecd
commit eda346979b
2 changed files with 24 additions and 30 deletions

View File

@@ -127,12 +127,13 @@ def ensure_initial_admin_user() -> None:
db.close()
# #endregion ensure_initial_admin_user
# #region run_alembic_migrations [C:2] [TYPE Function]
# @BRIEF Applies pending Alembic migrations before application initialization.
# @POST All Alembic migrations up to 'head' are applied to DATABASE_URL.
# @SIDE_EFFECT Executes ALTER TABLE / CREATE TABLE / etc. via Alembic migration chain.
# @RATIONALE Keeps DB schema in sync automatically at container startup —
# eliminates manual `alembic upgrade head` and drift between
# inline _ensure_*() migrations and Alembic migration files.
# @BRIEF Applies all pending Alembic migrations against DATABASE_URL.
# @POST All Alembic migrations up to 'head' are applied.
# @SIDE_EFFECT Executes ALTER TABLE / CREATE TABLE via Alembic chain.
# @RATIONALE Single source of truth for schema changes. All column additions,
# drops, renames, and data migrations go through Alembic.
# init_db() runs after to create any tables that don't have
# a standalone Alembic migration yet.
def run_alembic_migrations() -> None:
with belief_scope("run_alembic_migrations"):
try:
@@ -142,29 +143,28 @@ def run_alembic_migrations() -> None:
logger.reason("Alembic migrations applied up to head")
except Exception as exc:
logger.explore(
"Alembic migration failed — app may have schema drift",
"Alembic migration failed — check migration chain or alembic.ini",
extra={"error": str(exc)},
)
# Don't crash — init_db() + _ensure_*() serve as safety net
raise
# #endregion run_alembic_migrations
# #region startup_event [C:3] [TYPE Function]
# @BRIEF Handles application startup tasks: Alembic migrations, schema init, admin bootstrap, scheduler.
# @BRIEF Handles application startup: Alembic → create_all → admin bootstrap scheduler.
# @RELATION CALLS -> [run_alembic_migrations]
# @RELATION CALLS -> [init_db]
# @PRE None.
# @POST Schema is up-to-date (Alembic + inline safety net), admin user exists if configured, scheduler is started.
# @POST Schema is up-to-date via Alembic, missing tables created, admin exists, scheduler started.
@app.on_event("startup")
async def startup_event():
seed_trace_id()
with belief_scope("startup_event"):
ensure_encryption_key()
# 1. Alembic — applies complex migrations (add/drop/rename columns, data migrations)
# 1. Alembic — applies ALL pending migrations (add/drop/rename columns, data migrations)
run_alembic_migrations()
# 2. init_db — creates missing tables + inline _ensure_*() additive safety net
# 2. init_db — creates new tables via create_all() (does NOT alter existing ones)
init_db()
ensure_initial_admin_user()
scheduler = get_scheduler_service()