package handlers import ( "net/http" "github.com/breakpilot/ai-compliance-sdk/internal/iace" "github.com/gin-gonic/gin" "github.com/google/uuid" ) // ============================================================================ // CE x Compliance Crossover Engine // ============================================================================ // GetComplianceTriggers handles GET /projects/:id/compliance-triggers. // It analyses the project's hazards and component patterns to determine // which DSGVO, AI Act, CRA, NIS2, and EU Data Act obligations are triggered. // The response includes deduplicated triggers sorted by severity, plus boolean // summary flags (dsfa_required, ai_act_relevant, cra_relevant, etc.). func (h *IACEHandler) GetComplianceTriggers(c *gin.Context) { projectID, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid project ID"}) return } // Verify project exists project, err := h.store.GetProject(c.Request.Context(), projectID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } if project == nil { c.JSON(http.StatusNotFound, gin.H{"error": "project not found"}) return } // Fetch all hazards for this project hazards, err := h.store.ListHazards(c.Request.Context(), projectID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to load hazards: " + err.Error()}) return } // Also run pattern matching with component tags to catch tag-based triggers. // Collect tags from the project's components (reuse the norms handler logic). componentTags := collectComponentTags(h, c, projectID) // Get all patterns from the pattern library allPatterns := iace.AllPatterns() // Additionally derive extra fired patterns by re-matching component tags // against the pattern engine. This ensures patterns that are not yet // applied as hazards still contribute their compliance triggers. engine := iace.NewPatternEngine() matchInput := iace.MatchInput{ CustomTags: componentTags, } matchResult := engine.Match(matchInput) // Merge matched pattern IDs into a pseudo-hazard list so the crossover // engine picks them up. We create lightweight Hazard structs with the // pattern ID embedded in the Description field. mergedHazards := make([]iace.Hazard, len(hazards)) copy(mergedHazards, hazards) for _, pm := range matchResult.MatchedPatterns { mergedHazards = append(mergedHazards, iace.Hazard{ Name: pm.PatternName, Description: "Pattern " + pm.PatternID, Category: firstOrEmpty(pm.HazardCats), }) } // Run the crossover engine summary := iace.GetProjectComplianceTriggers(mergedHazards, allPatterns) c.JSON(http.StatusOK, summary) } // firstOrEmpty returns the first element of a string slice or "". func firstOrEmpty(ss []string) string { if len(ss) > 0 { return ss[0] } return "" }