-- ----------------------------------------------------------------------------- -- Migration: 20260129_001_create_identity_watchlist -- Sprint: SPRINT_0129_001_ATTESTOR_identity_watchlist_alerting -- Task: WATCH-004 -- Description: Creates identity watchlist and alert deduplication tables. -- ----------------------------------------------------------------------------- -- Watchlist entries table CREATE TABLE IF NOT EXISTS attestor.identity_watchlist ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id TEXT NOT NULL, scope TEXT NOT NULL DEFAULT 'Tenant', display_name TEXT NOT NULL, description TEXT, -- Identity matching fields (at least one required) issuer TEXT, subject_alternative_name TEXT, key_id TEXT, match_mode TEXT NOT NULL DEFAULT 'Exact', -- Alert configuration severity TEXT NOT NULL DEFAULT 'Warning', enabled BOOLEAN NOT NULL DEFAULT TRUE, channel_overrides JSONB, suppress_duplicates_minutes INT NOT NULL DEFAULT 60, -- Metadata tags TEXT[], created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), created_by TEXT NOT NULL, updated_by TEXT NOT NULL, -- Constraints CONSTRAINT chk_at_least_one_identity CHECK ( issuer IS NOT NULL OR subject_alternative_name IS NOT NULL OR key_id IS NOT NULL ), CONSTRAINT chk_scope_valid CHECK (scope IN ('Tenant', 'Global', 'System')), CONSTRAINT chk_match_mode_valid CHECK (match_mode IN ('Exact', 'Prefix', 'Glob', 'Regex')), CONSTRAINT chk_severity_valid CHECK (severity IN ('Info', 'Warning', 'Critical')), CONSTRAINT chk_suppress_duplicates_positive CHECK (suppress_duplicates_minutes >= 1) ); -- Performance indexes for active entry lookup CREATE INDEX IF NOT EXISTS idx_watchlist_tenant_enabled ON attestor.identity_watchlist(tenant_id) WHERE enabled = TRUE; CREATE INDEX IF NOT EXISTS idx_watchlist_scope_enabled ON attestor.identity_watchlist(scope) WHERE enabled = TRUE; CREATE INDEX IF NOT EXISTS idx_watchlist_issuer ON attestor.identity_watchlist(issuer) WHERE enabled = TRUE AND issuer IS NOT NULL; CREATE INDEX IF NOT EXISTS idx_watchlist_san ON attestor.identity_watchlist(subject_alternative_name) WHERE enabled = TRUE AND subject_alternative_name IS NOT NULL; CREATE INDEX IF NOT EXISTS idx_watchlist_keyid ON attestor.identity_watchlist(key_id) WHERE enabled = TRUE AND key_id IS NOT NULL; -- Alert deduplication table CREATE TABLE IF NOT EXISTS attestor.identity_alert_dedup ( watchlist_id UUID NOT NULL, identity_hash TEXT NOT NULL, last_alert_at TIMESTAMPTZ NOT NULL, alert_count INT NOT NULL DEFAULT 0, PRIMARY KEY (watchlist_id, identity_hash) ); -- Index for cleanup CREATE INDEX IF NOT EXISTS idx_alert_dedup_last_alert ON attestor.identity_alert_dedup(last_alert_at); -- Comment documentation COMMENT ON TABLE attestor.identity_watchlist IS 'Watchlist entries for monitoring signing identity appearances in transparency logs.'; COMMENT ON COLUMN attestor.identity_watchlist.scope IS 'Visibility scope: Tenant (owning tenant only), Global (all tenants), System (read-only).'; COMMENT ON COLUMN attestor.identity_watchlist.match_mode IS 'Pattern matching mode: Exact, Prefix, Glob, or Regex.'; COMMENT ON COLUMN attestor.identity_watchlist.suppress_duplicates_minutes IS 'Deduplication window in minutes. Alerts for same identity within window are suppressed.'; COMMENT ON TABLE attestor.identity_alert_dedup IS 'Tracks alert deduplication state to prevent alert storms.';