Files
leocrm/alembic/versions/0092_outbox_dlq.py
T

78 lines
2.8 KiB
Python
Raw Normal View History

"""Add DLQ columns to event_outbox and fix consumer_inbox RLS policy.
Phase 5: Dead-Letter-Queue support.
- Adds error_message TEXT and failed_at TIMESTAMPTZ to event_outbox
- Adds partial index for failed events
- Fixes consumer_inbox RLS policy (previous 0085 policy referenced
tenant_id column which does not exist on consumer_inbox; the
correct policy uses the event_id FK to event_outbox.tenant_id)
Revision ID: 0092
Revises: 0091
"""
from __future__ import annotations
from alembic import op
revision = "0092"
down_revision = "0091"
branch_labels = None
depends_on = None
def upgrade() -> None:
# 1. Add DLQ columns to event_outbox
op.execute(
"ALTER TABLE IF EXISTS event_outbox "
"ADD COLUMN IF NOT EXISTS error_message TEXT"
)
op.execute(
"ALTER TABLE IF EXISTS event_outbox "
"ADD COLUMN IF NOT EXISTS failed_at TIMESTAMPTZ"
)
# 2. Partial index for efficient failed-event queries
op.execute(
"CREATE INDEX IF NOT EXISTS ix_outbox_failed "
"ON event_outbox (status, failed_at) WHERE status = 'failed'"
)
# 3. Fix consumer_inbox RLS policy
# Migration 0085 created a policy using tenant_id, but consumer_inbox
# has no tenant_id column. Drop the broken policy and create one
# that follows the same pattern as outbox_deliveries (0075): use the
# event_id FK to check event_outbox.tenant_id.
op.execute(
"DROP POLICY IF EXISTS consumer_inbox_tenant_isolation ON consumer_inbox"
)
op.execute("DROP POLICY IF EXISTS tenant_isolation ON consumer_inbox")
op.execute("ALTER TABLE consumer_inbox ENABLE ROW LEVEL SECURITY")
op.execute("ALTER TABLE consumer_inbox FORCE ROW LEVEL SECURITY")
op.execute(
"CREATE POLICY consumer_inbox_tenant_isolation ON consumer_inbox "
"FOR ALL TO crm_api, crm_worker "
"USING (EXISTS (SELECT 1 FROM event_outbox "
"WHERE event_outbox.id = consumer_inbox.event_id "
"AND event_outbox.tenant_id = "
"NULLIF(current_setting('app.current_tenant_id', true), '')::uuid)) "
"WITH CHECK (EXISTS (SELECT 1 FROM event_outbox "
"WHERE event_outbox.id = consumer_inbox.event_id "
"AND event_outbox.tenant_id = "
"NULLIF(current_setting('app.current_tenant_id', true), '')::uuid))"
)
# Ensure grants are in place
op.execute(
"GRANT SELECT, INSERT, UPDATE, DELETE ON consumer_inbox TO crm_api, crm_worker"
)
def downgrade() -> None:
op.execute("DROP INDEX IF EXISTS ix_outbox_failed")
op.execute("ALTER TABLE event_outbox DROP COLUMN IF EXISTS failed_at")
op.execute("ALTER TABLE event_outbox DROP COLUMN IF EXISTS error_message")
# Restore the broken policy state (consumer_inbox RLS remains enabled)
op.execute(
"DROP POLICY IF EXISTS consumer_inbox_tenant_isolation ON consumer_inbox"
)