From 56892cf7dc6b8e291c35aed6e0c4dcd1595c2758 Mon Sep 17 00:00:00 2001 From: Benjamin Admin Date: Thu, 7 May 2026 15:07:22 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20CE=20=C3=97=20Compliance=20Crossover=20?= =?UTF-8?q?Engine?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Automatische Erkennung von DSGVO/AI Act/CRA/NIS2/Data Act Implikationen bei CE-Gefaehrdungen. 50 Trigger-Mappings auf Hazard-Patterns → Compliance-Module mit Modul-Links. - compliance_triggers.go: 50 Pattern→Regulation Mappings - compliance_crossover.go: Engine die Projekt-Hazards gegen Trigger prueft - iace_handler_compliance.go: GET /compliance-triggers API - ComplianceAlerts.tsx: Frontend Alert-Panel auf Projekt-Uebersicht Co-Authored-By: Claude Opus 4.6 (1M context) --- .../_components/ComplianceAlerts.tsx | 218 +++++++++ .../app/sdk/iace/[projectId]/page.tsx | 4 + .../api/handlers/iace_handler_compliance.go | 86 ++++ ai-compliance-sdk/internal/app/routes.go | 3 + .../internal/iace/compliance_crossover.go | 254 ++++++++++ .../internal/iace/compliance_triggers.go | 439 ++++++++++++++++++ 6 files changed, 1004 insertions(+) create mode 100644 admin-compliance/app/sdk/iace/[projectId]/_components/ComplianceAlerts.tsx create mode 100644 ai-compliance-sdk/internal/api/handlers/iace_handler_compliance.go create mode 100644 ai-compliance-sdk/internal/iace/compliance_crossover.go create mode 100644 ai-compliance-sdk/internal/iace/compliance_triggers.go diff --git a/admin-compliance/app/sdk/iace/[projectId]/_components/ComplianceAlerts.tsx b/admin-compliance/app/sdk/iace/[projectId]/_components/ComplianceAlerts.tsx new file mode 100644 index 0000000..1143ccd --- /dev/null +++ b/admin-compliance/app/sdk/iace/[projectId]/_components/ComplianceAlerts.tsx @@ -0,0 +1,218 @@ +'use client' + +import { useState, useEffect } from 'react' +import Link from 'next/link' + +interface ComplianceTrigger { + id: string + regulation: string + article: string + title: string + severity: 'high' | 'medium' | 'low' + reason: string + affected_hazard_count?: number + module_path: string + module_label: string +} + +interface TriggersResponse { + triggers: ComplianceTrigger[] + total: number +} + +const SEVERITY_CONFIG: Record = { + high: { + border: 'border-red-200 dark:border-red-800', + bg: 'bg-red-50 dark:bg-red-900/20', + text: 'text-red-700 dark:text-red-400', + badge: 'bg-red-100 text-red-800 dark:bg-red-900/50 dark:text-red-300', + icon: 'text-red-500', + }, + medium: { + border: 'border-yellow-200 dark:border-yellow-800', + bg: 'bg-yellow-50 dark:bg-yellow-900/20', + text: 'text-yellow-700 dark:text-yellow-400', + badge: 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900/50 dark:text-yellow-300', + icon: 'text-yellow-500', + }, + low: { + border: 'border-blue-200 dark:border-blue-800', + bg: 'bg-blue-50 dark:bg-blue-900/20', + text: 'text-blue-700 dark:text-blue-400', + badge: 'bg-blue-100 text-blue-800 dark:bg-blue-900/50 dark:text-blue-300', + icon: 'text-blue-500', + }, +} + +const SEVERITY_LABELS: Record = { + high: 'HOCH', + medium: 'MITTEL', + low: 'NIEDRIG', +} + +const REGULATION_BADGES: { key: string; label: string; activeColor: string }[] = [ + { key: 'DSGVO', label: 'DSGVO', activeColor: 'bg-red-100 text-red-800 border-red-300' }, + { key: 'AI Act', label: 'AI Act', activeColor: 'bg-orange-100 text-orange-800 border-orange-300' }, + { key: 'CRA', label: 'CRA', activeColor: 'bg-yellow-100 text-yellow-800 border-yellow-300' }, + { key: 'NIS2', label: 'NIS2', activeColor: 'bg-indigo-100 text-indigo-800 border-indigo-300' }, + { key: 'Data Act', label: 'Data Act', activeColor: 'bg-amber-100 text-amber-800 border-amber-300' }, +] + +function WarningIcon({ className }: { className?: string }) { + return ( + + + + ) +} + +function ChevronIcon({ open }: { open: boolean }) { + return ( + + + + ) +} + +export function ComplianceAlerts({ projectId }: { projectId: string }) { + const [data, setData] = useState(null) + const [loading, setLoading] = useState(true) + const [collapsed, setCollapsed] = useState(false) + const [expandedIds, setExpandedIds] = useState>(new Set()) + + useEffect(() => { + fetch(`/api/sdk/v1/iace/projects/${projectId}/compliance-triggers`) + .then((r) => (r.ok ? r.json() : null)) + .then((json) => { + if (json?.triggers) setData(json) + else if (Array.isArray(json)) setData({ triggers: json, total: json.length }) + }) + .catch(() => {}) + .finally(() => setLoading(false)) + }, [projectId]) + + if (loading) return null + if (!data || data.triggers.length === 0) return null + + const triggers = data.triggers + const activeRegulations = new Set(triggers.map((t) => t.regulation)) + + function toggleExpanded(id: string) { + setExpandedIds((prev) => { + const next = new Set(prev) + if (next.has(id)) next.delete(id) + else next.add(id) + return next + }) + } + + return ( +
+ {/* Header */} + + + {!collapsed && ( +
+ {/* Regulation summary badges */} +
+ {REGULATION_BADGES.map((reg) => { + const active = activeRegulations.has(reg.key) + return ( + + {reg.label} + + ) + })} +
+ + {/* Trigger list */} +
+ {triggers.map((trigger) => { + const sev = SEVERITY_CONFIG[trigger.severity] || SEVERITY_CONFIG.low + const isOpen = expandedIds.has(trigger.id) + + return ( +
+ {/* Trigger header row */} + + + {/* Expanded detail */} + {isOpen && ( +
+

+ Grund: {trigger.reason} +

+ {trigger.affected_hazard_count != null && trigger.affected_hazard_count > 0 && ( +

+ Betroffene Gefaehrdungen: {trigger.affected_hazard_count} +

+ )} + + {trigger.module_label} oeffnen + + + + +
+ )} +
+ ) + })} +
+ + {/* Disclaimer */} +
+ Hinweis: Diese Compliance-Hinweise werden automatisch aus den + Gefaehrdungen und Klassifikationen abgeleitet. Der CE-Fachmann muss die + regulatorischen Anforderungen im jeweiligen Modul verifizieren. +
+
+ )} +
+ ) +} diff --git a/admin-compliance/app/sdk/iace/[projectId]/page.tsx b/admin-compliance/app/sdk/iace/[projectId]/page.tsx index 9491baa..d289147 100644 --- a/admin-compliance/app/sdk/iace/[projectId]/page.tsx +++ b/admin-compliance/app/sdk/iace/[projectId]/page.tsx @@ -4,6 +4,7 @@ import React, { useState, useEffect } from 'react' import Link from 'next/link' import { useParams } from 'next/navigation' import { SuggestedNorms } from './_components/SuggestedNorms' +import { ComplianceAlerts } from './_components/ComplianceAlerts' interface ProjectOverview { id: string @@ -349,6 +350,9 @@ export default function ProjectOverviewPage() { + {/* Compliance Alerts */} + + {/* Suggested Norms */} diff --git a/ai-compliance-sdk/internal/api/handlers/iace_handler_compliance.go b/ai-compliance-sdk/internal/api/handlers/iace_handler_compliance.go new file mode 100644 index 0000000..3a7993a --- /dev/null +++ b/ai-compliance-sdk/internal/api/handlers/iace_handler_compliance.go @@ -0,0 +1,86 @@ +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 "" +} diff --git a/ai-compliance-sdk/internal/app/routes.go b/ai-compliance-sdk/internal/app/routes.go index ea43dd9..497060b 100644 --- a/ai-compliance-sdk/internal/app/routes.go +++ b/ai-compliance-sdk/internal/app/routes.go @@ -422,6 +422,9 @@ func registerIACERoutes(v1 *gin.RouterGroup, h *handlers.IACEHandler) { iaceRoutes.GET("/production-lines/:lid/dashboard", h.GetProductionLineDashboard) iaceRoutes.POST("/production-lines/:lid/stations", h.AddStationToLine) iaceRoutes.DELETE("/production-lines/:lid/stations/:sid", h.RemoveStationFromLine) + + // CE x Compliance Crossover + iaceRoutes.GET("/projects/:id/compliance-triggers", h.GetComplianceTriggers) } } diff --git a/ai-compliance-sdk/internal/iace/compliance_crossover.go b/ai-compliance-sdk/internal/iace/compliance_crossover.go new file mode 100644 index 0000000..f99d129 --- /dev/null +++ b/ai-compliance-sdk/internal/iace/compliance_crossover.go @@ -0,0 +1,254 @@ +package iace + +import "sort" + +// GetProjectComplianceTriggers analyses a project's hazards and the full +// pattern library to determine which DSGVO/AI Act/CRA/NIS2/Data Act +// obligations are triggered. It returns a deduplicated, severity-sorted +// summary with boolean flags for each regulation family. +func GetProjectComplianceTriggers(hazards []Hazard, patterns []HazardPattern) *ComplianceTriggerSummary { + triggerMap := GetComplianceTriggerMap() + + // Build set of pattern IDs present in the pattern library for quick lookup + patternByID := make(map[string]HazardPattern, len(patterns)) + for _, p := range patterns { + patternByID[p.ID] = p + } + + // Collect all fired pattern IDs from the project's hazards. + // Hazards created from pattern matching store the source pattern ID + // in their Description ("Pattern HPXXX") or Name field. + firedPatterns := make(map[string]bool) + for _, h := range hazards { + extractPatternIDs(h.Description, firedPatterns) + extractPatternIDs(h.Name, firedPatterns) + extractPatternIDs(h.Scenario, firedPatterns) + } + + // Also check each pattern against the hazard categories present + // in the project — if a pattern generates a category that exists + // among the hazards, consider the pattern relevant. + hazardCats := make(map[string]bool) + for _, h := range hazards { + if h.Category != "" { + hazardCats[h.Category] = true + } + } + for _, p := range patterns { + for _, cat := range p.GeneratedHazardCats { + if hazardCats[cat] { + firedPatterns[p.ID] = true + break + } + } + } + + // Collect tag-level information from hazard metadata + tags := collectHazardTags(hazards) + tagTriggers := GetTagBasedTriggers(tags) + + // Gather all triggers from fired patterns + var results []TriggerResult + seenRegulation := make(map[string]bool) + + for pid := range firedPatterns { + triggers, ok := triggerMap[pid] + if !ok { + continue + } + for _, t := range triggers { + key := t.Regulation + "|" + t.Module + if seenRegulation[key] { + continue + } + seenRegulation[key] = true + + // Try to find a hazard name for context + hName := findHazardNameForPattern(pid, hazards) + results = append(results, TriggerResult{ + HazardID: "", + HazardName: hName, + PatternID: pid, + Trigger: t, + }) + } + } + + // Append tag-based triggers (deduplicated against pattern triggers) + for _, t := range tagTriggers { + key := t.Regulation + "|" + t.Module + if seenRegulation[key] { + continue + } + seenRegulation[key] = true + results = append(results, TriggerResult{ + HazardID: "", + HazardName: "Tag-basiert", + PatternID: "", + Trigger: t, + }) + } + + // Sort by severity: high > medium > low + sort.Slice(results, func(i, j int) bool { + return severityRank(results[i].Trigger.Severity) > severityRank(results[j].Trigger.Severity) + }) + + // Build boolean summary flags + summary := buildSummaryFlags(results) + + return &ComplianceTriggerSummary{ + Triggers: results, + Total: len(results), + Summary: summary, + } +} + +// AllPatterns returns every hazard pattern from all pattern sources. +// This mirrors the aggregation in NewPatternEngine but returns just the slice. +func AllPatterns() []HazardPattern { + p := GetBuiltinHazardPatterns() + p = append(p, GetExtendedHazardPatterns()...) + p = append(p, GetPressHazardPatterns()...) + p = append(p, GetCobotHazardPatterns()...) + p = append(p, GetOperationalHazardPatterns()...) + p = append(p, GetDGUVExtendedPatterns()...) + p = append(p, GetExtendedHazardPatterns2()...) + p = append(p, GetElevatorPatterns()...) + p = append(p, GetAGVAgriPatterns()...) + p = append(p, GetFoodProcessingPatterns()...) + p = append(p, GetPackagingPatterns()...) + p = append(p, GetLaserPatterns()...) + p = append(p, GetMedicalDevicePatterns()...) + p = append(p, GetPressureEquipmentPatterns()...) + p = append(p, GetConstructionPatterns()...) + p = append(p, GetForestryConveyorPatterns()...) + p = append(p, GetPlasticsMetalPatterns()...) + p = append(p, GetWeldingGlassTextilePatterns()...) + p = append(p, GetSpecificMachinePatterns()...) + p = append(p, GetSpecificMachinePatterns2()...) + p = append(p, GetCyberExtendedPatterns()...) + p = append(p, GetCyberExtendedPatterns2()...) + p = append(p, GetCyberExtendedPatterns3()...) + p = append(p, GetWorkshopPatterns()...) + p = append(p, GetMaintenanceExtPatterns()...) + p = append(p, GetFinalPatternsA()...) + p = append(p, GetFinalPatternsB()...) + p = append(p, GetFinalPatternsC()...) + p = append(p, GetFinalPatternsD()...) + return p +} + +// extractPatternIDs scans a text for "HP" followed by digits and adds +// any found pattern IDs to the set. +func extractPatternIDs(text string, set map[string]bool) { + for i := 0; i < len(text)-2; i++ { + if text[i] == 'H' && text[i+1] == 'P' && i+2 < len(text) && text[i+2] >= '0' && text[i+2] <= '9' { + end := i + 2 + for end < len(text) && text[end] >= '0' && text[end] <= '9' { + end++ + } + set[text[i:end]] = true + } + } +} + +// findHazardNameForPattern returns the name of the first hazard whose +// description/name/scenario mentions the given pattern ID. +func findHazardNameForPattern(pid string, hazards []Hazard) string { + for _, h := range hazards { + if containsPatternID(h.Description, pid) || containsPatternID(h.Name, pid) || containsPatternID(h.Scenario, pid) { + return h.Name + } + } + if len(hazards) > 0 { + return hazards[0].Name + } + return "" +} + +// containsPatternID checks whether text contains the exact pattern ID token. +func containsPatternID(text, pid string) bool { + idx := 0 + for idx <= len(text)-len(pid) { + if text[idx:idx+len(pid)] == pid { + // Ensure it is not a substring of a longer ID + after := idx + len(pid) + if after >= len(text) || text[after] < '0' || text[after] > '9' { + return true + } + } + idx++ + } + return false +} + +// collectHazardTags extracts tag-like signals from hazard fields. +func collectHazardTags(hazards []Hazard) []string { + tagSet := make(map[string]bool) + for _, h := range hazards { + // Infer tags from hazard category names + switch h.Category { + case "software", "steuerung", "steuerungsfehler": + tagSet["has_software"] = true + tagSet["programmable"] = true + case "cyber", "cybersicherheit", "netzwerk": + tagSet["is_networked"] = true + tagSet["has_software"] = true + case "ki", "kuenstliche_intelligenz", "ai_ml": + tagSet["has_ai"] = true + tagSet["has_software"] = true + case "sensorik", "sensor": + tagSet["sensor_part"] = true + } + } + tags := make([]string, 0, len(tagSet)) + for t := range tagSet { + tags = append(tags, t) + } + return tags +} + +// severityRank maps severity strings to sort-order integers. +func severityRank(s string) int { + switch s { + case "high": + return 3 + case "medium": + return 2 + case "low": + return 1 + default: + return 0 + } +} + +// buildSummaryFlags derives boolean flags from the collected trigger results. +func buildSummaryFlags(results []TriggerResult) map[string]bool { + summary := map[string]bool{ + "dsfa_required": false, + "ai_act_relevant": false, + "cra_relevant": false, + "nis2_relevant": false, + "data_act_relevant": false, + } + for _, r := range results { + reg := r.Trigger.Regulation + if len(reg) >= 4 && reg[:4] == "DSGV" { + summary["dsfa_required"] = true + } + if len(reg) >= 6 && reg[:6] == "AI Act" { + summary["ai_act_relevant"] = true + } + if len(reg) >= 3 && reg[:3] == "CRA" { + summary["cra_relevant"] = true + } + if len(reg) >= 4 && reg[:4] == "NIS2" { + summary["nis2_relevant"] = true + } + if len(reg) >= 11 && reg[:11] == "EU Data Act" { + summary["data_act_relevant"] = true + } + } + return summary +} diff --git a/ai-compliance-sdk/internal/iace/compliance_triggers.go b/ai-compliance-sdk/internal/iace/compliance_triggers.go new file mode 100644 index 0000000..419414b --- /dev/null +++ b/ai-compliance-sdk/internal/iace/compliance_triggers.go @@ -0,0 +1,439 @@ +package iace + +// ComplianceTrigger maps a CE hazard pattern to a regulatory requirement. +// When a pattern fires for a project, the corresponding triggers tell +// the user which DSGVO/AI Act/CRA/NIS2/Data Act obligations apply and +// which SDK module they should visit. +type ComplianceTrigger struct { + Regulation string `json:"regulation"` // e.g. "DSGVO Art. 35" + TriggerCondDE string `json:"trigger_cond_de"` // Why this triggers (German) + Severity string `json:"severity"` // "high", "medium", "low" + Module string `json:"module"` // SDK module key + ModuleLink string `json:"module_link"` // Frontend route + ActionDE string `json:"action_de"` // Recommended action (German) + RAGQuery string `json:"rag_query"` // Search query for RAG enrichment +} + +// TriggerResult pairs a fired pattern with one of its compliance triggers. +type TriggerResult struct { + HazardID string `json:"hazard_id"` + HazardName string `json:"hazard_name"` + PatternID string `json:"pattern_id"` + Trigger ComplianceTrigger `json:"trigger"` +} + +// ComplianceTriggerSummary is the top-level response for the crossover engine. +type ComplianceTriggerSummary struct { + Triggers []TriggerResult `json:"triggers"` + Total int `json:"total"` + Summary map[string]bool `json:"summary"` // dsfa_required, ai_act_relevant, etc. +} + +// GetComplianceTriggerMap returns pattern-ID-keyed compliance triggers. +// Each entry lists the regulatory obligations that a fired pattern implies. +func GetComplianceTriggerMap() map[string][]ComplianceTrigger { + m := make(map[string][]ComplianceTrigger) + + // --- Cobot / camera / biometric patterns --- + m["HP059"] = []ComplianceTrigger{ + { + Regulation: "DSGVO Art. 35", + TriggerCondDE: "Kamera-Personenerkennung verarbeitet biometrische Daten", + Severity: "high", + Module: "dsfa", + ModuleLink: "/sdk/dsfa", + ActionDE: "Datenschutz-Folgenabschaetzung fuer Kamera-System durchfuehren", + RAGQuery: "DSFA biometrische Daten Kameraerkennung", + }, + { + Regulation: "AI Act Art. 6", + TriggerCondDE: "Autonome Sicherheitsentscheidung durch KI-System", + Severity: "high", + Module: "ai-act", + ModuleLink: "/sdk/ai-act", + ActionDE: "Hochrisiko-KI-Einstufung pruefen und dokumentieren", + RAGQuery: "AI Act Hochrisiko autonome Sicherheitsentscheidung", + }, + } + m["HP060"] = []ComplianceTrigger{ + { + Regulation: "DSGVO Art. 35", + TriggerCondDE: "Werkzeug-Tracking erfordert Personenerkennung", + Severity: "high", + Module: "dsfa", + ModuleLink: "/sdk/dsfa", + ActionDE: "DSFA fuer Werkzeug-Tracking mit Personenerkennung erstellen", + RAGQuery: "DSFA Personenerkennung Werkzeug-Tracking", + }, + } + + // --- AI/ML safety-critical patterns --- + m["HP040"] = []ComplianceTrigger{ + { + Regulation: "AI Act Art. 6", + TriggerCondDE: "KI trifft sicherheitsrelevante Entscheidung", + Severity: "high", + Module: "ai-act", + ModuleLink: "/sdk/ai-act", + ActionDE: "Hochrisiko-Klassifizierung und Konformitaetsbewertung einleiten", + RAGQuery: "AI Act Art 6 Hochrisiko Sicherheitsentscheidung", + }, + { + Regulation: "AI Act Art. 9", + TriggerCondDE: "Risikomanagement fuer Hochrisiko-KI erforderlich", + Severity: "high", + Module: "ai-act", + ModuleLink: "/sdk/ai-act", + ActionDE: "Risikomanagementsystem nach Art. 9 AI Act aufsetzen", + RAGQuery: "AI Act Art 9 Risikomanagementsystem Hochrisiko", + }, + } + m["HP041"] = []ComplianceTrigger{ + { + Regulation: "AI Act Art. 14", + TriggerCondDE: "Menschliche Aufsicht ueber KI-System erforderlich", + Severity: "high", + Module: "ai-act", + ModuleLink: "/sdk/ai-act", + ActionDE: "Human-Oversight-Mechanismus implementieren und dokumentieren", + RAGQuery: "AI Act Art 14 menschliche Aufsicht Human Oversight", + }, + } + m["HP042"] = []ComplianceTrigger{ + { + Regulation: "AI Act Art. 6", + TriggerCondDE: "Bias in sicherheitsrelevanter KI moeglich", + Severity: "high", + Module: "ai-act", + ModuleLink: "/sdk/ai-act", + ActionDE: "Bias-Analyse und Datenqualitaetspruefung durchfuehren", + RAGQuery: "AI Act Bias Diskriminierung Sicherheits-KI", + }, + } + m["HP043"] = []ComplianceTrigger{ + { + Regulation: "AI Act Art. 11", + TriggerCondDE: "Technische Dokumentation fuer KI-System erforderlich", + Severity: "medium", + Module: "ai-act", + ModuleLink: "/sdk/ai-act", + ActionDE: "Technische Dokumentation nach Anhang IV AI Act erstellen", + RAGQuery: "AI Act Art 11 technische Dokumentation Anhang IV", + }, + } + m["HP044"] = []ComplianceTrigger{ + { + Regulation: "AI Act Art. 13", + TriggerCondDE: "Transparenz-Anforderungen fuer KI-System", + Severity: "medium", + Module: "ai-act", + ModuleLink: "/sdk/ai-act", + ActionDE: "Transparenzhinweise und Nutzerdokumentation bereitstellen", + RAGQuery: "AI Act Art 13 Transparenz KI Nutzerinformation", + }, + } + + // --- Cyber Resilience Act (software/firmware) --- + m["HP033"] = []ComplianceTrigger{ + { + Regulation: "CRA Art. 10", + TriggerCondDE: "Schwachstellenmanagement fuer Software-Komponente", + Severity: "high", + Module: "cyber", + ModuleLink: "/sdk/security-backlog", + ActionDE: "Vulnerability-Management-Prozess nach CRA einrichten", + RAGQuery: "CRA Art 10 Schwachstellenmanagement Software", + }, + { + Regulation: "CRA Art. 13", + TriggerCondDE: "Sicherheitsupdates muessen bereitgestellt werden", + Severity: "medium", + Module: "cyber", + ModuleLink: "/sdk/security-backlog", + ActionDE: "Update-Strategie und Patch-Management dokumentieren", + RAGQuery: "CRA Art 13 Sicherheitsupdates Patch-Management", + }, + } + m["HP158"] = []ComplianceTrigger{ + { + Regulation: "CRA Art. 10", + TriggerCondDE: "Schwachstelle in Firmware erfordert Vulnerability-Handling", + Severity: "high", + Module: "cyber", + ModuleLink: "/sdk/security-backlog", + ActionDE: "Schwachstellenmeldung und Patch-Prozess nach CRA etablieren", + RAGQuery: "CRA Art 10 Firmware Schwachstelle Meldepflicht", + }, + { + Regulation: "CRA Art. 11", + TriggerCondDE: "Meldepflicht bei bekannter Schwachstelle", + Severity: "high", + Module: "cyber", + ModuleLink: "/sdk/security-backlog", + ActionDE: "Meldeprozess an ENISA/BSI fuer Schwachstellen einrichten", + RAGQuery: "CRA Art 11 Meldepflicht ENISA Schwachstelle", + }, + } + m["HP159"] = []ComplianceTrigger{ + { + Regulation: "CRA Art. 10", + TriggerCondDE: "Datenintegritaet der Software muss sichergestellt sein", + Severity: "medium", + Module: "cyber", + ModuleLink: "/sdk/security-backlog", + ActionDE: "Integritaetsschutz fuer Software-Artefakte implementieren", + RAGQuery: "CRA Art 10 Datenintegritaet Software Signierung", + }, + } + m["HP160"] = []ComplianceTrigger{ + { + Regulation: "NIS2 Art. 21", + TriggerCondDE: "Cybersicherheits-Risikomanagement erforderlich", + Severity: "high", + Module: "cyber", + ModuleLink: "/sdk/security-backlog", + ActionDE: "Cybersicherheits-Risikomanagement nach NIS2 Art. 21 aufsetzen", + RAGQuery: "NIS2 Art 21 Cybersicherheit Risikomanagement", + }, + { + Regulation: "CRA Anhang I", + TriggerCondDE: "Wesentliche Cybersicherheits-Anforderungen nach CRA", + Severity: "high", + Module: "cyber", + ModuleLink: "/sdk/security-backlog", + ActionDE: "CRA Anhang I Checkliste fuer Produkt-Cybersicherheit abarbeiten", + RAGQuery: "CRA Anhang I wesentliche Anforderungen Cybersicherheit", + }, + } + + // --- Logging/monitoring patterns --- + m["HP131"] = []ComplianceTrigger{ + { + Regulation: "DSGVO Art. 6", + TriggerCondDE: "Rechtsgrundlage fuer Protokollierung personenbez. Daten", + Severity: "medium", + Module: "dsfa", + ModuleLink: "/sdk/dsfa", + ActionDE: "Rechtsgrundlage fuer Protokollierung pruefen und dokumentieren", + RAGQuery: "DSGVO Art 6 Rechtsgrundlage Protokollierung Logging", + }, + } + + // --- AGV / movement profile patterns (HP199-HP213) --- + agvIDs := genPatternRange("HP", 199, 213) + for _, pid := range agvIDs { + m[pid] = []ComplianceTrigger{ + { + Regulation: "DSGVO Art. 35", + TriggerCondDE: "AGV-Bewegungsprofile koennen Rueckschluesse auf Personen erlauben", + Severity: "high", + Module: "dsfa", + ModuleLink: "/sdk/dsfa", + ActionDE: "DSFA fuer AGV-Bewegungsdaten erstellen", + RAGQuery: "DSFA Bewegungsprofile AGV Personenbezug", + }, + { + Regulation: "EU Data Act Art. 3", + TriggerCondDE: "Maschinendaten-Zugangsrecht fuer Nutzer nach Data Act", + Severity: "medium", + Module: "vendor-compliance", + ModuleLink: "/sdk/vendor-compliance", + ActionDE: "Datenzugangsrechte nach EU Data Act fuer Maschinendaten pruefen", + RAGQuery: "EU Data Act Art 3 Maschinendaten Zugangsrecht", + }, + } + } + + // --- Cyber-security patterns HP800-HP814 --- + cyberIDs1 := genPatternRange("HP", 800, 814) + for _, pid := range cyberIDs1 { + m[pid] = []ComplianceTrigger{ + { + Regulation: "NIS2 Art. 21", + TriggerCondDE: "Cybersicherheits-Risikomanagement fuer vernetzte Komponente", + Severity: "high", + Module: "cyber", + ModuleLink: "/sdk/security-backlog", + ActionDE: "NIS2 Cybersicherheits-Massnahmen pruefen und dokumentieren", + RAGQuery: "NIS2 Art 21 Cybersicherheit vernetzte Maschine", + }, + { + Regulation: "CRA Art. 10", + TriggerCondDE: "Schwachstellenmanagement fuer vernetzte Komponente", + Severity: "high", + Module: "cyber", + ModuleLink: "/sdk/security-backlog", + ActionDE: "CRA-konforme Schwachstellenbehandlung einrichten", + RAGQuery: "CRA Art 10 Schwachstellenmanagement vernetzte Maschine", + }, + } + } + + // --- Cyber-security patterns HP815-HP829 --- + cyberIDs2 := genPatternRange("HP", 815, 829) + for _, pid := range cyberIDs2 { + m[pid] = []ComplianceTrigger{ + { + Regulation: "NIS2 Art. 21", + TriggerCondDE: "Netzwerk-Sicherheitsmassnahmen nach NIS2", + Severity: "high", + Module: "cyber", + ModuleLink: "/sdk/security-backlog", + ActionDE: "NIS2-Sicherheitskonzept fuer Netzwerkkomponenten erstellen", + RAGQuery: "NIS2 Art 21 Netzwerk Sicherheit Massnahmen", + }, + { + Regulation: "CRA Art. 10", + TriggerCondDE: "CRA-Anforderungen fuer Software mit Netzwerkzugang", + Severity: "medium", + Module: "cyber", + ModuleLink: "/sdk/security-backlog", + ActionDE: "CRA-Konformitaet fuer Netzwerk-Software sicherstellen", + RAGQuery: "CRA Software Netzwerkzugang Sicherheitsanforderungen", + }, + } + } + + // --- AI/ML-specific cyber patterns HP830-HP844 --- + aiCyberIDs := genPatternRange("HP", 830, 844) + for _, pid := range aiCyberIDs { + m[pid] = []ComplianceTrigger{ + { + Regulation: "AI Act Art. 6", + TriggerCondDE: "KI/ML-System in sicherheitsrelevantem Kontext", + Severity: "high", + Module: "ai-act", + ModuleLink: "/sdk/ai-act", + ActionDE: "Hochrisiko-Einstufung und AI-Act-Konformitaet pruefen", + RAGQuery: "AI Act Hochrisiko KI ML sicherheitsrelevant", + }, + { + Regulation: "DSGVO Art. 22", + TriggerCondDE: "Automatisierte Entscheidungsfindung durch KI moeglich", + Severity: "high", + Module: "dsfa", + ModuleLink: "/sdk/dsfa", + ActionDE: "Automatisierte Einzelentscheidung nach Art. 22 DSGVO pruefen", + RAGQuery: "DSGVO Art 22 automatisierte Entscheidung KI Profiling", + }, + } + } + + // --- NIS2 network/HMI patterns HP845-HP864 --- + nis2IDs := genPatternRange("HP", 845, 864) + for _, pid := range nis2IDs { + m[pid] = []ComplianceTrigger{ + { + Regulation: "NIS2 Art. 21", + TriggerCondDE: "Netzwerk-/HMI-Komponente erfordert NIS2-Massnahmen", + Severity: "high", + Module: "cyber", + ModuleLink: "/sdk/security-backlog", + ActionDE: "NIS2-Sicherheitsanforderungen fuer HMI/Netzwerk umsetzen", + RAGQuery: "NIS2 Art 21 HMI Netzwerk Sicherheit", + }, + } + } + + return m +} + +// GetTagBasedTriggers returns compliance triggers that fire based on +// component tag combinations rather than specific pattern IDs. +func GetTagBasedTriggers(tags []string) []ComplianceTrigger { + tagSet := make(map[string]bool, len(tags)) + for _, t := range tags { + tagSet[t] = true + } + + var triggers []ComplianceTrigger + + // has_software + programmable → CRA Art. 10 + if tagSet["has_software"] && tagSet["programmable"] { + triggers = append(triggers, ComplianceTrigger{ + Regulation: "CRA Art. 10", + TriggerCondDE: "Programmierbare Software-Komponente erfordert CRA-Konformitaet", + Severity: "medium", + Module: "cyber", + ModuleLink: "/sdk/security-backlog", + ActionDE: "CRA-Anforderungen fuer programmierbare Software pruefen", + RAGQuery: "CRA Art 10 programmierbare Software Sicherheit", + }) + } + + // sensor_part + has_software → EU Data Act Art. 3 + if tagSet["sensor_part"] && tagSet["has_software"] { + triggers = append(triggers, ComplianceTrigger{ + Regulation: "EU Data Act Art. 3", + TriggerCondDE: "Sensor mit Software erzeugt Maschinendaten — Zugangsrecht nach Data Act", + Severity: "medium", + Module: "vendor-compliance", + ModuleLink: "/sdk/vendor-compliance", + ActionDE: "Datenzugangsrechte fuer Sensor-/Maschinendaten nach Data Act pruefen", + RAGQuery: "EU Data Act Art 3 Sensordaten Maschinendaten Zugang", + }) + } + + // has_ai → AI Act Art. 6 (generic) + if tagSet["has_ai"] { + triggers = append(triggers, ComplianceTrigger{ + Regulation: "AI Act Art. 6", + TriggerCondDE: "KI-Komponente erkannt — Hochrisiko-Einstufung pruefen", + Severity: "high", + Module: "ai-act", + ModuleLink: "/sdk/ai-act", + ActionDE: "AI-Act-Klassifizierung fuer KI-Komponente durchfuehren", + RAGQuery: "AI Act Art 6 Klassifizierung KI-System Hochrisiko", + }) + } + + // is_networked → NIS2 Art. 21 + if tagSet["is_networked"] { + triggers = append(triggers, ComplianceTrigger{ + Regulation: "NIS2 Art. 21", + TriggerCondDE: "Vernetzte Komponente unterliegt NIS2-Sicherheitspflichten", + Severity: "medium", + Module: "cyber", + ModuleLink: "/sdk/security-backlog", + ActionDE: "NIS2-Anforderungen fuer vernetzte Infrastruktur bewerten", + RAGQuery: "NIS2 Art 21 vernetzte Infrastruktur Pflichten", + }) + } + + return triggers +} + +// genPatternRange generates pattern IDs like "HP800", "HP801", ..., "HP814". +func genPatternRange(prefix string, from, to int) []string { + ids := make([]string, 0, to-from+1) + for i := from; i <= to; i++ { + ids = append(ids, prefix+padInt(i)) + } + return ids +} + +// padInt formats an integer with leading zeros to 3 digits minimum. +func padInt(n int) string { + if n < 10 { + return "00" + triggerItoa(n) + } + if n < 100 { + return "0" + triggerItoa(n) + } + return triggerItoa(n) +} + +// triggerItoa converts a non-negative integer to a string without importing strconv. +func triggerItoa(n int) string { + if n == 0 { + return "0" + } + var buf [20]byte + pos := len(buf) + for n > 0 { + pos-- + buf[pos] = byte('0' + n%10) + n /= 10 + } + return string(buf[pos:]) +}