Files
breakpilot-compliance/ai-compliance-sdk/internal/api/handlers/iace_handler_compliance.go
T
Benjamin Admin 1502ac6d8f feat: Kamera/PII-Trigger differenziert + CE × Compliance FAQ
- HP059 Trigger: "DSFA erforderlich" → "zu pruefen" mit Entscheidungslogik
  (Edge-Processing ohne Speicherung/Personenerkennung = keine DSFA)
- 6 FAQ-Eintraege: Kamera-PII, zugekaufte Baugruppen, Herstellererklaerung,
  KI-Hochrisiko, CRA OTA-Updates, verkettete Produktionslinien
- GET /compliance-faq Endpoint mit Kategorie-Filter

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-09 07:25:39 +02:00

107 lines
3.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)
}
// GetComplianceFAQ handles GET /compliance-faq
// Returns CE × Compliance FAQ entries, optionally filtered by ?category=
func (h *IACEHandler) GetComplianceFAQ(c *gin.Context) {
category := c.Query("category")
all := iace.GetComplianceFAQ()
if category != "" {
var filtered []iace.ComplianceFAQEntry
for _, f := range all {
if f.Category == category {
filtered = append(filtered, f)
}
}
c.JSON(http.StatusOK, gin.H{"faq": filtered, "total": len(filtered)})
return
}
c.JSON(http.StatusOK, gin.H{"faq": all, "total": len(all)})
}
// firstOrEmpty returns the first element of a string slice or "".
func firstOrEmpty(ss []string) string {
if len(ss) > 0 {
return ss[0]
}
return ""
}