P3-A: Add account_id to audit_logs model + migration (backfill via user_id → users.account_id). log_audit() gains optional account_id param with fallback SELECT to avoid churn across 40 call sites. P3-B: Add account_id to tree_shares model + migration (backfill via created_by → users.account_id). TreeShare constructor updated in trees.py. P3-C: Enable RLS on 6 remaining tables: step_ratings, step_usage_log, target_lists, session_shares, audit_logs, tree_shares. P3-D: Drop team_id from target_lists — endpoint, schema, and model now use account_id as the sole isolation key. P3-E: Append Phase 3 RLS isolation tests for all 6 tables. test_target_lists.py: fix cross-account test to use Account model (not Team) and set account_id on new User. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
52 lines
1.7 KiB
Python
52 lines
1.7 KiB
Python
"""Add account_id to audit_logs and backfill via user_id.
|
|
|
|
Revision ID: 2a9056eddd90
|
|
Revises: 70a5dd746e83
|
|
Create Date: 2026-04-11 00:00:00.000000
|
|
"""
|
|
from typing import Sequence, Union
|
|
from alembic import op
|
|
import sqlalchemy as sa
|
|
|
|
revision: str = '2a9056eddd90'
|
|
down_revision: Union[str, None] = '70a5dd746e83'
|
|
branch_labels: Union[str, Sequence[str], None] = None
|
|
depends_on: Union[str, Sequence[str], None] = None
|
|
|
|
|
|
def upgrade() -> None:
|
|
op.add_column('audit_logs', sa.Column('account_id', sa.UUID(), nullable=True))
|
|
op.create_foreign_key(
|
|
'fk_audit_logs_account_id', 'audit_logs', 'accounts',
|
|
['account_id'], ['id'], ondelete='CASCADE',
|
|
)
|
|
|
|
# Backfill: derive from the acting user's account
|
|
op.execute("""
|
|
UPDATE audit_logs al
|
|
SET account_id = u.account_id
|
|
FROM users u
|
|
WHERE al.user_id = u.id
|
|
AND u.account_id IS NOT NULL
|
|
AND al.account_id IS NULL
|
|
""")
|
|
|
|
result = op.get_bind().execute(
|
|
sa.text("SELECT COUNT(*) FROM audit_logs WHERE account_id IS NULL")
|
|
)
|
|
count = result.scalar()
|
|
if count > 0:
|
|
raise RuntimeError(
|
|
f"ROLLBACK: {count} audit_logs rows have NULL account_id after backfill. "
|
|
"All audit log entries must have an associated user with an account."
|
|
)
|
|
|
|
op.alter_column('audit_logs', 'account_id', nullable=False)
|
|
op.create_index('ix_audit_logs_account_id', 'audit_logs', ['account_id'])
|
|
|
|
|
|
def downgrade() -> None:
|
|
op.drop_index('ix_audit_logs_account_id', table_name='audit_logs')
|
|
op.drop_constraint('fk_audit_logs_account_id', 'audit_logs', type_='foreignkey')
|
|
op.drop_column('audit_logs', 'account_id')
|