Files
2026-09-08 11:00:05 +05:30

47 lines
1.4 KiB
Python

"""C4A — grants can expire
`user_roles.expires_at`, nullable. Null means permanent, which is every existing
row, so this migration changes nobody's authority.
Temporary elevation, contractor access and just-in-time grants all need this, and
all three are ordinary enterprise asks. Without it the only way to end a grant is
to remember to revoke it, and "remember to" is not an access control.
Expired rows are **not** deleted. They stay, and they become history — which is
what C4B reads to answer "who had access in March". A swept-away row answers
nothing.
Revision ID: c4_0_grant_expiry
Revises: c1_1_user_roles_tenant_id
"""
import sqlalchemy as sa
from alembic import op
revision = "c4_0_grant_expiry"
down_revision = "c1_1_user_roles_tenant_id"
branch_labels = None
depends_on = None
def upgrade() -> None:
op.add_column(
"user_roles",
sa.Column("expires_at", sa.DateTime(timezone=True), nullable=True),
)
# Partial: only live grants are ever queried by expiry, and indexing the
# permanent ones (which is all of them today) would be indexing a column
# that is null for every row.
op.create_index(
"ix_user_roles_expires_at",
"user_roles",
["expires_at"],
postgresql_where=sa.text("expires_at IS NOT NULL"),
)
def downgrade() -> None:
op.drop_index("ix_user_roles_expires_at", table_name="user_roles")
op.drop_column("user_roles", "expires_at")