"""add revoked_tokens table (refresh-token revocation/rotation)

Revision ID: 0003
Revises: 0002
Create Date: 2026-09-08

StockLab overhaul, final engineering pass, Part A4 — refresh-token revocation/rotation (logout,
invalidation) without an incompatible auth rewrite. Purely additive: one new table
(`app/models/governance.py::RevokedToken`), no existing table touched, no backfill needed (no
prior tokens carried a `jti` claim to revoke retroactively — revocation only applies going
forward, to tokens minted after this deploy). In line with docs/DEPLOYMENT.md §5's
"additive where possible" migration policy.

NOT RUN in the build environment this migration was authored in — `alembic`/`sqlalchemy` are not
installed there (no network access to install them; see docs/TEST_REPORT.md §2/§6). Verified with
`python3 -m py_compile` only. Run `alembic upgrade head` against a real Postgres instance and
confirm the resulting `revoked_tokens` table matches `app/models/governance.py::RevokedToken`
before trusting this file in production, per the same disclosure given for 0001/0002.
"""
from alembic import op
import sqlalchemy as sa

revision = "0003"
down_revision = "0002"
branch_labels = None
depends_on = None

UUID = sa.String(36)


def upgrade() -> None:
    op.create_table(
        "revoked_tokens",
        sa.Column("id", UUID, primary_key=True),
        sa.Column("jti", sa.String(64), nullable=False, unique=True),
        sa.Column("user_id", sa.String(64), nullable=True),
        sa.Column("token_type", sa.String(16), nullable=False, server_default="refresh"),
        sa.Column("revoked_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
        sa.Column("expires_at", sa.DateTime(timezone=True), nullable=False),
        sa.Column("reason", sa.String(32), nullable=True),
    )
    op.create_index("ix_revoked_tokens_jti", "revoked_tokens", ["jti"], unique=True)
    op.create_index("ix_revoked_tokens_user_id", "revoked_tokens", ["user_id"])
    op.create_index("ix_revoked_tokens_expires_at", "revoked_tokens", ["expires_at"])


def downgrade() -> None:
    op.drop_index("ix_revoked_tokens_expires_at", table_name="revoked_tokens")
    op.drop_index("ix_revoked_tokens_user_id", table_name="revoked_tokens")
    op.drop_index("ix_revoked_tokens_jti", table_name="revoked_tokens")
    op.drop_table("revoked_tokens")
