-- 011_compliance_rules_dynamic.sql -- Dynamic Compliance Rule Configuration with Hot-Reload Support -- Implements database-backed rule configuration for ComplianceValidator -- Enable required extensions CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; -- Compliance rule types enumeration CREATE TYPE compliance_rule_type AS ENUM ( 'POSITION_LIMIT', 'MARKET_ABUSE', 'CLIENT_SUITABILITY', 'BEST_EXECUTION', 'CONCENTRATION_RISK', 'LEVERAGE_LIMIT', 'CAPITAL_ADEQUACY', 'REGULATORY_REPORTING', 'CUSTOM' ); -- Main compliance rules configuration table CREATE TABLE compliance_rules ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), rule_id VARCHAR(100) UNIQUE NOT NULL, name VARCHAR(255) NOT NULL, description TEXT NOT NULL, rule_type compliance_rule_type NOT NULL, -- Rule activation and versioning active BOOLEAN NOT NULL DEFAULT true, version INTEGER NOT NULL DEFAULT 1, -- Severity and priority severity VARCHAR(20) NOT NULL CHECK (severity IN ('Info', 'Low', 'Medium', 'High', 'Critical')), priority INTEGER NOT NULL DEFAULT 50, -- 0-100 scale -- Flexible rule parameters as JSONB -- Examples: -- Position limit: {"instrument_id": "BTC-USD", "max_position": 1000000, "max_daily_turnover": 5000000} -- Market abuse: {"threshold": 1000000, "window_seconds": 300, "check_volume_spike": true} -- Client suitability: {"min_net_worth": 100000, "accredited_only": true} parameters JSONB NOT NULL DEFAULT '{}', -- Regulatory framework references regulatory_framework VARCHAR(50), -- MiFID II, Basel III, Dodd-Frank, etc. regulatory_reference TEXT, -- Specific article/section reference -- Temporal validity effective_date TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), expiry_date TIMESTAMP WITH TIME ZONE, -- Audit trail created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), created_by UUID, updated_by UUID, -- Additional metadata metadata JSONB DEFAULT '{}', CONSTRAINT valid_priority CHECK (priority >= 0 AND priority <= 100) ); -- Compliance rule version history for audit trail CREATE TABLE compliance_rule_versions ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), rule_id VARCHAR(100) NOT NULL, version INTEGER NOT NULL, -- Snapshot of rule at this version name VARCHAR(255) NOT NULL, description TEXT NOT NULL, rule_type compliance_rule_type NOT NULL, active BOOLEAN NOT NULL, severity VARCHAR(20) NOT NULL, priority INTEGER NOT NULL, parameters JSONB NOT NULL, regulatory_framework VARCHAR(50), regulatory_reference TEXT, -- Version metadata change_reason TEXT, changed_by UUID, changed_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), -- Link to current rule current_rule_id UUID REFERENCES compliance_rules(id) ON DELETE CASCADE, UNIQUE(rule_id, version) ); -- Compliance rule execution audit (tracks rule evaluations) CREATE TABLE compliance_rule_executions ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), rule_id VARCHAR(100) NOT NULL, -- Execution context order_id VARCHAR(100), instrument_id VARCHAR(50), portfolio_id VARCHAR(100), client_id VARCHAR(100), -- Execution result result VARCHAR(20) NOT NULL CHECK (result IN ('PASS', 'WARN', 'FAIL', 'ERROR')), violation_detected BOOLEAN NOT NULL DEFAULT false, warning_generated BOOLEAN NOT NULL DEFAULT false, -- Evaluation details evaluated_value JSONB, -- The values that were evaluated threshold_value JSONB, -- The threshold that was checked against breach_amount DECIMAL(20, 8), -- Timing execution_timestamp TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), execution_duration_ms INTEGER, -- Additional context details JSONB DEFAULT '{}' ); -- Indexes for performance CREATE INDEX idx_compliance_rules_active ON compliance_rules(active, rule_type); CREATE INDEX idx_compliance_rules_type ON compliance_rules(rule_type); CREATE INDEX idx_compliance_rules_effective ON compliance_rules(effective_date, expiry_date) WHERE active = true; CREATE INDEX idx_compliance_rules_framework ON compliance_rules(regulatory_framework); CREATE INDEX idx_compliance_rule_versions_rule ON compliance_rule_versions(rule_id, version DESC); CREATE INDEX idx_compliance_rule_versions_changed ON compliance_rule_versions(changed_at DESC); CREATE INDEX idx_compliance_rule_executions_rule ON compliance_rule_executions(rule_id, execution_timestamp DESC); CREATE INDEX idx_compliance_rule_executions_result ON compliance_rule_executions(result, execution_timestamp DESC); CREATE INDEX idx_compliance_rule_executions_instrument ON compliance_rule_executions(instrument_id, execution_timestamp DESC); -- Function to archive rule version on update CREATE OR REPLACE FUNCTION archive_compliance_rule_version() RETURNS TRIGGER AS $$ BEGIN -- Only archive if this is an actual update (not insert) IF TG_OP = 'UPDATE' THEN INSERT INTO compliance_rule_versions ( rule_id, version, name, description, rule_type, active, severity, priority, parameters, regulatory_framework, regulatory_reference, change_reason, changed_by, current_rule_id ) VALUES ( OLD.rule_id, OLD.version, OLD.name, OLD.description, OLD.rule_type, OLD.active, OLD.severity, OLD.priority, OLD.parameters, OLD.regulatory_framework, OLD.regulatory_reference, COALESCE(NEW.metadata->>'change_reason', 'Rule updated'), NEW.updated_by, NEW.id ); -- Increment version on the new row NEW.version := OLD.version + 1; NEW.updated_at := NOW(); END IF; RETURN NEW; END; $$ LANGUAGE plpgsql; CREATE TRIGGER compliance_rule_version_trigger BEFORE UPDATE ON compliance_rules FOR EACH ROW EXECUTE FUNCTION archive_compliance_rule_version(); -- Function to notify on rule changes (for hot-reload) CREATE OR REPLACE FUNCTION notify_compliance_rule_change() RETURNS TRIGGER AS $$ DECLARE notification_payload TEXT; BEGIN -- Build notification payload notification_payload := json_build_object( 'operation', TG_OP, 'rule_id', COALESCE(NEW.rule_id, OLD.rule_id), 'rule_type', COALESCE(NEW.rule_type::text, OLD.rule_type::text), 'active', COALESCE(NEW.active, false), 'timestamp', extract(epoch from now()) )::text; -- Send notification on compliance_rules_changed channel PERFORM pg_notify('compliance_rules_changed', notification_payload); RETURN COALESCE(NEW, OLD); END; $$ LANGUAGE plpgsql; CREATE TRIGGER compliance_rule_change_notify_trigger AFTER INSERT OR UPDATE OR DELETE ON compliance_rules FOR EACH ROW EXECUTE FUNCTION notify_compliance_rule_change(); -- Function to load active compliance rules CREATE OR REPLACE FUNCTION get_active_compliance_rules() RETURNS TABLE ( id UUID, rule_id VARCHAR(100), name VARCHAR(255), description TEXT, rule_type compliance_rule_type, active BOOLEAN, version INTEGER, severity VARCHAR(20), priority INTEGER, parameters JSONB, regulatory_framework VARCHAR(50), regulatory_reference TEXT, effective_date TIMESTAMP WITH TIME ZONE, expiry_date TIMESTAMP WITH TIME ZONE ) AS $$ BEGIN RETURN QUERY SELECT cr.id, cr.rule_id, cr.name, cr.description, cr.rule_type, cr.active, cr.version, cr.severity, cr.priority, cr.parameters, cr.regulatory_framework, cr.regulatory_reference, cr.effective_date, cr.expiry_date FROM compliance_rules cr WHERE cr.active = true AND cr.effective_date <= NOW() AND (cr.expiry_date IS NULL OR cr.expiry_date > NOW()) ORDER BY cr.priority DESC, cr.created_at ASC; END; $$ LANGUAGE plpgsql STABLE; -- Function to get rules by type CREATE OR REPLACE FUNCTION get_compliance_rules_by_type(p_rule_type compliance_rule_type) RETURNS TABLE ( id UUID, rule_id VARCHAR(100), name VARCHAR(255), parameters JSONB, severity VARCHAR(20) ) AS $$ BEGIN RETURN QUERY SELECT cr.id, cr.rule_id, cr.name, cr.parameters, cr.severity FROM compliance_rules cr WHERE cr.active = true AND cr.rule_type = p_rule_type AND cr.effective_date <= NOW() AND (cr.expiry_date IS NULL OR cr.expiry_date > NOW()) ORDER BY cr.priority DESC; END; $$ LANGUAGE plpgsql STABLE; -- Function to record rule execution CREATE OR REPLACE FUNCTION record_compliance_rule_execution( p_rule_id VARCHAR(100), p_result VARCHAR(20), p_violation_detected BOOLEAN DEFAULT false, p_order_id VARCHAR(100) DEFAULT NULL, p_instrument_id VARCHAR(50) DEFAULT NULL, p_evaluated_value JSONB DEFAULT NULL, p_threshold_value JSONB DEFAULT NULL, p_breach_amount DECIMAL DEFAULT NULL ) RETURNS UUID AS $$ DECLARE v_execution_id UUID; BEGIN v_execution_id := uuid_generate_v4(); INSERT INTO compliance_rule_executions ( id, rule_id, result, violation_detected, order_id, instrument_id, evaluated_value, threshold_value, breach_amount ) VALUES ( v_execution_id, p_rule_id, p_result, p_violation_detected, p_order_id, p_instrument_id, p_evaluated_value, p_threshold_value, p_breach_amount ); RETURN v_execution_id; END; $$ LANGUAGE plpgsql SECURITY DEFINER; -- Insert default compliance rules INSERT INTO compliance_rules (rule_id, name, description, rule_type, severity, priority, parameters, regulatory_framework, regulatory_reference) VALUES ('position_limit_default', 'Default Position Limit', 'Default maximum position size for instruments without specific limits', 'POSITION_LIMIT', 'High', 90, '{"max_position_size": 1000000, "max_daily_turnover": 5000000, "concentration_limit": 0.1}'::jsonb, 'Basel III', 'Capital Requirements'), ('market_abuse_large_order', 'Large Order Market Abuse Detection', 'Detect potentially manipulative large orders', 'MARKET_ABUSE', 'Critical', 95, '{"threshold": 1000000, "window_seconds": 300, "volume_spike_multiplier": 3.0}'::jsonb, 'MAR', 'Market Abuse Regulation Article 15'), ('client_suitability_conservative', 'Conservative Client Suitability', 'Suitability checks for conservative risk profile clients', 'CLIENT_SUITABILITY', 'Medium', 70, '{"max_order_value": 10000, "max_leverage": 2.0, "complex_products_allowed": false}'::jsonb, 'MiFID II', 'Article 25 - Suitability Assessment'), ('best_execution_monitoring', 'Best Execution Monitoring', 'Monitor best execution requirements', 'BEST_EXECUTION', 'High', 85, '{"min_execution_quality_score": 0.70, "max_price_improvement_deviation": 0.05, "venue_analysis_required": true}'::jsonb, 'MiFID II', 'Article 27 - Best Execution'), ('capital_adequacy_basel', 'Basel III Capital Adequacy', 'Monitor capital adequacy ratios', 'CAPITAL_ADEQUACY', 'Critical', 100, '{"min_capital_adequacy_ratio": 0.08, "min_leverage_ratio": 0.03, "tier1_minimum": 0.06}'::jsonb, 'Basel III', 'Capital Adequacy Requirements'), ('concentration_risk_limit', 'Portfolio Concentration Limit', 'Maximum concentration in single instrument', 'CONCENTRATION_RISK', 'High', 80, '{"max_concentration_pct": 0.15, "max_sector_concentration": 0.30}'::jsonb, 'Internal Risk', 'Risk Management Policy Section 4.2'); -- Grant permissions GRANT SELECT ON compliance_rules TO authenticated_users; GRANT SELECT ON compliance_rule_versions TO authenticated_users; GRANT INSERT ON compliance_rule_executions TO authenticated_users; GRANT EXECUTE ON FUNCTION get_active_compliance_rules TO authenticated_users; GRANT EXECUTE ON FUNCTION get_compliance_rules_by_type TO authenticated_users; GRANT EXECUTE ON FUNCTION record_compliance_rule_execution TO authenticated_users; -- Comments for documentation COMMENT ON TABLE compliance_rules IS 'Dynamic compliance rules with hot-reload support via PostgreSQL NOTIFY/LISTEN'; COMMENT ON TABLE compliance_rule_versions IS 'Version history for compliance rule changes - full audit trail'; COMMENT ON TABLE compliance_rule_executions IS 'Audit trail of compliance rule evaluations'; COMMENT ON FUNCTION notify_compliance_rule_change IS 'Triggers PostgreSQL NOTIFY on compliance rule changes for hot-reload'; COMMENT ON FUNCTION get_active_compliance_rules IS 'Returns all currently active compliance rules'; COMMENT ON FUNCTION get_compliance_rules_by_type IS 'Returns active rules filtered by type';