Files
breakpilot-compliance/ai-compliance-sdk/internal/ucca/rules.go
Sharang Parnerkar 9f96061631 refactor(go): split training/store, ucca/rules, ucca_handlers, document_export under 500 LOC
Each of the four oversized files (training/store.go 1569 LOC, ucca/rules.go 1231 LOC,
ucca_handlers.go 1135 LOC, document_export.go 1101 LOC) is split by logical group
into same-package files, all under the 500-line hard cap. Zero behavior changes,
no renamed exported symbols. Also fixed pre-existing hazard_library split (missing
functions and duplicate UUID keys from a prior session).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 09:29:54 +02:00

288 lines
9.0 KiB
Go

package ucca
import (
"fmt"
"strings"
)
// ============================================================================
// Rule Engine - Deterministic rule evaluation
// ============================================================================
// Rule represents a single evaluation rule
type Rule struct {
Code string
Category string
Title string
TitleDE string
Description string
DescriptionDE string
Severity Severity
ScoreDelta int // Points added to risk score
GDPRRef string // GDPR article reference
Controls []string // Required control IDs
Patterns []string // Recommended pattern IDs
Condition func(intake *UseCaseIntake) bool
Rationale func(intake *UseCaseIntake) string
}
// RuleEngine holds all rules and performs evaluation
type RuleEngine struct {
rules []Rule
}
// NewRuleEngine creates a new rule engine with all rules
func NewRuleEngine() *RuleEngine {
return &RuleEngine{
rules: AllRules,
}
}
// Evaluate runs all rules against the intake and returns the assessment result
func (e *RuleEngine) Evaluate(intake *UseCaseIntake) *AssessmentResult {
result := &AssessmentResult{
Feasibility: FeasibilityYES,
RiskLevel: RiskLevelMINIMAL,
Complexity: ComplexityLOW,
RiskScore: 0,
TriggeredRules: []TriggeredRule{},
RequiredControls: []RequiredControl{},
RecommendedArchitecture: []PatternRecommendation{},
ForbiddenPatterns: []ForbiddenPattern{},
ExampleMatches: []ExampleMatch{},
DSFARecommended: false,
Art22Risk: false,
TrainingAllowed: TrainingYES,
}
// Track triggered severities
hasBlock := false
hasWarn := false
controlMap := make(map[string]bool)
patternMap := make(map[string]int) // pattern -> priority
// Evaluate each rule
for _, rule := range e.rules {
if rule.Condition(intake) {
// Add triggered rule
triggered := TriggeredRule{
Code: rule.Code,
Category: rule.Category,
Title: rule.TitleDE,
Description: rule.DescriptionDE,
Severity: rule.Severity,
ScoreDelta: rule.ScoreDelta,
GDPRRef: rule.GDPRRef,
Rationale: rule.Rationale(intake),
}
result.TriggeredRules = append(result.TriggeredRules, triggered)
// Update risk score
result.RiskScore += rule.ScoreDelta
// Track severity
switch rule.Severity {
case SeverityBLOCK:
hasBlock = true
case SeverityWARN:
hasWarn = true
}
// Collect required controls
for _, controlID := range rule.Controls {
if !controlMap[controlID] {
controlMap[controlID] = true
if ctrl := GetControlByID(controlID); ctrl != nil {
result.RequiredControls = append(result.RequiredControls, *ctrl)
}
}
}
// Collect recommended patterns
for i, patternID := range rule.Patterns {
if _, exists := patternMap[patternID]; !exists {
patternMap[patternID] = i + 1 // priority
}
}
}
}
// Determine feasibility based on aggregation rules (R-090 to R-092)
if hasBlock {
result.Feasibility = FeasibilityNO
} else if hasWarn {
result.Feasibility = FeasibilityCONDITIONAL
} else {
result.Feasibility = FeasibilityYES
}
// Determine risk level based on score
if result.RiskScore >= 80 {
result.RiskLevel = RiskLevelUNACCEPTABLE
} else if result.RiskScore >= 60 {
result.RiskLevel = RiskLevelHIGH
} else if result.RiskScore >= 40 {
result.RiskLevel = RiskLevelMEDIUM
} else if result.RiskScore >= 20 {
result.RiskLevel = RiskLevelLOW
} else {
result.RiskLevel = RiskLevelMINIMAL
}
// Determine complexity
if len(result.RequiredControls) >= 5 || result.RiskScore >= 50 {
result.Complexity = ComplexityHIGH
} else if len(result.RequiredControls) >= 3 || result.RiskScore >= 25 {
result.Complexity = ComplexityMEDIUM
} else {
result.Complexity = ComplexityLOW
}
// Check DSFA recommendation
if result.RiskLevel == RiskLevelHIGH || result.RiskLevel == RiskLevelUNACCEPTABLE ||
intake.DataTypes.Article9Data || intake.DataTypes.BiometricData ||
(intake.Purpose.Profiling && intake.DataTypes.PersonalData) {
result.DSFARecommended = true
}
// Check Art. 22 risk
if intake.Automation == AutomationFullyAutomated &&
(intake.Outputs.LegalEffects || intake.Outputs.RankingsOrScores || intake.Purpose.EvaluationScoring) {
result.Art22Risk = true
}
// Determine training allowed
if intake.ModelUsage.Training && intake.DataTypes.PersonalData {
result.TrainingAllowed = TrainingNO
} else if intake.ModelUsage.Finetune && intake.DataTypes.PersonalData {
result.TrainingAllowed = TrainingCONDITIONAL
}
// Add recommended architecture patterns
for patternID, priority := range patternMap {
if p := GetPatternByID(patternID); p != nil {
result.RecommendedArchitecture = append(result.RecommendedArchitecture,
PatternToRecommendation(*p, "Empfohlen basierend auf ausgelösten Regeln", priority))
}
}
// Add applicable patterns not yet recommended
applicable := GetApplicablePatterns(intake)
for _, p := range applicable {
found := false
for _, rec := range result.RecommendedArchitecture {
if rec.PatternID == p.ID {
found = true
break
}
}
if !found {
result.RecommendedArchitecture = append(result.RecommendedArchitecture,
PatternToRecommendation(p, "Anwendbar für Ihren Use Case", len(result.RecommendedArchitecture)+1))
}
}
// Add forbidden patterns
result.ForbiddenPatterns = GetForbiddenPatterns(intake)
// Add matching examples
result.ExampleMatches = MatchExamples(intake)
// Generate summary
result.Summary = generateSummary(result, intake)
result.Recommendation = generateRecommendation(result, intake)
if result.Feasibility == FeasibilityNO {
result.AlternativeApproach = generateAlternative(result, intake)
}
return result
}
// generateSummary creates a human-readable summary
func generateSummary(result *AssessmentResult, intake *UseCaseIntake) string {
var parts []string
switch result.Feasibility {
case FeasibilityYES:
parts = append(parts, "Der Use Case ist aus DSGVO-Sicht grundsätzlich umsetzbar.")
case FeasibilityCONDITIONAL:
parts = append(parts, "Der Use Case ist unter Auflagen umsetzbar.")
case FeasibilityNO:
parts = append(parts, "Der Use Case ist in der aktuellen Form nicht DSGVO-konform umsetzbar.")
}
if len(result.TriggeredRules) > 0 {
blockCount := 0
warnCount := 0
for _, r := range result.TriggeredRules {
if r.Severity == SeverityBLOCK {
blockCount++
} else if r.Severity == SeverityWARN {
warnCount++
}
}
if blockCount > 0 {
parts = append(parts, fmt.Sprintf("%d kritische Regelverletzung(en) identifiziert.", blockCount))
}
if warnCount > 0 {
parts = append(parts, fmt.Sprintf("%d Warnungen erfordern Aufmerksamkeit.", warnCount))
}
}
if result.DSFARecommended {
parts = append(parts, "Eine Datenschutz-Folgenabschätzung (DSFA) wird empfohlen.")
}
return strings.Join(parts, " ")
}
// generateRecommendation creates actionable recommendations
func generateRecommendation(result *AssessmentResult, intake *UseCaseIntake) string {
if result.Feasibility == FeasibilityYES {
return "Fahren Sie mit der Implementierung fort. Beachten Sie die empfohlenen Architektur-Patterns für optimale DSGVO-Konformität."
}
if result.Feasibility == FeasibilityCONDITIONAL {
if len(result.RequiredControls) > 0 {
return fmt.Sprintf("Implementieren Sie die %d erforderlichen Kontrollen vor dem Go-Live. Dokumentieren Sie alle Maßnahmen für den Nachweis der Rechenschaftspflicht (Art. 5 DSGVO).", len(result.RequiredControls))
}
return "Prüfen Sie die ausgelösten Warnungen und implementieren Sie entsprechende Schutzmaßnahmen."
}
// FeasibilityNO
return "Der Use Case erfordert grundlegende Änderungen. Prüfen Sie die Alternative-Ansatz-Empfehlung."
}
// generateAlternative creates alternative approach suggestions
func generateAlternative(result *AssessmentResult, intake *UseCaseIntake) string {
var suggestions []string
// Check specific blocking reasons
if intake.ModelUsage.Training && intake.DataTypes.PersonalData {
suggestions = append(suggestions, "Nutzen Sie nur RAG statt Training mit personenbezogenen Daten")
}
if intake.Automation == AutomationFullyAutomated && intake.Outputs.LegalEffects {
suggestions = append(suggestions, "Implementieren Sie Human-in-the-Loop für Entscheidungen mit rechtlichen Auswirkungen")
}
if intake.DataTypes.MinorData && intake.Purpose.EvaluationScoring {
suggestions = append(suggestions, "Verzichten Sie auf automatisches Scoring von Minderjährigen - nutzen Sie KI nur zur Unterstützung menschlicher Entscheidungsträger")
}
if intake.Hosting.Region == "third_country" && intake.DataTypes.PersonalData {
suggestions = append(suggestions, "Hosten Sie innerhalb der EU oder implementieren Sie Standardvertragsklauseln (SCCs)")
}
if len(suggestions) == 0 {
return "Überarbeiten Sie den Use Case unter Berücksichtigung der ausgelösten Regeln."
}
return strings.Join(suggestions, ". ") + "."
}
// GetRules returns all rules
func (e *RuleEngine) GetRules() []Rule {
return e.rules
}