| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275 |
- """Add the unified governance approval, task and notification work center."""
- from alembic import op
- revision = "20260802_430"
- down_revision = "20260801_420"
- branch_labels = None
- depends_on = None
- def upgrade() -> None:
- op.execute(
- """
- CREATE TABLE public.governance_workflows (
- uid UUID PRIMARY KEY,
- code VARCHAR(120) NOT NULL UNIQUE,
- name VARCHAR(300) NOT NULL,
- status VARCHAR(20) NOT NULL CHECK (
- status IN ('draft','published','retired')
- ),
- current_version INTEGER NOT NULL CHECK (current_version > 0),
- active_version_uid UUID,
- created_by UUID NOT NULL REFERENCES public.users(id),
- created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
- );
- CREATE TABLE public.governance_workflow_versions (
- uid UUID PRIMARY KEY,
- workflow_uid UUID NOT NULL REFERENCES public.governance_workflows(uid),
- version INTEGER NOT NULL CHECK (version > 0),
- status VARCHAR(20) NOT NULL CHECK (
- status IN ('draft','published','superseded')
- ),
- definition JSONB NOT NULL,
- created_by UUID NOT NULL REFERENCES public.users(id),
- created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
- published_by UUID REFERENCES public.users(id),
- published_at TIMESTAMPTZ,
- UNIQUE (workflow_uid, version),
- CHECK (jsonb_typeof(definition) = 'object')
- );
- ALTER TABLE public.governance_workflows
- ADD CONSTRAINT governance_workflow_active_version_fk
- FOREIGN KEY (active_version_uid)
- REFERENCES public.governance_workflow_versions(uid);
- CREATE UNIQUE INDEX uq_governance_workflow_published_version
- ON public.governance_workflow_versions(workflow_uid)
- WHERE status = 'published';
- CREATE TABLE public.governance_tasks (
- uid UUID PRIMARY KEY,
- task_code VARCHAR(40) NOT NULL UNIQUE,
- workflow_uid UUID NOT NULL REFERENCES public.governance_workflows(uid),
- workflow_version INTEGER NOT NULL CHECK (workflow_version > 0),
- task_type VARCHAR(40) NOT NULL CHECK (
- task_type IN (
- 'approval','quality_issue','semantic_governance',
- 'data_product_approval','agent_approval',
- 'governance_work_order','release','high_risk'
- )
- ),
- subject_type VARCHAR(40) NOT NULL CHECK (
- subject_type IN (
- 'quality_issue','semantic_governance','data_product','agent'
- )
- ),
- subject_uid VARCHAR(200) NOT NULL,
- source_type VARCHAR(80) NOT NULL,
- source_uid VARCHAR(200) NOT NULL,
- title VARCHAR(300) NOT NULL,
- description VARCHAR(2000) NOT NULL,
- business_domain_uid VARCHAR(200),
- priority VARCHAR(20) NOT NULL CHECK (
- priority IN ('low','medium','high','critical')
- ),
- status VARCHAR(30) NOT NULL CHECK (
- status IN (
- 'pending','in_progress','pending_review','approved',
- 'rejected','closed','reopened','cancelled'
- )
- ),
- assignee_uid UUID NOT NULL REFERENCES public.users(id),
- due_at TIMESTAMPTZ NOT NULL,
- escalation_level INTEGER NOT NULL DEFAULT 0 CHECK (
- escalation_level BETWEEN 0 AND 10
- ),
- current_version INTEGER NOT NULL DEFAULT 1 CHECK (
- current_version > 0
- ),
- route_snapshot JSONB NOT NULL,
- context JSONB NOT NULL DEFAULT '{}'::jsonb,
- source_state_unchanged BOOLEAN NOT NULL DEFAULT TRUE CHECK (
- source_state_unchanged
- ),
- created_by UUID NOT NULL REFERENCES public.users(id),
- created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
- updated_by UUID NOT NULL REFERENCES public.users(id),
- updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
- closed_at TIMESTAMPTZ,
- CHECK (jsonb_typeof(route_snapshot) = 'object'),
- CHECK (jsonb_typeof(context) = 'object')
- );
- CREATE UNIQUE INDEX uq_governance_task_active_source
- ON public.governance_tasks(source_type, source_uid)
- WHERE status NOT IN ('closed','cancelled');
- CREATE INDEX idx_governance_task_worklist
- ON public.governance_tasks(
- assignee_uid, status, priority, due_at, created_at DESC
- );
- CREATE INDEX idx_governance_task_domain
- ON public.governance_tasks(
- business_domain_uid, status, due_at
- );
- CREATE TABLE public.governance_task_participants (
- uid UUID PRIMARY KEY,
- task_uid UUID NOT NULL
- REFERENCES public.governance_tasks(uid) ON DELETE CASCADE,
- user_uid UUID NOT NULL REFERENCES public.users(id),
- sequence INTEGER NOT NULL CHECK (sequence > 0),
- status VARCHAR(20) NOT NULL CHECK (
- status IN ('pending','decided','transferred')
- ),
- transferred_from_uid UUID REFERENCES public.users(id),
- created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
- UNIQUE (task_uid, user_uid)
- );
- CREATE INDEX idx_governance_task_participant_user
- ON public.governance_task_participants(user_uid, status, task_uid);
- CREATE TABLE public.governance_task_reviews (
- uid UUID PRIMARY KEY,
- task_uid UUID NOT NULL
- REFERENCES public.governance_tasks(uid) ON DELETE CASCADE,
- reviewer_uid UUID NOT NULL REFERENCES public.users(id),
- decision VARCHAR(20) NOT NULL CHECK (
- decision IN ('approve','reject')
- ),
- reason VARCHAR(1000) NOT NULL,
- created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
- UNIQUE (task_uid, reviewer_uid)
- );
- CREATE TABLE public.governance_task_comments (
- uid UUID PRIMARY KEY,
- task_uid UUID NOT NULL
- REFERENCES public.governance_tasks(uid) ON DELETE CASCADE,
- content VARCHAR(4000) NOT NULL,
- mentions JSONB NOT NULL DEFAULT '[]'::jsonb,
- created_by UUID NOT NULL REFERENCES public.users(id),
- created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
- CHECK (jsonb_typeof(mentions) = 'array')
- );
- CREATE TABLE public.governance_task_attachments (
- uid UUID PRIMARY KEY,
- task_uid UUID NOT NULL
- REFERENCES public.governance_tasks(uid) ON DELETE CASCADE,
- name VARCHAR(300) NOT NULL,
- storage_ref VARCHAR(1000) NOT NULL,
- content_hash CHAR(64) NOT NULL,
- created_by UUID NOT NULL REFERENCES public.users(id),
- created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
- UNIQUE (task_uid, content_hash)
- );
- CREATE TABLE public.governance_task_events (
- uid UUID PRIMARY KEY,
- task_uid UUID NOT NULL
- REFERENCES public.governance_tasks(uid) ON DELETE CASCADE,
- task_version INTEGER NOT NULL CHECK (task_version > 0),
- action VARCHAR(40) NOT NULL,
- actor_uid UUID NOT NULL REFERENCES public.users(id),
- before_state JSONB NOT NULL DEFAULT '{}'::jsonb,
- after_state JSONB NOT NULL,
- payload JSONB NOT NULL DEFAULT '{}'::jsonb,
- created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
- UNIQUE (task_uid, task_version, action),
- CHECK (jsonb_typeof(before_state) = 'object'),
- CHECK (jsonb_typeof(after_state) = 'object'),
- CHECK (jsonb_typeof(payload) = 'object')
- );
- CREATE INDEX idx_governance_task_event_timeline
- ON public.governance_task_events(task_uid, created_at, uid);
- CREATE TABLE public.governance_notification_templates (
- uid UUID PRIMARY KEY,
- code VARCHAR(120) NOT NULL,
- channel VARCHAR(20) NOT NULL CHECK (
- channel IN ('in_app','email')
- ),
- subject_template VARCHAR(500) NOT NULL,
- body_template VARCHAR(4000) NOT NULL,
- status VARCHAR(20) NOT NULL CHECK (
- status IN ('active','retired')
- ),
- current_version INTEGER NOT NULL DEFAULT 1 CHECK (
- current_version > 0
- ),
- created_by UUID NOT NULL REFERENCES public.users(id),
- updated_by UUID NOT NULL REFERENCES public.users(id),
- created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
- UNIQUE (code, channel)
- );
- CREATE TABLE public.governance_notification_preferences (
- user_uid UUID PRIMARY KEY REFERENCES public.users(id),
- enabled_channels JSONB NOT NULL DEFAULT '["in_app","email"]'::jsonb,
- subscribed_events JSONB NOT NULL DEFAULT '[]'::jsonb,
- quiet_hours JSONB NOT NULL DEFAULT '{}'::jsonb,
- revision INTEGER NOT NULL DEFAULT 1 CHECK (revision > 0),
- updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
- CHECK (jsonb_typeof(enabled_channels) = 'array'),
- CHECK (jsonb_typeof(subscribed_events) = 'array'),
- CHECK (jsonb_typeof(quiet_hours) = 'object')
- );
- CREATE TABLE public.governance_notifications (
- uid UUID PRIMARY KEY,
- event_key VARCHAR(500) NOT NULL UNIQUE,
- event_type VARCHAR(80) NOT NULL,
- recipient_uid UUID NOT NULL REFERENCES public.users(id),
- channel VARCHAR(20) NOT NULL CHECK (
- channel IN ('in_app','email')
- ),
- subject VARCHAR(500) NOT NULL,
- body VARCHAR(4000) NOT NULL,
- related_task_uid UUID
- REFERENCES public.governance_tasks(uid) ON DELETE CASCADE,
- status VARCHAR(20) NOT NULL CHECK (
- status IN (
- 'pending','processing','delivered','suppressed','dead_letter'
- )
- ),
- attempts INTEGER NOT NULL DEFAULT 0 CHECK (attempts >= 0),
- max_attempts INTEGER NOT NULL DEFAULT 5 CHECK (max_attempts > 0),
- next_attempt_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
- last_error VARCHAR(1000),
- delivered_at TIMESTAMPTZ,
- read_at TIMESTAMPTZ,
- created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
- );
- CREATE INDEX idx_governance_notification_inbox
- ON public.governance_notifications(
- recipient_uid, channel, read_at, created_at DESC
- );
- CREATE INDEX idx_governance_notification_delivery
- ON public.governance_notifications(
- channel, status, next_attempt_at
- );
- CREATE TABLE public.governance_notification_attempts (
- uid UUID PRIMARY KEY,
- notification_uid UUID NOT NULL
- REFERENCES public.governance_notifications(uid) ON DELETE CASCADE,
- attempt INTEGER NOT NULL CHECK (attempt > 0),
- status VARCHAR(20) NOT NULL CHECK (
- status IN ('pending','delivered','dead_letter')
- ),
- safe_error VARCHAR(1000),
- created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
- UNIQUE (notification_uid, attempt)
- );
- """
- )
- def downgrade() -> None:
- raise RuntimeError(
- "workflow, task and notification evidence is retained; "
- "downgrade requires an approved archival migration"
- )
|