diff --git a/crates/executor/src/enforcement_processor.rs b/crates/executor/src/enforcement_processor.rs index 5437f2f..a594c5e 100644 --- a/crates/executor/src/enforcement_processor.rs +++ b/crates/executor/src/enforcement_processor.rs @@ -11,15 +11,15 @@ use anyhow::{bail, Result}; use attune_common::{ - models::{Enforcement, Event, Rule}, + models::{Enforcement, EnforcementStatus, Event, Rule}, mq::{ Consumer, EnforcementCreatedPayload, ExecutionRequestedPayload, MessageEnvelope, Publisher, }, repositories::{ - event::{EnforcementRepository, EventRepository}, + event::{EnforcementRepository, EventRepository, UpdateEnforcementInput}, execution::{CreateExecutionInput, ExecutionRepository}, rule::RuleRepository, - Create, FindById, + Create, FindById, Update, }, }; @@ -144,11 +144,40 @@ impl EnforcementProcessor { &rule, ) .await?; + + // Update enforcement status to Processed after successful execution creation + EnforcementRepository::update( + pool, + enforcement_id, + UpdateEnforcementInput { + status: Some(EnforcementStatus::Processed), + payload: None, + }, + ) + .await?; + + debug!("Updated enforcement {} status to Processed", enforcement_id); } else { info!( "Skipping execution creation for enforcement: {}", enforcement_id ); + + // Update enforcement status to Disabled since it was not actionable + EnforcementRepository::update( + pool, + enforcement_id, + UpdateEnforcementInput { + status: Some(EnforcementStatus::Disabled), + payload: None, + }, + ) + .await?; + + debug!( + "Updated enforcement {} status to Disabled (skipped)", + enforcement_id + ); } Ok(()) @@ -215,7 +244,8 @@ impl EnforcementProcessor { ); bail!( "Rule {} references a deleted action (action_ref: {})", - rule.id, rule.action_ref + rule.id, + rule.action_ref ); } }; diff --git a/crates/notifier/src/postgres_listener.rs b/crates/notifier/src/postgres_listener.rs index d603315..31f1f59 100644 --- a/crates/notifier/src/postgres_listener.rs +++ b/crates/notifier/src/postgres_listener.rs @@ -15,6 +15,7 @@ const NOTIFICATION_CHANNELS: &[&str] = &[ "inquiry_created", "inquiry_responded", "enforcement_created", + "enforcement_status_changed", "event_created", "workflow_execution_status_changed", ]; @@ -167,6 +168,8 @@ mod tests { fn test_notification_channels_defined() { assert!(!NOTIFICATION_CHANNELS.is_empty()); assert!(NOTIFICATION_CHANNELS.contains(&"execution_status_changed")); + assert!(NOTIFICATION_CHANNELS.contains(&"enforcement_created")); + assert!(NOTIFICATION_CHANNELS.contains(&"enforcement_status_changed")); assert!(NOTIFICATION_CHANNELS.contains(&"inquiry_created")); } diff --git a/migrations/20250101000008_notify_triggers.sql b/migrations/20250101000008_notify_triggers.sql index 3d39db3..f8562b0 100644 --- a/migrations/20250101000008_notify_triggers.sql +++ b/migrations/20250101000008_notify_triggers.sql @@ -180,6 +180,47 @@ CREATE TRIGGER enforcement_created_notify COMMENT ON FUNCTION notify_enforcement_created() IS 'Sends enforcement creation notifications via PostgreSQL LISTEN/NOTIFY'; +-- Function to notify on enforcement status changes +CREATE OR REPLACE FUNCTION notify_enforcement_status_changed() +RETURNS TRIGGER AS $$ +DECLARE + payload JSON; +BEGIN + -- Only notify on updates when status actually changed + IF TG_OP = 'UPDATE' AND OLD.status IS DISTINCT FROM NEW.status THEN + payload := json_build_object( + 'entity_type', 'enforcement', + 'entity_id', NEW.id, + 'id', NEW.id, + 'rule', NEW.rule, + 'rule_ref', NEW.rule_ref, + 'trigger_ref', NEW.trigger_ref, + 'event', NEW.event, + 'status', NEW.status, + 'old_status', OLD.status, + 'condition', NEW.condition, + 'conditions', NEW.conditions, + 'config', NEW.config, + 'payload', NEW.payload, + 'created', NEW.created, + 'updated', NEW.updated + ); + + PERFORM pg_notify('enforcement_status_changed', payload::text); + END IF; + + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +-- Trigger on enforcement table for status changes +CREATE TRIGGER enforcement_status_changed_notify + AFTER UPDATE ON enforcement + FOR EACH ROW + EXECUTE FUNCTION notify_enforcement_status_changed(); + +COMMENT ON FUNCTION notify_enforcement_status_changed() IS 'Sends enforcement status change notifications via PostgreSQL LISTEN/NOTIFY'; + -- ============================================================================ -- INQUIRY NOTIFICATIONS -- ============================================================================ diff --git a/web/src/hooks/useEnforcementStream.ts b/web/src/hooks/useEnforcementStream.ts index 8942419..5754b96 100644 --- a/web/src/hooks/useEnforcementStream.ts +++ b/web/src/hooks/useEnforcementStream.ts @@ -90,6 +90,12 @@ export function useEnforcementStream( // Extract enforcement data from notification payload (flat structure) const enforcementData = notification.payload as any; + // Invalidate history queries so the EntityHistoryPanel picks up new records + // (e.g. status changes recorded by the enforcement_history trigger) + queryClient.invalidateQueries({ + queryKey: ["history", "enforcement", notification.entity_id], + }); + // Update specific enforcement query if it exists queryClient.setQueryData( ["enforcements", notification.entity_id],