"""Widen core_idempotency_records.scope 64 -> 128. Revision ID: 268 Revises: 267 Create Date: 2026-06-09 Why this exists --------------- ``IdempotencyService`` scopes a reservation by ``f"{audit_action}:{id}"``. The ``scope`` column was ``VARCHAR(64)``, which silently sufficed only because every scope in production happened to be short — until the leaver-blocked admin endpoints stretched it: leaver_blocked.force_bypass_grace_advance:{uuid} -> 78 chars leaver_blocked.execute_override:{uuid} -> 68 chars leaver_blocked.advance_now:{uuid} -> 63 chars (fits, barely) On TiDB (``STRICT_TRANS_TABLES``) an over-length INSERT raises ``1406 Data too long for column 'scope'`` — and that INSERT happens in ``IdempotencyService.lookup()`` BEFORE the endpoint's try/except, so it bubbles straight to the catch-all 500 handler ("An unexpected error occurred.") with **no audit row and no idempotency row** ever written. Symptom: clicking "强制立即推进" (force-bypass-grace) on the Leaver Blocked console always 500'd on prod; ``advance_now`` worked only because its scope was one character under the limit. The override-execute path (68 chars) was broken the same way. Fix: widen ``scope`` to ``VARCHAR(128)`` (matches the ``key`` column's width) so any reasonable ``{audit_action}:{uuid}`` fits with headroom. This is a pure column widen — no data migration, fully online on TiDB, and reversible only if no row already exceeds 64 chars (downgrade keeps this safe by leaving longer rows in place would fail strict mode, so the downgrade is best-effort and documented). """ from __future__ import annotations import sqlalchemy as sa from alembic import op revision = "268" down_revision = "267" branch_labels = None depends_on = None def upgrade() -> None: op.alter_column( "core_idempotency_records", "scope", existing_type=sa.String(length=64), type_=sa.String(length=128), existing_nullable=False, ) def downgrade() -> None: # Reversible only while no stored scope exceeds 64 chars. The widen # was motivated by scopes up to 78 chars, so a blind narrow would # truncate / fail under strict mode — purge the over-length rows # (idempotency reservations are short-lived cache entries; dropping # them only forces a fresh run, never corrupts business state). op.execute( "DELETE FROM core_idempotency_records WHERE CHAR_LENGTH(scope) > 64" ) op.alter_column( "core_idempotency_records", "scope", existing_type=sa.String(length=128), type_=sa.String(length=64), existing_nullable=False, )