-- Migration 019: Enhanced Configuration NOTIFY Triggers -- -- Implements service-specific PostgreSQL NOTIFY channels for hot-reload -- Replaces basic notify_config_change() with intelligent routing -- -- Created: Wave 70 Agent 11 -- Purpose: Enable targeted configuration updates per service -- ============================================================================ -- Enhanced NOTIFY Trigger Function for config_entries -- ============================================================================ -- Drop existing trigger if it exists from initial schema DROP TRIGGER IF EXISTS notify_config_entries_change ON config_entries; -- Create enhanced function with service-specific channel routing CREATE OR REPLACE FUNCTION notify_config_change() RETURNS TRIGGER AS $$ DECLARE service_name TEXT; category TEXT; payload JSON; old_val TEXT; new_val TEXT; BEGIN -- Extract values for payload IF TG_OP = 'DELETE' THEN old_val := OLD.value; new_val := NULL; ELSIF TG_OP = 'UPDATE' THEN old_val := OLD.value; new_val := NEW.value; ELSE -- INSERT old_val := NULL; new_val := NEW.value; END IF; -- Extract category from key (e.g., "risk.max_daily_loss" → "risk") category := split_part(COALESCE(NEW.key, OLD.key), '.', 1); -- Determine service scope based on category CASE category WHEN 'trading', 'execution', 'order' THEN service_name := 'trading'; WHEN 'risk', 'compliance', 'var', 'circuit' THEN service_name := 'trading'; -- Risk is part of trading service WHEN 'backtesting', 'strategy', 'simulation', 'backtest' THEN service_name := 'backtesting'; WHEN 'ml', 'training', 'models', 'inference', 'model' THEN service_name := 'ml_training'; WHEN 'api', 'auth', 'gateway', 'jwt', 'mfa', 'rbac' THEN service_name := 'api_gateway'; WHEN 'system', 's3', 'database', 'redis', 'vault' THEN service_name := 'global'; -- Infrastructure settings affect all services ELSE service_name := 'global'; -- Unknown categories go to global channel END CASE; -- Build notification payload with full context payload := json_build_object( 'operation', TG_OP, 'table', TG_TABLE_NAME, 'key', COALESCE(NEW.key, OLD.key), 'value', new_val, 'old_value', old_val, 'category', category, 'timestamp', EXTRACT(EPOCH FROM NOW()), 'id', COALESCE(NEW.id, OLD.id) ); -- Send NOTIFY to service-specific channel PERFORM pg_notify('config_changed_' || service_name, payload::text); -- Also send to global channel for monitoring/debugging PERFORM pg_notify('config_changed_global', payload::text); RETURN COALESCE(NEW, OLD); END; $$ LANGUAGE plpgsql; -- Apply enhanced trigger to config_entries CREATE TRIGGER notify_config_entries_change AFTER INSERT OR UPDATE OR DELETE ON config_entries FOR EACH ROW EXECUTE FUNCTION notify_config_change(); COMMENT ON FUNCTION notify_config_change() IS 'Enhanced NOTIFY trigger with service-specific channel routing based on config key prefix. Channels: config_changed_trading, config_changed_backtesting, config_changed_ml_training, config_changed_api_gateway, config_changed_global'; -- ============================================================================ -- Enhanced NOTIFY Trigger Function for model_config -- ============================================================================ CREATE OR REPLACE FUNCTION notify_model_config_change() RETURNS TRIGGER AS $$ DECLARE payload JSON; model_name_val TEXT; version_val TEXT; BEGIN -- Extract model info for payload IF TG_OP = 'DELETE' THEN model_name_val := OLD.name; version_val := OLD.version; ELSE model_name_val := NEW.name; version_val := NEW.version; END IF; -- Build notification payload payload := json_build_object( 'operation', TG_OP, 'table', TG_TABLE_NAME, 'model_name', model_name_val, 'version', version_val, 'is_active', CASE WHEN TG_OP = 'DELETE' THEN false ELSE NEW.is_active END, 'timestamp', EXTRACT(EPOCH FROM NOW()), 'id', COALESCE(NEW.id, OLD.id) ); -- Send to ML training service (model owner) PERFORM pg_notify('config_changed_ml_training', payload::text); -- Also send to trading service (model consumer) PERFORM pg_notify('config_changed_trading', payload::text); -- Global channel for monitoring PERFORM pg_notify('config_changed_global', payload::text); RETURN COALESCE(NEW, OLD); END; $$ LANGUAGE plpgsql; -- Drop old trigger and create new one DROP TRIGGER IF EXISTS notify_model_config_change ON model_config; CREATE TRIGGER notify_model_config_change AFTER INSERT OR UPDATE OR DELETE ON model_config FOR EACH ROW EXECUTE FUNCTION notify_model_config_change(); COMMENT ON FUNCTION notify_model_config_change() IS 'NOTIFY trigger for model_config changes. Sends to both ml_training (owner) and trading (consumer) services.'; -- ============================================================================ -- Enhanced NOTIFY Trigger Function for Permissions (RBAC) -- ============================================================================ CREATE OR REPLACE FUNCTION notify_permission_change() RETURNS TRIGGER AS $$ DECLARE payload JSON; BEGIN -- Build notification payload payload := json_build_object( 'operation', TG_OP, 'table', TG_TABLE_NAME, 'timestamp', EXTRACT(EPOCH FROM NOW()), -- Include IDs based on operation type 'role_id', CASE WHEN TG_TABLE_NAME = 'role_permissions' OR TG_TABLE_NAME = 'user_roles' THEN COALESCE(NEW.role_id, OLD.role_id) ELSE NULL END, 'permission_id', CASE WHEN TG_TABLE_NAME = 'role_permissions' THEN COALESCE(NEW.permission_id, OLD.permission_id) ELSE NULL END, 'user_id', CASE WHEN TG_TABLE_NAME = 'user_roles' THEN COALESCE(NEW.user_id, OLD.user_id) ELSE NULL END ); -- Send to API Gateway (RBAC enforcement point) PERFORM pg_notify('permissions_changed', payload::text); -- Also send to global for monitoring PERFORM pg_notify('config_changed_global', payload::text); RETURN COALESCE(NEW, OLD); END; $$ LANGUAGE plpgsql; -- Apply permission change triggers to RBAC tables -- Note: role_permissions and user_roles triggers already exist from migration 018 -- We're enhancing them here DROP TRIGGER IF EXISTS trigger_role_permissions_change ON role_permissions; CREATE TRIGGER trigger_role_permissions_change AFTER INSERT OR UPDATE OR DELETE ON role_permissions FOR EACH ROW EXECUTE FUNCTION notify_permission_change(); DROP TRIGGER IF EXISTS trigger_user_roles_change ON user_roles; CREATE TRIGGER trigger_user_roles_change AFTER INSERT OR UPDATE OR DELETE ON user_roles FOR EACH ROW EXECUTE FUNCTION notify_permission_change(); DROP TRIGGER IF EXISTS trigger_permissions_change ON permissions; CREATE TRIGGER trigger_permissions_change AFTER INSERT OR UPDATE OR DELETE ON permissions FOR EACH ROW EXECUTE FUNCTION notify_permission_change(); DROP TRIGGER IF EXISTS trigger_roles_change ON roles; CREATE TRIGGER trigger_roles_change AFTER INSERT OR UPDATE OR DELETE ON roles FOR EACH ROW EXECUTE FUNCTION notify_permission_change(); COMMENT ON FUNCTION notify_permission_change() IS 'NOTIFY trigger for RBAC permission changes. Sends to permissions_changed channel for API Gateway cache invalidation.'; -- ============================================================================ -- NOTIFY Channels Documentation -- ============================================================================ COMMENT ON EXTENSION pg_notify IS 'PostgreSQL NOTIFY/LISTEN mechanism for real-time configuration updates. Active Channels: - config_changed_trading: Trading service configuration (risk, execution, compliance) - config_changed_backtesting: Backtesting service configuration (strategy, simulation) - config_changed_ml_training: ML training service configuration (models, training) - config_changed_api_gateway: API Gateway configuration (auth, rate limits, routing) - config_changed_global: All configuration changes (monitoring, debugging) - permissions_changed: RBAC permission changes (API Gateway cache invalidation) Payload Format (config_changed_*): { "operation": "INSERT|UPDATE|DELETE", "table": "config_entries|model_config", "key": "risk.max_daily_loss", "value": "100000", "old_value": "50000", "category": "risk", "timestamp": 1730000000.123, "id": "uuid" } Payload Format (permissions_changed): { "operation": "INSERT|UPDATE|DELETE", "table": "role_permissions|user_roles|permissions|roles", "timestamp": 1730000000.123, "role_id": "uuid", "permission_id": "uuid", "user_id": "uuid" } '; -- ============================================================================ -- Test Queries for NOTIFY Verification -- ============================================================================ -- Test 1: Risk configuration change (should notify trading service) -- In psql session 1: LISTEN config_changed_trading; -- In psql session 2: UPDATE config_entries SET value = '100000' WHERE key = 'risk.max_daily_loss'; -- Expected: Session 1 receives NOTIFY with payload showing old/new values -- Test 2: ML configuration change (should notify ml_training service) -- In psql session 1: LISTEN config_changed_ml_training; -- In psql session 2: UPDATE config_entries SET value = '7200' WHERE key = 'ml.model_cache_ttl'; -- Expected: Session 1 receives NOTIFY -- Test 3: Global monitoring (receives all changes) -- In psql session 1: LISTEN config_changed_global; -- In psql session 2: UPDATE config_entries SET value = '15' WHERE key = 'system.latency_target_ns'; -- Expected: Session 1 receives NOTIFY on global channel -- Test 4: Model configuration change (should notify both ml_training and trading) -- In psql session 1: LISTEN config_changed_ml_training; -- In psql session 2: LISTEN config_changed_trading; -- In psql session 3: UPDATE model_config SET is_active = true WHERE name = 'mamba2' AND version = 'v1.2.3'; -- Expected: Both sessions 1 and 2 receive NOTIFY -- Test 5: Permission change (should notify API Gateway) -- In psql session 1: LISTEN permissions_changed; -- In psql session 2: -- INSERT INTO role_permissions (role_id, permission_id) -- SELECT r.id, p.id FROM roles r, permissions p -- WHERE r.name = 'trader' AND p.endpoint = 'risk.view_metrics'; -- Expected: Session 1 receives NOTIFY -- ============================================================================ -- Migration Verification -- ============================================================================ DO $$ DECLARE trigger_count INTEGER; function_count INTEGER; BEGIN -- Count triggers on config_entries SELECT COUNT(*) INTO trigger_count FROM pg_trigger t JOIN pg_class c ON t.tgrelid = c.oid WHERE c.relname = 'config_entries' AND t.tgname LIKE '%notify%'; -- Count notify functions SELECT COUNT(*) INTO function_count FROM pg_proc WHERE proname LIKE 'notify_%_change'; RAISE NOTICE 'Migration 019 Complete:'; RAISE NOTICE ' - NOTIFY triggers on config_entries: %', trigger_count; RAISE NOTICE ' - NOTIFY functions created: %', function_count; RAISE NOTICE ' - Service channels: config_changed_{trading,backtesting,ml_training,api_gateway,global}'; RAISE NOTICE ' - RBAC channel: permissions_changed'; RAISE NOTICE ''; RAISE NOTICE 'To test NOTIFY, run in psql:'; RAISE NOTICE ' Session 1: LISTEN config_changed_trading;'; RAISE NOTICE ' Session 2: UPDATE config_entries SET value = ''999999'' WHERE key = ''risk.max_daily_loss'';'; END $$;