"""Cache the warn-only reconciler drift on the endpoint state. Adds endpointstates.driftjson: a per-asset JSON snapshot of the monitors a site policy (or the shipped bundle) expects to be running on an endpoint but which the latest agent report does not show as running. It is computed server-side on every agent check-in and overwritten in place, the same ledger pattern the other state snapshots (servicesjson, portsjson) already use - one current row per asset, no history. Warn-only: nothing here is auto-remediated, it exists so an operator can see the drift and intervene manually. Nullable so an endpoint that has never checked in since the column landed reads as "never computed" rather than "no drift". Idempotent: the column-add checks the live schema first, so a db.create_all() site (which built the column from the model already) and a re-run both stay clean on strict MySQL 8. """ from alembic import op import sqlalchemy as sa # revision identifiers, used by Alembic. revision = 'endpoints0008drift' down_revision = 'endpoints0007shippedpolicy' branch_labels = None depends_on = None def _has_column(table_name, column_name): from sqlalchemy import inspect inspector = inspect(op.get_bind()) if not inspector.has_table(table_name): return False return any(col['name'] == column_name for col in inspector.get_columns(table_name)) def upgrade(): if _has_column('endpointstates', 'driftjson'): return op.add_column( 'endpointstates', sa.Column('driftjson', sa.Text(length=16777215), nullable=True)) def downgrade(): if not _has_column('endpointstates', 'driftjson'): return op.drop_column('endpointstates', 'driftjson')