Skip to content

Commit 5a80ed2

Browse files
THIOYE97claude
andcommitted
Amorçage d'une base neuve : caler sur le schéma, puis migrer
init_db faisait « alembic stamp head » sur une base fraîchement peuplée du schéma canonique. Or ce schéma est antérieur à m6d : il ne contient ni ubo_declarations, ni ubo_documents, ni offshore_records, ni offshore_relations, ni press_search_cache. Caler sur la tête déclarait donc ces migrations appliquées alors que leurs tables n'existaient pas. Conséquence : toute reconstruction d'environnement — test, secours, ou reprise après incident — démarrait amputée, et Alembic la disait à jour. L'anomalie ne se serait vue qu'à l'usage, sur une fonction qui aurait dû marcher. Le calage se fait désormais sur la révision que REPRÉSENTE le schéma (m6b_compliance_audit, déterminée par comparaison : compliance_events présente, alert_status encore à ses anciennes valeurs), puis les migrations suivantes sont appliquées. Un contrôle final vérifie la présence effective des tables postérieures et échoue sinon : une base incomplète ne doit pas se présenter comme saine. Découvert en montant l'environnement de test, dont le pré-déploiement a échoué. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 88add62 commit 5a80ed2

1 file changed

Lines changed: 45 additions & 4 deletions

File tree

scripts/init_db.py

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,19 @@
2828
ROOT = Path(__file__).resolve().parents[1]
2929
BASELINE = ROOT / "db" / "baseline_schema.sql"
3030

31+
# Révision Alembic que représente le schéma canonique. Déterminée par
32+
# comparaison : le schéma contient `compliance_events` (m6b) mais l'énumération
33+
# `alert_status` y porte encore ses anciennes valeurs, donc il précède m6d.
34+
# À mettre à jour si le fichier db/baseline_schema.sql est régénéré.
35+
BASELINE_REVISION = "m6b_compliance_audit"
36+
37+
# Tables introduites par des migrations POSTÉRIEURES au schéma canonique. Leur
38+
# présence atteste que la montée de version a bien eu lieu.
39+
POST_BASELINE_TABLES = (
40+
"ubo_declarations", "ubo_documents", "offshore_records",
41+
"offshore_relations", "press_search_cache",
42+
)
43+
3144
APP_ROLE = "screening_app"
3245
BYPASS_ROLE = "auth_bypass_rls"
3346

@@ -107,11 +120,39 @@ def main() -> int:
107120
print(f"⚠ échec sur : {s[:80]}\n {str(e)[:160]}", file=sys.stderr)
108121
print(f"✓ schéma appliqué ({len(stmts)} instructions)")
109122

110-
# 3) alembic stamp head -------------------------------------------------
123+
# 3) calage Alembic puis montée jusqu'à la tête -------------------------
124+
#
125+
# On cale sur la révision que REPRÉSENTE le schéma canonique, puis on
126+
# applique les migrations suivantes. Caler directement sur la tête
127+
# déclarerait appliquées des migrations dont les tables n'existent pas :
128+
# une base neuve démarrerait alors sans `ubo_declarations`,
129+
# `offshore_records`, `offshore_relations` ni `press_search_cache`, et
130+
# Alembic la dirait à jour. L'anomalie ne se verrait qu'à l'usage.
111131
env = {**os.environ, "DATABASE_URL": url}
112-
r = subprocess.run([sys.executable, "-m", "alembic", "stamp", "head"], env=env,
113-
cwd=str(ROOT), capture_output=True, text=True)
114-
print("✓ alembic stamp head" if r.returncode == 0 else f"⚠ alembic stamp: {r.stderr[-200:]}")
132+
133+
def _alembic(*args) -> bool:
134+
r = subprocess.run([sys.executable, "-m", "alembic", *args], env=env,
135+
cwd=str(ROOT), capture_output=True, text=True)
136+
if r.returncode != 0:
137+
print(f"⚠ alembic {' '.join(args)} : {r.stderr[-300:]}", file=sys.stderr)
138+
return r.returncode == 0
139+
140+
if _alembic("stamp", BASELINE_REVISION):
141+
print(f"✓ alembic stamp {BASELINE_REVISION}")
142+
if _alembic("upgrade", "head"):
143+
print("✓ alembic upgrade head")
144+
145+
# Contrôle : les tables introduites APRÈS le schéma canonique doivent
146+
# exister. Sans ce garde-fou, une base incomplète se présenterait comme
147+
# saine — c'est précisément ce qui rendait toute reconstruction illusoire.
148+
with eng.begin() as c:
149+
manquantes = [t for t in POST_BASELINE_TABLES if not c.execute(text(
150+
"SELECT COUNT(*) FROM information_schema.tables "
151+
"WHERE table_schema='public' AND table_name=:t"), {"t": t}).scalar()]
152+
if manquantes:
153+
print(f"✗ tables absentes après migration : {', '.join(manquantes)}", file=sys.stderr)
154+
return 1
155+
print("✓ schéma complet vérifié")
115156

116157
# 4) seed référentiel + admin ------------------------------------------
117158
from app.core.config import settings

0 commit comments

Comments
 (0)