All checks were successful
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-ai-compliance (push) Successful in 32s
CI / test-python-backend-compliance (push) Successful in 29s
CI / test-python-document-crawler (push) Successful in 20s
CI / test-python-dsms-gateway (push) Successful in 18s
- 9 Regulation-JSON-Dateien (DSGVO 80, AI Act 60, NIS2 40, BDSG 30, TTDSG 20, DSA 35, Data Act 25, EU-Maschinen 15, DORA 20) - Condition-Tree-Engine fuer automatische Pflichtenselektion (all_of/any_of, 80+ Field-Paths) - Generischer JSONRegulationModule-Loader mit YAML-Fallback - Bidirektionales TOM-Control-Mapping (291 Obligation→Control, 92 Control→Obligation) - Gap-Analyse-Engine (Compliance-%, Priority Actions, Domain Breakdown) - ScopeDecision→UnifiedFacts Bridge fuer Auto-Profiling - 4 neue API-Endpoints (assess-from-scope, tom-controls, gap-analysis, reverse-lookup) - Frontend: Auto-Profiling Button, Regulation-Filter Chips, TOM-Panel, Gap-Analyse-View - 18 Unit Tests (Condition Engine, v2 Loader, TOM Mapper) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
301 lines
17 KiB
Go
301 lines
17 KiB
Go
package ucca
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
// ObligationConditionEngine evaluates condition trees against UnifiedFacts
|
|
type ObligationConditionEngine struct {
|
|
fieldMap map[string]func(*UnifiedFacts) interface{}
|
|
}
|
|
|
|
// NewObligationConditionEngine creates a new condition engine with all field mappings
|
|
func NewObligationConditionEngine() *ObligationConditionEngine {
|
|
e := &ObligationConditionEngine{}
|
|
e.fieldMap = e.buildFieldMap()
|
|
return e
|
|
}
|
|
|
|
// Evaluate evaluates a condition node against facts
|
|
func (e *ObligationConditionEngine) Evaluate(node *ConditionNode, facts *UnifiedFacts) bool {
|
|
if node == nil {
|
|
return true // nil condition = always applies
|
|
}
|
|
|
|
// Composite: all_of (AND)
|
|
if len(node.AllOf) > 0 {
|
|
for _, child := range node.AllOf {
|
|
if !e.Evaluate(&child, facts) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// Composite: any_of (OR)
|
|
if len(node.AnyOf) > 0 {
|
|
for _, child := range node.AnyOf {
|
|
if e.Evaluate(&child, facts) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Leaf node: field + operator + value
|
|
if node.Field != "" {
|
|
return e.evaluateLeaf(node, facts)
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func (e *ObligationConditionEngine) evaluateLeaf(node *ConditionNode, facts *UnifiedFacts) bool {
|
|
getter, ok := e.fieldMap[node.Field]
|
|
if !ok {
|
|
fmt.Printf("Warning: unknown field in condition: %s\n", node.Field)
|
|
return false
|
|
}
|
|
|
|
actual := getter(facts)
|
|
return e.compare(actual, node.Operator, node.Value)
|
|
}
|
|
|
|
func (e *ObligationConditionEngine) compare(actual interface{}, operator string, expected interface{}) bool {
|
|
switch strings.ToUpper(operator) {
|
|
case "EQUALS":
|
|
return e.equals(actual, expected)
|
|
case "NOT_EQUALS":
|
|
return !e.equals(actual, expected)
|
|
case "GREATER_THAN":
|
|
return e.toFloat(actual) > e.toFloat(expected)
|
|
case "LESS_THAN":
|
|
return e.toFloat(actual) < e.toFloat(expected)
|
|
case "GREATER_OR_EQUAL":
|
|
return e.toFloat(actual) >= e.toFloat(expected)
|
|
case "LESS_OR_EQUAL":
|
|
return e.toFloat(actual) <= e.toFloat(expected)
|
|
case "IN":
|
|
return e.inSlice(actual, expected)
|
|
case "NOT_IN":
|
|
return !e.inSlice(actual, expected)
|
|
case "CONTAINS":
|
|
return e.contains(actual, expected)
|
|
case "EXISTS":
|
|
return actual != nil && actual != "" && actual != false && actual != 0
|
|
default:
|
|
fmt.Printf("Warning: unknown operator: %s\n", operator)
|
|
return false
|
|
}
|
|
}
|
|
|
|
func (e *ObligationConditionEngine) equals(a, b interface{}) bool {
|
|
// Handle bool comparisons
|
|
aBool, aIsBool := e.toBool(a)
|
|
bBool, bIsBool := e.toBool(b)
|
|
if aIsBool && bIsBool {
|
|
return aBool == bBool
|
|
}
|
|
|
|
// Handle numeric comparisons
|
|
aFloat, aIsNum := e.toFloatOk(a)
|
|
bFloat, bIsNum := e.toFloatOk(b)
|
|
if aIsNum && bIsNum {
|
|
return aFloat == bFloat
|
|
}
|
|
|
|
// String comparison
|
|
return fmt.Sprintf("%v", a) == fmt.Sprintf("%v", b)
|
|
}
|
|
|
|
func (e *ObligationConditionEngine) toBool(v interface{}) (bool, bool) {
|
|
switch b := v.(type) {
|
|
case bool:
|
|
return b, true
|
|
}
|
|
return false, false
|
|
}
|
|
|
|
func (e *ObligationConditionEngine) toFloat(v interface{}) float64 {
|
|
f, _ := e.toFloatOk(v)
|
|
return f
|
|
}
|
|
|
|
func (e *ObligationConditionEngine) toFloatOk(v interface{}) (float64, bool) {
|
|
switch n := v.(type) {
|
|
case int:
|
|
return float64(n), true
|
|
case int64:
|
|
return float64(n), true
|
|
case float64:
|
|
return n, true
|
|
case float32:
|
|
return float64(n), true
|
|
case json.Number:
|
|
f, err := n.Float64()
|
|
return f, err == nil
|
|
}
|
|
return 0, false
|
|
}
|
|
|
|
func (e *ObligationConditionEngine) inSlice(actual, expected interface{}) bool {
|
|
actualStr := fmt.Sprintf("%v", actual)
|
|
switch v := expected.(type) {
|
|
case []interface{}:
|
|
for _, item := range v {
|
|
if fmt.Sprintf("%v", item) == actualStr {
|
|
return true
|
|
}
|
|
}
|
|
case []string:
|
|
for _, item := range v {
|
|
if item == actualStr {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (e *ObligationConditionEngine) contains(actual, expected interface{}) bool {
|
|
// Check if actual (slice) contains expected
|
|
switch v := actual.(type) {
|
|
case []string:
|
|
exp := fmt.Sprintf("%v", expected)
|
|
for _, item := range v {
|
|
if item == exp {
|
|
return true
|
|
}
|
|
}
|
|
case string:
|
|
return strings.Contains(v, fmt.Sprintf("%v", expected))
|
|
}
|
|
return false
|
|
}
|
|
|
|
// buildFieldMap creates the mapping from JSON field paths to Go struct accessors
|
|
func (e *ObligationConditionEngine) buildFieldMap() map[string]func(*UnifiedFacts) interface{} {
|
|
return map[string]func(*UnifiedFacts) interface{}{
|
|
// Organization
|
|
"organization.employee_count": func(f *UnifiedFacts) interface{} { return f.Organization.EmployeeCount },
|
|
"organization.annual_revenue": func(f *UnifiedFacts) interface{} { return f.Organization.AnnualRevenue },
|
|
"organization.country": func(f *UnifiedFacts) interface{} { return f.Organization.Country },
|
|
"organization.eu_member": func(f *UnifiedFacts) interface{} { return f.Organization.EUMember },
|
|
"organization.is_public_authority": func(f *UnifiedFacts) interface{} { return f.Organization.IsPublicAuthority },
|
|
"organization.legal_form": func(f *UnifiedFacts) interface{} { return f.Organization.LegalForm },
|
|
"organization.size_category": func(f *UnifiedFacts) interface{} { return f.Organization.CalculateSizeCategory() },
|
|
"organization.is_part_of_group": func(f *UnifiedFacts) interface{} { return f.Organization.IsPartOfGroup },
|
|
|
|
// Data Protection
|
|
"data_protection.processes_personal_data": func(f *UnifiedFacts) interface{} { return f.DataProtection.ProcessesPersonalData },
|
|
"data_protection.is_controller": func(f *UnifiedFacts) interface{} { return f.DataProtection.IsController },
|
|
"data_protection.is_processor": func(f *UnifiedFacts) interface{} { return f.DataProtection.IsProcessor },
|
|
"data_protection.processes_special_categories": func(f *UnifiedFacts) interface{} { return f.DataProtection.ProcessesSpecialCategories },
|
|
"data_protection.processes_children_data": func(f *UnifiedFacts) interface{} { return f.DataProtection.ProcessesMinorData },
|
|
"data_protection.processes_minor_data": func(f *UnifiedFacts) interface{} { return f.DataProtection.ProcessesMinorData },
|
|
"data_protection.processes_criminal_data": func(f *UnifiedFacts) interface{} { return f.DataProtection.ProcessesCriminalData },
|
|
"data_protection.large_scale": func(f *UnifiedFacts) interface{} { return f.DataProtection.LargeScaleProcessing },
|
|
"data_protection.large_scale_processing": func(f *UnifiedFacts) interface{} { return f.DataProtection.LargeScaleProcessing },
|
|
"data_protection.systematic_monitoring": func(f *UnifiedFacts) interface{} { return f.DataProtection.SystematicMonitoring },
|
|
"data_protection.uses_automated_decisions": func(f *UnifiedFacts) interface{} { return f.DataProtection.AutomatedDecisionMaking },
|
|
"data_protection.automated_decision_making": func(f *UnifiedFacts) interface{} { return f.DataProtection.AutomatedDecisionMaking },
|
|
"data_protection.cross_border_transfer": func(f *UnifiedFacts) interface{} { return f.DataProtection.TransfersToThirdCountries },
|
|
"data_protection.transfers_to_third_countries": func(f *UnifiedFacts) interface{} { return f.DataProtection.TransfersToThirdCountries },
|
|
"data_protection.cross_border_processing": func(f *UnifiedFacts) interface{} { return f.DataProtection.CrossBorderProcessing },
|
|
"data_protection.uses_processors": func(f *UnifiedFacts) interface{} { return f.DataProtection.UsesExternalProcessor },
|
|
"data_protection.uses_external_processor": func(f *UnifiedFacts) interface{} { return f.DataProtection.UsesExternalProcessor },
|
|
"data_protection.high_risk": func(f *UnifiedFacts) interface{} { return f.DataProtection.LargeScaleProcessing || f.DataProtection.SystematicMonitoring || f.DataProtection.ProcessesSpecialCategories },
|
|
"data_protection.uses_profiling": func(f *UnifiedFacts) interface{} { return f.DataProtection.Profiling },
|
|
"data_protection.profiling": func(f *UnifiedFacts) interface{} { return f.DataProtection.Profiling },
|
|
"data_protection.requires_dsb": func(f *UnifiedFacts) interface{} { return f.DataProtection.RequiresDSBByLaw },
|
|
"data_protection.needs_dpo": func(f *UnifiedFacts) interface{} { return f.DataProtection.RequiresDSBByLaw },
|
|
"data_protection.has_appointed_dsb": func(f *UnifiedFacts) interface{} { return f.DataProtection.HasAppointedDSB },
|
|
"data_protection.data_subject_count": func(f *UnifiedFacts) interface{} { return f.DataProtection.DataSubjectCount },
|
|
"data_protection.sccs_in_place": func(f *UnifiedFacts) interface{} { return f.DataProtection.SCCsInPlace },
|
|
"data_protection.binding_corporate_rules": func(f *UnifiedFacts) interface{} { return f.DataProtection.BindingCorporateRules },
|
|
"data_protection.special_categories": func(f *UnifiedFacts) interface{} { return f.DataProtection.SpecialCategories },
|
|
"data_protection.processes_employee_data": func(f *UnifiedFacts) interface{} { return f.DataProtection.ProcessesEmployeeData },
|
|
"data_protection.processes_health_data": func(f *UnifiedFacts) interface{} { return f.DataProtection.ProcessesHealthData },
|
|
"data_protection.processes_financial_data": func(f *UnifiedFacts) interface{} { return f.DataProtection.ProcessesFinancialData },
|
|
"data_protection.uses_cookies": func(f *UnifiedFacts) interface{} { return f.DataProtection.UsesCookies },
|
|
"data_protection.uses_tracking": func(f *UnifiedFacts) interface{} { return f.DataProtection.UsesTracking },
|
|
"data_protection.uses_video_surveillance": func(f *UnifiedFacts) interface{} { return f.DataProtection.UsesVideoSurveillance },
|
|
"data_protection.processes_biometric_data": func(f *UnifiedFacts) interface{} { return f.DataProtection.ProcessesBiometricData },
|
|
"data_protection.operates_platform": func(f *UnifiedFacts) interface{} { return f.DataProtection.OperatesPlatform },
|
|
"data_protection.platform_user_count": func(f *UnifiedFacts) interface{} { return f.DataProtection.PlatformUserCount },
|
|
|
|
// AI Usage
|
|
"ai_usage.uses_ai": func(f *UnifiedFacts) interface{} { return f.AIUsage.UsesAI },
|
|
"ai_usage.is_ai_provider": func(f *UnifiedFacts) interface{} { return f.AIUsage.IsAIProvider },
|
|
"ai_usage.is_ai_deployer": func(f *UnifiedFacts) interface{} { return f.AIUsage.IsAIDeployer },
|
|
"ai_usage.is_ai_distributor": func(f *UnifiedFacts) interface{} { return f.AIUsage.IsAIDistributor },
|
|
"ai_usage.is_ai_importer": func(f *UnifiedFacts) interface{} { return f.AIUsage.IsAIImporter },
|
|
"ai_usage.high_risk_ai": func(f *UnifiedFacts) interface{} { return f.AIUsage.HasHighRiskAI },
|
|
"ai_usage.has_high_risk_ai": func(f *UnifiedFacts) interface{} { return f.AIUsage.HasHighRiskAI },
|
|
"ai_usage.limited_risk_ai": func(f *UnifiedFacts) interface{} { return f.AIUsage.HasLimitedRiskAI },
|
|
"ai_usage.has_limited_risk_ai": func(f *UnifiedFacts) interface{} { return f.AIUsage.HasLimitedRiskAI },
|
|
"ai_usage.minimal_risk_ai": func(f *UnifiedFacts) interface{} { return f.AIUsage.HasMinimalRiskAI },
|
|
"ai_usage.is_gpai_provider": func(f *UnifiedFacts) interface{} { return f.AIUsage.UsesGPAI },
|
|
"ai_usage.gpai_systemic_risk": func(f *UnifiedFacts) interface{} { return f.AIUsage.GPAIWithSystemicRisk },
|
|
"ai_usage.uses_biometric_ai": func(f *UnifiedFacts) interface{} { return f.AIUsage.BiometricIdentification },
|
|
"ai_usage.biometric_identification": func(f *UnifiedFacts) interface{} { return f.AIUsage.BiometricIdentification },
|
|
"ai_usage.uses_emotion_recognition": func(f *UnifiedFacts) interface{} { return f.AIUsage.EmotionRecognition },
|
|
"ai_usage.ai_in_education": func(f *UnifiedFacts) interface{} { return f.AIUsage.EducationAccess },
|
|
"ai_usage.ai_in_employment": func(f *UnifiedFacts) interface{} { return f.AIUsage.EmploymentDecisions },
|
|
"ai_usage.ai_in_critical_infrastructure": func(f *UnifiedFacts) interface{} { return f.AIUsage.CriticalInfrastructure },
|
|
"ai_usage.ai_in_law_enforcement": func(f *UnifiedFacts) interface{} { return f.AIUsage.LawEnforcement },
|
|
"ai_usage.ai_in_justice": func(f *UnifiedFacts) interface{} { return f.AIUsage.JusticeAdministration },
|
|
"ai_usage.uses_generative_ai": func(f *UnifiedFacts) interface{} { return f.AIUsage.AIInteractsWithNaturalPersons },
|
|
"ai_usage.uses_deepfakes": func(f *UnifiedFacts) interface{} { return f.AIUsage.GeneratesDeepfakes },
|
|
"ai_usage.ai_makes_decisions": func(f *UnifiedFacts) interface{} { return f.DataProtection.AutomatedDecisionMaking },
|
|
"ai_usage.ai_interacts_with_persons": func(f *UnifiedFacts) interface{} { return f.AIUsage.AIInteractsWithNaturalPersons },
|
|
|
|
// Sector
|
|
"sector.primary_sector": func(f *UnifiedFacts) interface{} { return f.Sector.PrimarySector },
|
|
"sector.is_kritis": func(f *UnifiedFacts) interface{} { return f.Sector.IsKRITIS },
|
|
"sector.nis2_applicable": func(f *UnifiedFacts) interface{} { return f.Sector.PrimarySector != "" && f.Sector.PrimarySector != "other" },
|
|
"sector.nis2_classification": func(f *UnifiedFacts) interface{} { return f.Sector.NIS2Classification },
|
|
"sector.is_annex_i": func(f *UnifiedFacts) interface{} { return f.Sector.IsAnnexI },
|
|
"sector.is_annex_ii": func(f *UnifiedFacts) interface{} { return f.Sector.IsAnnexII },
|
|
"sector.provides_dns": func(f *UnifiedFacts) interface{} { return containsString(f.Sector.SpecialServices, "dns") },
|
|
"sector.provides_cloud": func(f *UnifiedFacts) interface{} { return containsString(f.Sector.SpecialServices, "cloud") },
|
|
"sector.provides_cdn": func(f *UnifiedFacts) interface{} { return containsString(f.Sector.SpecialServices, "cdn") },
|
|
"sector.provides_data_center": func(f *UnifiedFacts) interface{} { return containsString(f.Sector.SpecialServices, "datacenter") },
|
|
"sector.provides_managed_services": func(f *UnifiedFacts) interface{} { return containsString(f.Sector.SpecialServices, "msp") || containsString(f.Sector.SpecialServices, "mssp") },
|
|
"sector.is_financial_institution": func(f *UnifiedFacts) interface{} { return f.Sector.IsFinancialInstitution },
|
|
"sector.is_healthcare_provider": func(f *UnifiedFacts) interface{} { return f.Sector.IsHealthcareProvider },
|
|
|
|
// IT Security
|
|
"it_security.has_isms": func(f *UnifiedFacts) interface{} { return f.ITSecurity.HasISMS },
|
|
"it_security.iso27001_certified": func(f *UnifiedFacts) interface{} { return f.ITSecurity.ISO27001Certified },
|
|
"it_security.has_incident_process": func(f *UnifiedFacts) interface{} { return f.ITSecurity.HasIncidentProcess },
|
|
"it_security.has_mfa": func(f *UnifiedFacts) interface{} { return f.ITSecurity.HasMFA },
|
|
"it_security.has_encryption": func(f *UnifiedFacts) interface{} { return f.ITSecurity.HasEncryption },
|
|
"it_security.has_backup": func(f *UnifiedFacts) interface{} { return f.ITSecurity.HasBackup },
|
|
"it_security.has_bcm": func(f *UnifiedFacts) interface{} { return f.ITSecurity.HasBCM },
|
|
"it_security.has_siem": func(f *UnifiedFacts) interface{} { return f.ITSecurity.HasSecurityMonitoring },
|
|
"it_security.has_network_segmentation": func(f *UnifiedFacts) interface{} { return f.ITSecurity.HasNetworkSegmentation },
|
|
"it_security.has_vulnerability_mgmt": func(f *UnifiedFacts) interface{} { return f.ITSecurity.HasVulnerabilityMgmt },
|
|
|
|
// Financial
|
|
"financial.dora_applies": func(f *UnifiedFacts) interface{} { return f.Financial.DORAApplies },
|
|
"financial.is_regulated": func(f *UnifiedFacts) interface{} { return f.Financial.IsRegulated },
|
|
"financial.has_critical_ict": func(f *UnifiedFacts) interface{} { return f.Financial.HasCriticalICT },
|
|
"financial.ict_outsourced": func(f *UnifiedFacts) interface{} { return f.Financial.ICTOutsourced },
|
|
"financial.concentration_risk": func(f *UnifiedFacts) interface{} { return f.Financial.ConcentrationRisk },
|
|
|
|
// Supply Chain
|
|
"supply_chain.has_risk_management": func(f *UnifiedFacts) interface{} { return f.SupplyChain.HasSupplyChainRiskMgmt },
|
|
"supply_chain.supplier_count": func(f *UnifiedFacts) interface{} { return f.SupplyChain.SupplierCount },
|
|
|
|
// Personnel
|
|
"personnel.has_ciso": func(f *UnifiedFacts) interface{} { return f.Personnel.HasCISO },
|
|
"personnel.has_dpo": func(f *UnifiedFacts) interface{} { return f.Personnel.HasDPO },
|
|
"personnel.has_ai_competence": func(f *UnifiedFacts) interface{} { return f.Personnel.HasAICompetence },
|
|
"personnel.has_ai_governance": func(f *UnifiedFacts) interface{} { return f.Personnel.HasAIGovernance },
|
|
"personnel.has_compliance_officer": func(f *UnifiedFacts) interface{} { return f.Personnel.HasComplianceOfficer },
|
|
}
|
|
}
|