"""offboarding_processes: per-channel session-enforcement proof column. The offboarding force-logout path (`_push_force_logout`) previously returned only a bool and swallowed per-vendor failures, so IT had no durable record of whether a leaver's Google/Feishu session + Google account were actually cut — the exact question the P0051 offboarding incident surfaced (Google `users.signOut` was silently 403-ing on a scope gap for weeks with nobody the wiser). This adds a nullable JSON column `session_enforcement` to record the outcome of each identity-plane cutoff channel (google_session / google_account / feishu_session) so the offboarding detail page and the person-detail 离职 tab can display proof, and a failed channel can be re-run and re-recorded. Nullable, no backfill (existing rows simply have no recorded enforcement snapshot until the next force-logout / re-run). Revision ID: 383 Revises: 382 """ from __future__ import annotations import sqlalchemy as sa from alembic import op revision = "383" down_revision = "382" branch_labels = None depends_on = None TABLE = "offboarding_processes" COLUMN = "session_enforcement" def _column_exists(bind, table: str, column: str) -> bool: return bool( bind.execute( sa.text( "SELECT 1 FROM information_schema.columns " "WHERE table_schema = DATABASE() " "AND table_name = :t AND column_name = :c" ), {"t": table, "c": column}, ).first() ) def upgrade() -> None: bind = op.get_bind() if not _column_exists(bind, TABLE, COLUMN): op.add_column(TABLE, sa.Column(COLUMN, sa.JSON(), nullable=True)) def downgrade() -> None: bind = op.get_bind() if _column_exists(bind, TABLE, COLUMN): op.drop_column(TABLE, COLUMN)