20260811_483_trusted_delivery_subscriptions.py 3.6 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970
  1. """Persist P3-WP06 subscription governance and fenced delivery queue."""
  2. from alembic import op
  3. revision = "20260811_483"
  4. down_revision = "20260811_482"
  5. branch_labels = None
  6. depends_on = None
  7. def upgrade() -> None:
  8. op.execute(
  9. """
  10. CREATE TABLE public.trusted_delivery_subscriptions (
  11. uid UUID PRIMARY KEY,
  12. grant_uid UUID NOT NULL REFERENCES public.trusted_delivery_grants(uid),
  13. asset_uid UUID NOT NULL,
  14. trigger JSONB NOT NULL CHECK(jsonb_typeof(trigger)='object'),
  15. purpose VARCHAR(100) NOT NULL,
  16. expires_at TIMESTAMPTZ NOT NULL,
  17. idempotency_key VARCHAR(160) NOT NULL UNIQUE,
  18. request_digest CHAR(64) NOT NULL CHECK(request_digest ~ '^[0-9a-f]{64}$'),
  19. status VARCHAR(20) NOT NULL CHECK(status IN ('draft','active','paused','terminated','expired','reclaimed')),
  20. current_version INTEGER NOT NULL DEFAULT 1 CHECK(current_version>0),
  21. created_by UUID NOT NULL REFERENCES public.users(id),
  22. created_at TIMESTAMPTZ NOT NULL DEFAULT clock_timestamp(),
  23. updated_at TIMESTAMPTZ NOT NULL DEFAULT clock_timestamp(),
  24. CHECK((trigger->>'kind'='schedule' AND jsonb_typeof(trigger->'schedule_ref')='string'
  25. AND trigger=jsonb_build_object('kind','schedule','schedule_ref',trigger->'schedule_ref'))
  26. OR (trigger->>'kind'='event' AND jsonb_typeof(trigger->'event_type')='string'
  27. AND trigger=jsonb_build_object('kind','event','event_type',trigger->'event_type')))
  28. );
  29. CREATE INDEX ix_trusted_delivery_subscription_expiry ON public.trusted_delivery_subscriptions(status,expires_at);
  30. CREATE TABLE public.trusted_delivery_subscription_deliveries (
  31. uid UUID PRIMARY KEY,
  32. subscription_uid UUID NOT NULL REFERENCES public.trusted_delivery_subscriptions(uid),
  33. trigger_ref VARCHAR(160) NOT NULL,
  34. event_digest CHAR(64) NOT NULL CHECK(event_digest ~ '^[0-9a-f]{64}$'),
  35. idempotency_key VARCHAR(320) NOT NULL UNIQUE,
  36. request_digest CHAR(64) NOT NULL CHECK(request_digest ~ '^[0-9a-f]{64}$'),
  37. status VARCHAR(20) NOT NULL CHECK(status IN ('pending','processing','delivered','dead_letter','compensated')),
  38. attempt_count INTEGER NOT NULL DEFAULT 0 CHECK(attempt_count BETWEEN 0 AND 3),
  39. next_attempt_at TIMESTAMPTZ NOT NULL DEFAULT clock_timestamp(),
  40. lease_owner VARCHAR(80),
  41. lease_fence BIGINT NOT NULL DEFAULT 0 CHECK(lease_fence>=0),
  42. lease_expires_at TIMESTAMPTZ,
  43. last_reason_code VARCHAR(80),
  44. compensation_reason_code VARCHAR(64),
  45. compensation_receipt_code VARCHAR(64),
  46. created_at TIMESTAMPTZ NOT NULL DEFAULT clock_timestamp(),
  47. updated_at TIMESTAMPTZ NOT NULL DEFAULT clock_timestamp(),
  48. CHECK((status='processing')=(lease_owner IS NOT NULL AND lease_expires_at IS NOT NULL)),
  49. CHECK((status='compensated')=(compensation_reason_code IS NOT NULL AND compensation_receipt_code IS NOT NULL))
  50. );
  51. CREATE INDEX ix_trusted_delivery_subscription_claim ON public.trusted_delivery_subscription_deliveries(status,next_attempt_at);
  52. """
  53. )
  54. def downgrade() -> None:
  55. op.execute(
  56. """DO $$ BEGIN
  57. IF EXISTS(SELECT 1 FROM public.trusted_delivery_subscription_deliveries)
  58. OR EXISTS(SELECT 1 FROM public.trusted_delivery_subscriptions) THEN
  59. RAISE EXCEPTION 'downgrade requires approved subscription archival migration';
  60. END IF;
  61. END $$;
  62. DROP TABLE public.trusted_delivery_subscription_deliveries;
  63. DROP TABLE public.trusted_delivery_subscriptions;"""
  64. )