| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183 |
- """Add canonical SLO policies, alerts and evidence-bound data incidents."""
- from alembic import op
- revision = "20260731_410"
- down_revision = "20260731_400"
- branch_labels = None
- depends_on = None
- def upgrade() -> None:
- op.execute(
- """
- CREATE TABLE public.data_slo_policies (
- uid UUID PRIMARY KEY,
- code VARCHAR(120) NOT NULL UNIQUE,
- name VARCHAR(300) NOT NULL,
- sli_type VARCHAR(30) NOT NULL CHECK (
- sli_type IN ('freshness','completeness','quality','delivery')
- ),
- scope_type VARCHAR(40) NOT NULL,
- scope_uid VARCHAR(500) NOT NULL,
- operator VARCHAR(2) NOT NULL CHECK (operator IN ('>=','<=')),
- target NUMERIC NOT NULL,
- window_seconds INTEGER NOT NULL CHECK (
- window_seconds BETWEEN 60 AND 31536000
- ),
- owner_uid UUID NOT NULL REFERENCES public.users(id),
- status VARCHAR(20) NOT NULL CHECK (
- status IN ('active','retired')
- ),
- 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,
- UNIQUE (sli_type, scope_type, scope_uid)
- );
- CREATE TABLE public.data_incidents (
- uid UUID PRIMARY KEY,
- code VARCHAR(40) NOT NULL UNIQUE,
- dedup_key CHAR(64) NOT NULL,
- title VARCHAR(300) NOT NULL,
- severity VARCHAR(20) NOT NULL CHECK (
- severity IN ('info','warning','error','critical')
- ),
- status VARCHAR(20) NOT NULL CHECK (
- status IN ('open','monitoring','closed')
- ),
- owner_uid UUID NOT NULL REFERENCES public.users(id),
- escalation_level INTEGER NOT NULL CHECK (
- escalation_level BETWEEN 1 AND 3
- ),
- first_detected_at TIMESTAMPTZ NOT NULL,
- last_observed_at TIMESTAMPTZ NOT NULL,
- 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,
- closed_by UUID REFERENCES public.users(id),
- closed_at TIMESTAMPTZ
- );
- CREATE UNIQUE INDEX uq_data_incident_open_dedup
- ON public.data_incidents(dedup_key)
- WHERE status <> 'closed';
- CREATE TABLE public.data_observability_alerts (
- uid UUID PRIMARY KEY,
- dedup_key CHAR(64) NOT NULL,
- incident_uid UUID NOT NULL REFERENCES public.data_incidents(uid),
- layer VARCHAR(20) NOT NULL CHECK (
- layer IN ('service','task','data','capacity')
- ),
- sli_type VARCHAR(30) NOT NULL CHECK (
- sli_type IN ('freshness','completeness','quality','delivery')
- ),
- title VARCHAR(300) NOT NULL,
- severity VARCHAR(20) NOT NULL CHECK (
- severity IN ('info','warning','error','critical')
- ),
- status VARCHAR(20) NOT NULL CHECK (
- status IN ('open','suppressed','recovered')
- ),
- occurrence_count INTEGER NOT NULL CHECK (occurrence_count > 0),
- escalation_level INTEGER NOT NULL CHECK (
- escalation_level BETWEEN 1 AND 3
- ),
- owner_uid UUID NOT NULL REFERENCES public.users(id),
- first_observed_at TIMESTAMPTZ NOT NULL,
- last_observed_at TIMESTAMPTZ NOT NULL,
- suppressed_until TIMESTAMPTZ,
- suppression_reason VARCHAR(500),
- delivery_status VARCHAR(30) NOT NULL CHECK (
- delivery_status IN (
- 'pending','suppressed','acknowledged','recovery_pending'
- )
- ),
- delivery_receipt JSONB NOT NULL DEFAULT '{}'::jsonb,
- evidence JSONB NOT NULL,
- created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
- CHECK (jsonb_typeof(delivery_receipt) = 'object'),
- CHECK (jsonb_typeof(evidence) = 'object')
- );
- CREATE UNIQUE INDEX uq_data_alert_open_dedup
- ON public.data_observability_alerts(dedup_key)
- WHERE status <> 'recovered';
- CREATE TABLE public.data_observability_source_events (
- uid UUID PRIMARY KEY,
- source_event_key VARCHAR(500) NOT NULL UNIQUE,
- source_type VARCHAR(80) NOT NULL,
- source_uid VARCHAR(500) NOT NULL,
- status VARCHAR(20) NOT NULL CHECK (
- status IN ('violated','recovered')
- ),
- signal JSONB NOT NULL,
- processed_at TIMESTAMPTZ NOT NULL,
- CHECK (jsonb_typeof(signal) = 'object')
- );
- CREATE TABLE public.data_incident_impacts (
- uid BIGSERIAL PRIMARY KEY,
- incident_uid UUID NOT NULL
- REFERENCES public.data_incidents(uid) ON DELETE CASCADE,
- target_type VARCHAR(40) NOT NULL CHECK (
- target_type IN (
- 'asset','data_product','business_domain',
- 'user_group','service'
- )
- ),
- target_uid VARCHAR(500) NOT NULL,
- label VARCHAR(300) NOT NULL,
- created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
- UNIQUE (incident_uid, target_type, target_uid)
- );
- CREATE TABLE public.data_incident_timeline (
- uid UUID PRIMARY KEY,
- incident_uid UUID NOT NULL
- REFERENCES public.data_incidents(uid) ON DELETE CASCADE,
- action VARCHAR(40) NOT NULL,
- actor_uid UUID NOT NULL REFERENCES public.users(id),
- evidence JSONB NOT NULL,
- created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
- CHECK (jsonb_typeof(evidence) = 'object')
- );
- CREATE TABLE public.data_incident_postmortems (
- uid UUID PRIMARY KEY,
- incident_uid UUID NOT NULL UNIQUE
- REFERENCES public.data_incidents(uid) ON DELETE CASCADE,
- root_cause TEXT NOT NULL,
- user_impact TEXT NOT NULL,
- corrective_actions JSONB NOT NULL,
- closure_evidence JSONB NOT NULL,
- created_by UUID NOT NULL REFERENCES public.users(id),
- created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
- CHECK (jsonb_typeof(corrective_actions) = 'array'),
- CHECK (jsonb_array_length(corrective_actions) > 0),
- CHECK (jsonb_typeof(closure_evidence) = 'array'),
- CHECK (jsonb_array_length(closure_evidence) > 0)
- );
- CREATE INDEX idx_data_alert_layer_status
- ON public.data_observability_alerts(
- layer, status, severity, last_observed_at DESC
- );
- CREATE INDEX idx_data_incident_status_updated
- ON public.data_incidents(status, updated_at DESC);
- CREATE INDEX idx_data_incident_timeline_created
- ON public.data_incident_timeline(
- incident_uid, created_at, uid
- );
- """
- )
- def downgrade() -> None:
- raise RuntimeError(
- "data incident evidence is append-only; "
- "downgrade requires an approved archival migration"
- )
|