"""Enforce the two fixed Node Agent credential scopes. Revision ID: 20260830_0023 Revises: 20260828_0022 """ from collections.abc import Sequence import sqlalchemy as sa from alembic import op revision: str = "20260830_0023" down_revision: str | None = "20260828_0022" branch_labels: str | Sequence[str] | None = None depends_on: str | Sequence[str] | None = None _SCOPE_CONSTRAINTS = ( ("node_enrollments", "node.enroll", "ck_node_enrollment_scope"), ("node_credentials", "node.publish", "ck_node_credential_scope"), ) def _validate_existing_scopes() -> None: connection = op.get_bind() for table_name, expected_scope, _constraint_name in _SCOPE_CONSTRAINTS: table = sa.table(table_name, sa.column("scope", sa.String(64))) invalid_rows = connection.scalar( sa.select(sa.func.count()) .select_from(table) .where(sa.or_(table.c.scope.is_(None), table.c.scope != expected_scope)) ) if invalid_rows: raise RuntimeError( f"refusing node-scope migration: {table_name} contains " f"{invalid_rows} row(s) outside {expected_scope!r}" ) def upgrade() -> None: # Validate every table before changing either one. A malformed production row therefore aborts # the migration without leaving a partially hardened schema. _validate_existing_scopes() for table_name, expected_scope, constraint_name in _SCOPE_CONSTRAINTS: with op.batch_alter_table(table_name) as batch_op: batch_op.create_check_constraint( constraint_name, f"scope = '{expected_scope}'", ) def downgrade() -> None: for table_name, _expected_scope, constraint_name in reversed(_SCOPE_CONSTRAINTS): with op.batch_alter_table(table_name) as batch_op: batch_op.drop_constraint(constraint_name, type_="check")