Files
leocrm/alembic/versions/0148_plugin_migration_hash.py
T
Agent Zero fd4a1ec4ce
Check Cross-Plugin Imports / check (push) Waiting to run
fix(migrations): F40 (Astra P1) — Plugin-Migrationen tracken SHA-256-Content-Hash, Drift wird sichtbar
Vorher: Der Migration-Runner trackte Migrationen nur per DATEINAMEN —
eine nachtraeglich geaenderte, bereits angewandte Migration blieb
unbemerkt (genau die #389-Bugklasse: kaputte Migration wurde gefixt,
Runner skippte still, weil der Dateiname schon getrackt war).

Fix:
- Migration 0148: content_hash-Spalte (SHA-256, 64 Zeichen) in
  plugin_migrations + Index
- PluginMigration-Modell: content_hash-Feld
- run_migration: speichert den Hash des angewandten SQL-Inhalts
- run_all_migrations: vergleicht bei bereits angewandten Migrationen
  den Hash und warnt LAUT bei Abweichung (F40 DRIFT-Warnung mit Plugin,
  Datei, recorded/current-Hash) — Skip bleibt idempotent (kein Deploy-
  Bruch bei legitimen Reparaturen), aber Drift ist ab JETZT sichtbar

Abnahme (Astra): Eine veraenderte angewandte Migration wird erkannt —
erfuellt (Drift-Warnung im Runner-Log; #389 haette so beim naechsten
Start aufgefallen).

Verifikation: Syntax OK, ruff clean, alembic heads = 0148.
2026-09-18 13:01:25 +02:00

42 lines
1.1 KiB
Python

"""Add content_hash to plugin_migrations (F40/Astra).
The migration runner previously tracked migrations by FILENAME only —
editing an already-applied migration stayed unnoticed (the #389 bug
class: a broken migration was fixed, but the runner silently skipped
it because the filename was already tracked).
Now every applied migration records the SHA-256 of its SQL content.
On subsequent runs the runner compares hashes and logs a loud warning
when an applied migration was modified (repaired) — the skip stays
idempotent, but drift becomes VISIBLE.
Revision ID: 0148
Revises: 0147
"""
import sqlalchemy as sa
from alembic import op
revision = "0148"
down_revision = "0147"
branch_labels = None
depends_on = None
def upgrade() -> None:
op.add_column(
"plugin_migrations",
sa.Column("content_hash", sa.String(64), nullable=True),
)
op.create_index(
"ix_plugin_migrations_content_hash",
"plugin_migrations",
["content_hash"],
)
def downgrade() -> None:
op.drop_index("ix_plugin_migrations_content_hash", table_name="plugin_migrations")
op.drop_column("plugin_migrations", "content_hash")