package ucca import ( "fmt" "os" "path/filepath" "sort" "strings" "gopkg.in/yaml.v3" ) // ============================================================================ // YAML-based Policy Engine // ============================================================================ // // This engine evaluates use-case intakes against YAML-defined rules. // Key design principles: // - Deterministic: No LLM involvement in rule evaluation // - Transparent: Rules are auditable YAML // - Composable: Each field carries its own legal metadata // - Solution-oriented: Problems include suggested solutions // // ============================================================================ // DefaultPolicyPath is the default location for the policy file var DefaultPolicyPath = "policies/ucca_policy_v1.yaml" // PolicyConfig represents the full YAML policy structure type PolicyConfig struct { Policy PolicyMetadata `yaml:"policy"` Thresholds Thresholds `yaml:"thresholds"` Patterns map[string]PatternDef `yaml:"patterns"` Controls map[string]ControlDef `yaml:"controls"` Rules []RuleDef `yaml:"rules"` ProblemSolutions []ProblemSolutionDef `yaml:"problem_solutions"` EscalationTriggers []EscalationTriggerDef `yaml:"escalation_triggers"` } // PolicyMetadata contains policy header info type PolicyMetadata struct { Name string `yaml:"name"` Version string `yaml:"version"` Jurisdiction string `yaml:"jurisdiction"` Basis []string `yaml:"basis"` DefaultFeasibility string `yaml:"default_feasibility"` DefaultRiskScore int `yaml:"default_risk_score"` } // Thresholds for risk scoring and escalation type Thresholds struct { Risk RiskThresholds `yaml:"risk"` Escalation []string `yaml:"escalation"` } // RiskThresholds defines risk level boundaries type RiskThresholds struct { Minimal int `yaml:"minimal"` Low int `yaml:"low"` Medium int `yaml:"medium"` High int `yaml:"high"` Unacceptable int `yaml:"unacceptable"` } // PatternDef represents an architecture pattern from YAML type PatternDef struct { ID string `yaml:"id"` Title string `yaml:"title"` Description string `yaml:"description"` Benefit string `yaml:"benefit"` Effort string `yaml:"effort"` RiskReduction int `yaml:"risk_reduction"` } // ControlDef represents a required control from YAML type ControlDef struct { ID string `yaml:"id"` Title string `yaml:"title"` Description string `yaml:"description"` GDPRRef string `yaml:"gdpr_ref"` Effort string `yaml:"effort"` } // RuleDef represents a single rule from YAML type RuleDef struct { ID string `yaml:"id"` Category string `yaml:"category"` Title string `yaml:"title"` Description string `yaml:"description"` Condition ConditionDef `yaml:"condition"` Effect EffectDef `yaml:"effect"` Severity string `yaml:"severity"` GDPRRef string `yaml:"gdpr_ref"` Rationale string `yaml:"rationale"` } // ConditionDef represents a rule condition (supports field checks and compositions) type ConditionDef struct { // Simple field check Field string `yaml:"field,omitempty"` Operator string `yaml:"operator,omitempty"` Value interface{} `yaml:"value,omitempty"` // Composite conditions AllOf []ConditionDef `yaml:"all_of,omitempty"` AnyOf []ConditionDef `yaml:"any_of,omitempty"` // Aggregate conditions (evaluated after all rules) Aggregate string `yaml:"aggregate,omitempty"` } // EffectDef represents the effect when a rule triggers type EffectDef struct { RiskAdd int `yaml:"risk_add,omitempty"` Feasibility string `yaml:"feasibility,omitempty"` ControlsAdd []string `yaml:"controls_add,omitempty"` SuggestedPatterns []string `yaml:"suggested_patterns,omitempty"` Escalation bool `yaml:"escalation,omitempty"` Art22Risk bool `yaml:"art22_risk,omitempty"` TrainingAllowed bool `yaml:"training_allowed,omitempty"` LegalBasis string `yaml:"legal_basis,omitempty"` } // ProblemSolutionDef maps problems to solutions type ProblemSolutionDef struct { ProblemID string `yaml:"problem_id"` Title string `yaml:"title"` Triggers []ProblemTriggerDef `yaml:"triggers"` Solutions []SolutionDef `yaml:"solutions"` } // ProblemTriggerDef defines when a problem is triggered type ProblemTriggerDef struct { Rule string `yaml:"rule"` WithoutControl string `yaml:"without_control,omitempty"` } // SolutionDef represents a potential solution type SolutionDef struct { ID string `yaml:"id"` Title string `yaml:"title"` Pattern string `yaml:"pattern,omitempty"` Control string `yaml:"control,omitempty"` RemovesProblem bool `yaml:"removes_problem"` TeamQuestion string `yaml:"team_question"` } // EscalationTriggerDef defines when to escalate to DSB type EscalationTriggerDef struct { Condition string `yaml:"condition"` Reason string `yaml:"reason"` } // ============================================================================ // Policy Engine Implementation // ============================================================================ // PolicyEngine evaluates intakes against YAML-defined rules type PolicyEngine struct { config *PolicyConfig } // NewPolicyEngine creates a new policy engine, loading from the default path // It searches for the policy file in common locations func NewPolicyEngine() (*PolicyEngine, error) { // Try multiple locations to find the policy file searchPaths := []string{ DefaultPolicyPath, filepath.Join(".", "policies", "ucca_policy_v1.yaml"), filepath.Join("..", "policies", "ucca_policy_v1.yaml"), filepath.Join("..", "..", "policies", "ucca_policy_v1.yaml"), "/app/policies/ucca_policy_v1.yaml", // Docker container path } var data []byte var err error for _, path := range searchPaths { data, err = os.ReadFile(path) if err == nil { break } } if err != nil { return nil, fmt.Errorf("failed to load policy from any known location: %w", err) } var config PolicyConfig if err := yaml.Unmarshal(data, &config); err != nil { return nil, fmt.Errorf("failed to parse policy YAML: %w", err) } return &PolicyEngine{config: &config}, nil } // NewPolicyEngineFromPath loads policy from a specific file path func NewPolicyEngineFromPath(path string) (*PolicyEngine, error) { data, err := os.ReadFile(path) if err != nil { return nil, fmt.Errorf("failed to read policy file: %w", err) } var config PolicyConfig if err := yaml.Unmarshal(data, &config); err != nil { return nil, fmt.Errorf("failed to parse policy YAML: %w", err) } return &PolicyEngine{config: &config}, nil } // GetPolicyVersion returns the policy version func (e *PolicyEngine) GetPolicyVersion() string { return e.config.Policy.Version } // Evaluate runs all YAML rules against the intake func (e *PolicyEngine) Evaluate(intake *UseCaseIntake) *AssessmentResult { result := &AssessmentResult{ Feasibility: FeasibilityYES, RiskLevel: RiskLevelMINIMAL, Complexity: ComplexityLOW, RiskScore: 0, Intake: *intake, TriggeredRules: []TriggeredRule{}, RequiredControls: []RequiredControl{}, RecommendedArchitecture: []PatternRecommendation{}, ForbiddenPatterns: []ForbiddenPattern{}, ExampleMatches: []ExampleMatch{}, DSFARecommended: false, Art22Risk: false, TrainingAllowed: TrainingYES, } // Track state for aggregation hasBlock := false hasWarn := false controlSet := make(map[string]bool) patternPriority := make(map[string]int) triggeredRuleIDs := make(map[string]bool) needsEscalation := false // Evaluate each non-aggregate rule priority := 1 for _, rule := range e.config.Rules { // Skip aggregate rules (evaluated later) if rule.Condition.Aggregate != "" { continue } if e.evaluateCondition(&rule.Condition, intake) { triggeredRuleIDs[rule.ID] = true // Create triggered rule record triggered := TriggeredRule{ Code: rule.ID, Category: rule.Category, Title: rule.Title, Description: rule.Description, Severity: parseSeverity(rule.Severity), ScoreDelta: rule.Effect.RiskAdd, GDPRRef: rule.GDPRRef, Rationale: rule.Rationale, } result.TriggeredRules = append(result.TriggeredRules, triggered) // Apply effects result.RiskScore += rule.Effect.RiskAdd // Track severity switch parseSeverity(rule.Severity) { case SeverityBLOCK: hasBlock = true case SeverityWARN: hasWarn = true } // Override feasibility if specified if rule.Effect.Feasibility != "" { switch rule.Effect.Feasibility { case "NO": result.Feasibility = FeasibilityNO case "CONDITIONAL": if result.Feasibility != FeasibilityNO { result.Feasibility = FeasibilityCONDITIONAL } case "YES": // Only set YES if not already NO or CONDITIONAL if result.Feasibility != FeasibilityNO && result.Feasibility != FeasibilityCONDITIONAL { result.Feasibility = FeasibilityYES } } } // Collect controls for _, ctrlID := range rule.Effect.ControlsAdd { if !controlSet[ctrlID] { controlSet[ctrlID] = true if ctrl, ok := e.config.Controls[ctrlID]; ok { result.RequiredControls = append(result.RequiredControls, RequiredControl{ ID: ctrl.ID, Title: ctrl.Title, Description: ctrl.Description, Severity: parseSeverity(rule.Severity), Category: categorizeControl(ctrl.ID), GDPRRef: ctrl.GDPRRef, }) } } } // Collect patterns for _, patternID := range rule.Effect.SuggestedPatterns { if _, exists := patternPriority[patternID]; !exists { patternPriority[patternID] = priority priority++ } } // Track special flags if rule.Effect.Escalation { needsEscalation = true } if rule.Effect.Art22Risk { result.Art22Risk = true } } } // Apply aggregation rules if hasBlock { result.Feasibility = FeasibilityNO } else if hasWarn && result.Feasibility != FeasibilityNO { result.Feasibility = FeasibilityCONDITIONAL } // Determine risk level from thresholds result.RiskLevel = e.calculateRiskLevel(result.RiskScore) // Determine complexity result.Complexity = e.calculateComplexity(result) // Calculate BetrVG Conflict Score (Germany only, employees >= 5) result.BetrvgConflictScore, result.BetrvgConsultationRequired = e.calculateBetrvgConflictScore(intake) // Check if DSFA is recommended result.DSFARecommended = e.shouldRecommendDSFA(intake, result) // Determine training allowed status result.TrainingAllowed = e.determineTrainingAllowed(intake) // Add recommended patterns (sorted by priority) result.RecommendedArchitecture = e.buildPatternRecommendations(patternPriority) // Match didactic examples result.ExampleMatches = MatchExamples(intake) // Generate summaries result.Summary = e.generateSummary(result, intake) result.Recommendation = e.generateRecommendation(result, intake) if result.Feasibility == FeasibilityNO { result.AlternativeApproach = e.generateAlternative(result, intake, triggeredRuleIDs) } // Note: needsEscalation could be used to flag the assessment for DSB review _ = needsEscalation return result } // evaluateCondition recursively evaluates a condition against the intake func (e *PolicyEngine) evaluateCondition(cond *ConditionDef, intake *UseCaseIntake) bool { // Handle composite all_of if len(cond.AllOf) > 0 { for _, subCond := range cond.AllOf { if !e.evaluateCondition(&subCond, intake) { return false } } return true } // Handle composite any_of if len(cond.AnyOf) > 0 { for _, subCond := range cond.AnyOf { if e.evaluateCondition(&subCond, intake) { return true } } return false } // Handle simple field condition if cond.Field != "" { return e.evaluateFieldCondition(cond.Field, cond.Operator, cond.Value, intake) } return false } // evaluateFieldCondition evaluates a single field comparison func (e *PolicyEngine) evaluateFieldCondition(field, operator string, value interface{}, intake *UseCaseIntake) bool { // Get the field value from intake fieldValue := e.getFieldValue(field, intake) if fieldValue == nil { return false } switch operator { case "equals": return e.compareEquals(fieldValue, value) case "not_equals": return !e.compareEquals(fieldValue, value) case "in": return e.compareIn(fieldValue, value) case "contains": return e.compareContains(fieldValue, value) default: return false } } // getFieldValue extracts a field value from the intake using dot notation func (e *PolicyEngine) getFieldValue(field string, intake *UseCaseIntake) interface{} { parts := strings.Split(field, ".") if len(parts) == 0 { return nil } switch parts[0] { case "data_types": if len(parts) < 2 { return nil } return e.getDataTypeValue(parts[1], intake) case "purpose": if len(parts) < 2 { return nil } return e.getPurposeValue(parts[1], intake) case "automation": return string(intake.Automation) case "outputs": if len(parts) < 2 { return nil } return e.getOutputsValue(parts[1], intake) case "hosting": if len(parts) < 2 { return nil } return e.getHostingValue(parts[1], intake) case "model_usage": if len(parts) < 2 { return nil } return e.getModelUsageValue(parts[1], intake) case "domain": return string(intake.Domain) case "retention": if len(parts) < 2 { return nil } return e.getRetentionValue(parts[1], intake) case "employee_monitoring": return intake.EmployeeMonitoring case "hr_decision_support": return intake.HRDecisionSupport case "works_council_consulted": return intake.WorksCouncilConsulted case "hr_context": if len(parts) < 2 || intake.HRContext == nil { return nil } return e.getHRContextValue(parts[1], intake) case "education_context": if len(parts) < 2 || intake.EducationContext == nil { return nil } return e.getEducationContextValue(parts[1], intake) case "healthcare_context": if len(parts) < 2 || intake.HealthcareContext == nil { return nil } return e.getHealthcareContextValue(parts[1], intake) case "legal_context": if len(parts) < 2 || intake.LegalDomainContext == nil { return nil } return e.getLegalContextValue(parts[1], intake) case "public_sector_context": if len(parts) < 2 || intake.PublicSectorContext == nil { return nil } return e.getPublicSectorContextValue(parts[1], intake) case "critical_infra_context": if len(parts) < 2 || intake.CriticalInfraContext == nil { return nil } return e.getCriticalInfraContextValue(parts[1], intake) case "automotive_context": if len(parts) < 2 || intake.AutomotiveContext == nil { return nil } return e.getAutomotiveContextValue(parts[1], intake) case "retail_context": if len(parts) < 2 || intake.RetailContext == nil { return nil } return e.getRetailContextValue(parts[1], intake) case "it_security_context": if len(parts) < 2 || intake.ITSecurityContext == nil { return nil } return e.getITSecurityContextValue(parts[1], intake) case "logistics_context": if len(parts) < 2 || intake.LogisticsContext == nil { return nil } return e.getLogisticsContextValue(parts[1], intake) case "construction_context": if len(parts) < 2 || intake.ConstructionContext == nil { return nil } return e.getConstructionContextValue(parts[1], intake) case "marketing_context": if len(parts) < 2 || intake.MarketingContext == nil { return nil } return e.getMarketingContextValue(parts[1], intake) case "manufacturing_context": if len(parts) < 2 || intake.ManufacturingContext == nil { return nil } return e.getManufacturingContextValue(parts[1], intake) case "agriculture_context": if len(parts) < 2 || intake.AgricultureContext == nil { return nil } return e.getAgricultureContextValue(parts[1], intake) case "social_services_context": if len(parts) < 2 || intake.SocialServicesCtx == nil { return nil } return e.getSocialServicesContextValue(parts[1], intake) case "hospitality_context": if len(parts) < 2 || intake.HospitalityContext == nil { return nil } return e.getHospitalityContextValue(parts[1], intake) case "insurance_context": if len(parts) < 2 || intake.InsuranceContext == nil { return nil } return e.getInsuranceContextValue(parts[1], intake) case "investment_context": if len(parts) < 2 || intake.InvestmentContext == nil { return nil } return e.getInvestmentContextValue(parts[1], intake) case "defense_context": if len(parts) < 2 || intake.DefenseContext == nil { return nil } return e.getDefenseContextValue(parts[1], intake) case "supply_chain_context": if len(parts) < 2 || intake.SupplyChainContext == nil { return nil } return e.getSupplyChainContextValue(parts[1], intake) case "facility_context": if len(parts) < 2 || intake.FacilityContext == nil { return nil } return e.getFacilityContextValue(parts[1], intake) case "sports_context": if len(parts) < 2 || intake.SportsContext == nil { return nil } return e.getSportsContextValue(parts[1], intake) } return nil } func (e *PolicyEngine) getHRContextValue(field string, intake *UseCaseIntake) interface{} { if intake.HRContext == nil { return nil } switch field { case "automated_screening": return intake.HRContext.AutomatedScreening case "automated_rejection": return intake.HRContext.AutomatedRejection case "candidate_ranking": return intake.HRContext.CandidateRanking case "bias_audits_done": return intake.HRContext.BiasAuditsDone case "agg_categories_visible": return intake.HRContext.AGGCategoriesVisible case "human_review_enforced": return intake.HRContext.HumanReviewEnforced case "performance_evaluation": return intake.HRContext.PerformanceEvaluation } return nil } func (e *PolicyEngine) getEducationContextValue(field string, intake *UseCaseIntake) interface{} { if intake.EducationContext == nil { return nil } switch field { case "grade_influence": return intake.EducationContext.GradeInfluence case "exam_evaluation": return intake.EducationContext.ExamEvaluation case "student_selection": return intake.EducationContext.StudentSelection case "minors_involved": return intake.EducationContext.MinorsInvolved case "teacher_review_required": return intake.EducationContext.TeacherReviewRequired case "learning_adaptation": return intake.EducationContext.LearningAdaptation } return nil } func (e *PolicyEngine) getHealthcareContextValue(field string, intake *UseCaseIntake) interface{} { if intake.HealthcareContext == nil { return nil } switch field { case "diagnosis_support": return intake.HealthcareContext.DiagnosisSupport case "treatment_recommendation": return intake.HealthcareContext.TreatmentRecommend case "triage_decision": return intake.HealthcareContext.TriageDecision case "patient_data_processed": return intake.HealthcareContext.PatientDataProcessed case "medical_device": return intake.HealthcareContext.MedicalDevice case "clinical_validation": return intake.HealthcareContext.ClinicalValidation } return nil } func (e *PolicyEngine) getLegalContextValue(field string, intake *UseCaseIntake) interface{} { if intake.LegalDomainContext == nil { return nil } switch field { case "legal_advice": return intake.LegalDomainContext.LegalAdvice case "contract_analysis": return intake.LegalDomainContext.ContractAnalysis case "court_prediction": return intake.LegalDomainContext.CourtPrediction case "access_to_justice": return intake.LegalDomainContext.AccessToJustice case "client_confidential": return intake.LegalDomainContext.ClientConfidential } return nil } func (e *PolicyEngine) getPublicSectorContextValue(field string, intake *UseCaseIntake) interface{} { if intake.PublicSectorContext == nil { return nil } switch field { case "admin_decision": return intake.PublicSectorContext.AdminDecision case "citizen_service": return intake.PublicSectorContext.CitizenService case "benefit_allocation": return intake.PublicSectorContext.BenefitAllocation case "public_safety": return intake.PublicSectorContext.PublicSafety case "transparency_ensured": return intake.PublicSectorContext.TransparencyEnsured } return nil } func (e *PolicyEngine) getCriticalInfraContextValue(field string, intake *UseCaseIntake) interface{} { if intake.CriticalInfraContext == nil { return nil } switch field { case "grid_control": return intake.CriticalInfraContext.GridControl case "safety_critical": return intake.CriticalInfraContext.SafetyCritical case "anomaly_detection": return intake.CriticalInfraContext.AnomalyDetection case "redundancy_exists": return intake.CriticalInfraContext.RedundancyExists case "incident_response": return intake.CriticalInfraContext.IncidentResponse } return nil } func (e *PolicyEngine) getAutomotiveContextValue(field string, intake *UseCaseIntake) interface{} { if intake.AutomotiveContext == nil { return nil } switch field { case "autonomous_driving": return intake.AutomotiveContext.AutonomousDriving case "safety_relevant": return intake.AutomotiveContext.SafetyRelevant case "type_approval_needed": return intake.AutomotiveContext.TypeApprovalNeeded case "functional_safety": return intake.AutomotiveContext.FunctionalSafety } return nil } func (e *PolicyEngine) getRetailContextValue(field string, intake *UseCaseIntake) interface{} { if intake.RetailContext == nil { return nil } switch field { case "pricing_personalized": return intake.RetailContext.PricingPersonalized case "customer_profiling": return intake.RetailContext.CustomerProfiling case "recommendation_engine": return intake.RetailContext.RecommendationEngine case "credit_scoring": return intake.RetailContext.CreditScoring case "dark_patterns": return intake.RetailContext.DarkPatterns } return nil } func (e *PolicyEngine) getITSecurityContextValue(field string, intake *UseCaseIntake) interface{} { if intake.ITSecurityContext == nil { return nil } switch field { case "employee_surveillance": return intake.ITSecurityContext.EmployeeSurveillance case "network_monitoring": return intake.ITSecurityContext.NetworkMonitoring case "threat_detection": return intake.ITSecurityContext.ThreatDetection case "access_control_ai": return intake.ITSecurityContext.AccessControl case "data_retention_logs": return intake.ITSecurityContext.DataRetention } return nil } func (e *PolicyEngine) getLogisticsContextValue(field string, intake *UseCaseIntake) interface{} { if intake.LogisticsContext == nil { return nil } switch field { case "driver_tracking": return intake.LogisticsContext.DriverTracking case "route_optimization": return intake.LogisticsContext.RouteOptimization case "workload_scoring": return intake.LogisticsContext.WorkloadScoring case "predictive_maintenance": return intake.LogisticsContext.PredictiveMaint } return nil } func (e *PolicyEngine) getConstructionContextValue(field string, intake *UseCaseIntake) interface{} { if intake.ConstructionContext == nil { return nil } switch field { case "safety_monitoring": return intake.ConstructionContext.SafetyMonitoring case "tenant_screening": return intake.ConstructionContext.TenantScreening case "building_automation": return intake.ConstructionContext.BuildingAutomation case "worker_safety": return intake.ConstructionContext.WorkerSafety } return nil } func (e *PolicyEngine) getMarketingContextValue(field string, intake *UseCaseIntake) interface{} { if intake.MarketingContext == nil { return nil } switch field { case "deepfake_content": return intake.MarketingContext.DeepfakeContent case "content_moderation": return intake.MarketingContext.ContentModeration case "behavioral_targeting": return intake.MarketingContext.BehavioralTargeting case "minors_targeted": return intake.MarketingContext.MinorsTargeted case "ai_content_labeled": return intake.MarketingContext.AIContentLabeled } return nil } func (e *PolicyEngine) getManufacturingContextValue(field string, intake *UseCaseIntake) interface{} { if intake.ManufacturingContext == nil { return nil } switch field { case "machine_safety": return intake.ManufacturingContext.MachineSafety case "quality_control": return intake.ManufacturingContext.QualityControl case "process_control": return intake.ManufacturingContext.ProcessControl case "ce_marking_required": return intake.ManufacturingContext.CEMarkingRequired case "safety_validated": return intake.ManufacturingContext.SafetyValidated } return nil } func (e *PolicyEngine) getAgricultureContextValue(field string, intake *UseCaseIntake) interface{} { if intake.AgricultureContext == nil { return nil } switch field { case "pesticide_ai": return intake.AgricultureContext.PesticideAI case "animal_welfare": return intake.AgricultureContext.AnimalWelfare case "environmental_data": return intake.AgricultureContext.EnvironmentalData } return nil } func (e *PolicyEngine) getSocialServicesContextValue(field string, intake *UseCaseIntake) interface{} { if intake.SocialServicesCtx == nil { return nil } switch field { case "vulnerable_groups": return intake.SocialServicesCtx.VulnerableGroups case "benefit_decision": return intake.SocialServicesCtx.BenefitDecision case "case_management": return intake.SocialServicesCtx.CaseManagement } return nil } func (e *PolicyEngine) getHospitalityContextValue(field string, intake *UseCaseIntake) interface{} { if intake.HospitalityContext == nil { return nil } switch field { case "guest_profiling": return intake.HospitalityContext.GuestProfiling case "dynamic_pricing": return intake.HospitalityContext.DynamicPricing case "review_manipulation": return intake.HospitalityContext.ReviewManipulation } return nil } func (e *PolicyEngine) getInsuranceContextValue(field string, intake *UseCaseIntake) interface{} { if intake.InsuranceContext == nil { return nil } switch field { case "risk_classification": return intake.InsuranceContext.RiskClassification case "claims_automation": return intake.InsuranceContext.ClaimsAutomation case "premium_calculation": return intake.InsuranceContext.PremiumCalculation case "fraud_detection": return intake.InsuranceContext.FraudDetection } return nil } func (e *PolicyEngine) getInvestmentContextValue(field string, intake *UseCaseIntake) interface{} { if intake.InvestmentContext == nil { return nil } switch field { case "algo_trading": return intake.InvestmentContext.AlgoTrading case "investment_advice": return intake.InvestmentContext.InvestmentAdvice case "robo_advisor": return intake.InvestmentContext.RoboAdvisor } return nil } func (e *PolicyEngine) getDefenseContextValue(field string, intake *UseCaseIntake) interface{} { if intake.DefenseContext == nil { return nil } switch field { case "dual_use": return intake.DefenseContext.DualUse case "export_controlled": return intake.DefenseContext.ExportControlled case "classified_data": return intake.DefenseContext.ClassifiedData } return nil } func (e *PolicyEngine) getSupplyChainContextValue(field string, intake *UseCaseIntake) interface{} { if intake.SupplyChainContext == nil { return nil } switch field { case "supplier_monitoring": return intake.SupplyChainContext.SupplierMonitoring case "human_rights_check": return intake.SupplyChainContext.HumanRightsCheck case "environmental_impact": return intake.SupplyChainContext.EnvironmentalImpact } return nil } func (e *PolicyEngine) getFacilityContextValue(field string, intake *UseCaseIntake) interface{} { if intake.FacilityContext == nil { return nil } switch field { case "access_control_ai": return intake.FacilityContext.AccessControlAI case "occupancy_tracking": return intake.FacilityContext.OccupancyTracking case "energy_optimization": return intake.FacilityContext.EnergyOptimization } return nil } func (e *PolicyEngine) getSportsContextValue(field string, intake *UseCaseIntake) interface{} { if intake.SportsContext == nil { return nil } switch field { case "athlete_tracking": return intake.SportsContext.AthleteTracking case "fan_profiling": return intake.SportsContext.FanProfiling case "doping_detection": return intake.SportsContext.DopingDetection } return nil } func (e *PolicyEngine) getDataTypeValue(field string, intake *UseCaseIntake) interface{} { switch field { case "personal_data": return intake.DataTypes.PersonalData case "article_9_data": return intake.DataTypes.Article9Data case "minor_data": return intake.DataTypes.MinorData case "license_plates": return intake.DataTypes.LicensePlates case "images": return intake.DataTypes.Images case "audio": return intake.DataTypes.Audio case "location_data": return intake.DataTypes.LocationData case "biometric_data": return intake.DataTypes.BiometricData case "financial_data": return intake.DataTypes.FinancialData case "employee_data": return intake.DataTypes.EmployeeData case "customer_data": return intake.DataTypes.CustomerData case "public_data": return intake.DataTypes.PublicData } return nil } func (e *PolicyEngine) getPurposeValue(field string, intake *UseCaseIntake) interface{} { switch field { case "customer_support": return intake.Purpose.CustomerSupport case "marketing": return intake.Purpose.Marketing case "analytics": return intake.Purpose.Analytics case "automation": return intake.Purpose.Automation case "evaluation_scoring": return intake.Purpose.EvaluationScoring case "decision_making": return intake.Purpose.DecisionMaking case "profiling": return intake.Purpose.Profiling case "research": return intake.Purpose.Research case "internal_tools": return intake.Purpose.InternalTools case "public_service": return intake.Purpose.PublicService } return nil } func (e *PolicyEngine) getOutputsValue(field string, intake *UseCaseIntake) interface{} { switch field { case "recommendations_to_users": return intake.Outputs.RecommendationsToUsers case "rankings_or_scores": return intake.Outputs.RankingsOrScores case "legal_effects": return intake.Outputs.LegalEffects case "access_decisions": return intake.Outputs.AccessDecisions case "content_generation": return intake.Outputs.ContentGeneration case "data_export": return intake.Outputs.DataExport } return nil } func (e *PolicyEngine) getHostingValue(field string, intake *UseCaseIntake) interface{} { switch field { case "provider": return intake.Hosting.Provider case "region": return intake.Hosting.Region case "data_residency": return intake.Hosting.DataResidency } return nil } func (e *PolicyEngine) getModelUsageValue(field string, intake *UseCaseIntake) interface{} { switch field { case "rag": return intake.ModelUsage.RAG case "finetune": return intake.ModelUsage.Finetune case "training": return intake.ModelUsage.Training case "inference": return intake.ModelUsage.Inference } return nil } func (e *PolicyEngine) getRetentionValue(field string, intake *UseCaseIntake) interface{} { switch field { case "store_prompts": return intake.Retention.StorePrompts case "store_responses": return intake.Retention.StoreResponses case "retention_days": return intake.Retention.RetentionDays case "anonymize_after_use": return intake.Retention.AnonymizeAfterUse } return nil } // compareEquals compares two values for equality func (e *PolicyEngine) compareEquals(fieldValue, expected interface{}) bool { // Handle bool comparison if bv, ok := fieldValue.(bool); ok { if eb, ok := expected.(bool); ok { return bv == eb } } // Handle string comparison if sv, ok := fieldValue.(string); ok { if es, ok := expected.(string); ok { return sv == es } } // Handle int comparison if iv, ok := fieldValue.(int); ok { switch ev := expected.(type) { case int: return iv == ev case float64: return iv == int(ev) } } return false } // compareIn checks if fieldValue is in a list of expected values func (e *PolicyEngine) compareIn(fieldValue, expected interface{}) bool { list, ok := expected.([]interface{}) if !ok { return false } sv, ok := fieldValue.(string) if !ok { return false } for _, item := range list { if is, ok := item.(string); ok && is == sv { return true } } return false } // compareContains checks if a string contains a substring func (e *PolicyEngine) compareContains(fieldValue, expected interface{}) bool { sv, ok := fieldValue.(string) if !ok { return false } es, ok := expected.(string) if !ok { return false } return strings.Contains(strings.ToLower(sv), strings.ToLower(es)) } // calculateRiskLevel determines risk level from score func (e *PolicyEngine) calculateRiskLevel(score int) RiskLevel { t := e.config.Thresholds.Risk if score >= t.Unacceptable { return RiskLevelUNACCEPTABLE } if score >= t.High { return RiskLevelHIGH } if score >= t.Medium { return RiskLevelMEDIUM } if score >= t.Low { return RiskLevelLOW } return RiskLevelMINIMAL } // calculateComplexity determines implementation complexity func (e *PolicyEngine) calculateComplexity(result *AssessmentResult) Complexity { controlCount := len(result.RequiredControls) if controlCount >= 5 || result.RiskScore >= 50 { return ComplexityHIGH } if controlCount >= 3 || result.RiskScore >= 25 { return ComplexityMEDIUM } return ComplexityLOW } // shouldRecommendDSFA checks if a DSFA is recommended func (e *PolicyEngine) shouldRecommendDSFA(intake *UseCaseIntake, result *AssessmentResult) bool { if result.RiskLevel == RiskLevelHIGH || result.RiskLevel == RiskLevelUNACCEPTABLE { return true } if intake.DataTypes.Article9Data || intake.DataTypes.BiometricData { return true } if intake.Purpose.Profiling && intake.DataTypes.PersonalData { return true } // Check if C_DSFA control is required for _, ctrl := range result.RequiredControls { if ctrl.ID == "C_DSFA" { return true } } return false } // determineTrainingAllowed checks training permission func (e *PolicyEngine) determineTrainingAllowed(intake *UseCaseIntake) TrainingAllowed { if intake.ModelUsage.Training && intake.DataTypes.PersonalData { return TrainingNO } if intake.ModelUsage.Finetune && intake.DataTypes.PersonalData { return TrainingCONDITIONAL } if intake.DataTypes.MinorData && (intake.ModelUsage.Training || intake.ModelUsage.Finetune) { return TrainingNO } return TrainingYES } // buildPatternRecommendations creates sorted pattern recommendations func (e *PolicyEngine) buildPatternRecommendations(patternPriority map[string]int) []PatternRecommendation { type priorityPair struct { id string priority int } pairs := make([]priorityPair, 0, len(patternPriority)) for id, p := range patternPriority { pairs = append(pairs, priorityPair{id, p}) } sort.Slice(pairs, func(i, j int) bool { return pairs[i].priority < pairs[j].priority }) recommendations := make([]PatternRecommendation, 0, len(pairs)) for _, p := range pairs { if pattern, ok := e.config.Patterns[p.id]; ok { recommendations = append(recommendations, PatternRecommendation{ PatternID: pattern.ID, Title: pattern.Title, Description: pattern.Description, Rationale: pattern.Benefit, Priority: p.priority, }) } } return recommendations } // generateSummary creates a human-readable summary func (e *PolicyEngine) 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.") } 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 (e *PolicyEngine) 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." } return "Der Use Case erfordert grundlegende Änderungen. Prüfen Sie die Lösungsvorschläge." } // generateAlternative creates alternative approach suggestions func (e *PolicyEngine) generateAlternative(result *AssessmentResult, intake *UseCaseIntake, triggeredRules map[string]bool) string { var suggestions []string // Find applicable problem-solutions for _, ps := range e.config.ProblemSolutions { for _, trigger := range ps.Triggers { if triggeredRules[trigger.Rule] { // Check if control is missing (if specified) if trigger.WithoutControl != "" { hasControl := false for _, ctrl := range result.RequiredControls { if ctrl.ID == trigger.WithoutControl { hasControl = true break } } if hasControl { continue } } // Add first solution as suggestion if len(ps.Solutions) > 0 { sol := ps.Solutions[0] suggestions = append(suggestions, fmt.Sprintf("%s: %s", sol.Title, sol.TeamQuestion)) } } } } // Fallback suggestions based on intake if len(suggestions) == 0 { 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") } } if len(suggestions) == 0 { return "Überarbeiten Sie den Use Case unter Berücksichtigung der ausgelösten Regeln." } return strings.Join(suggestions, " | ") } // GetAllRules returns all rules in the policy func (e *PolicyEngine) GetAllRules() []RuleDef { return e.config.Rules } // GetAllPatterns returns all patterns in the policy func (e *PolicyEngine) GetAllPatterns() map[string]PatternDef { return e.config.Patterns } // GetAllControls returns all controls in the policy func (e *PolicyEngine) GetAllControls() map[string]ControlDef { return e.config.Controls } // GetProblemSolutions returns problem-solution mappings func (e *PolicyEngine) GetProblemSolutions() []ProblemSolutionDef { return e.config.ProblemSolutions } // ============================================================================ // Helper Functions // ============================================================================ func parseSeverity(s string) Severity { switch strings.ToUpper(s) { case "BLOCK": return SeverityBLOCK case "WARN": return SeverityWARN default: return SeverityINFO } } func categorizeControl(id string) string { // Map control IDs to categories technical := map[string]bool{ "C_ENCRYPTION": true, "C_ACCESS_LOGGING": true, } if technical[id] { return "technical" } return "organizational" } // calculateBetrvgConflictScore computes a works council conflict score (0-100). // Higher score = higher risk of escalation with works council. // Only relevant for German organizations with >= 5 employees. func (e *PolicyEngine) calculateBetrvgConflictScore(intake *UseCaseIntake) (int, bool) { if intake.Domain == "" { return 0, false } score := 0 consultationRequired := false // Factor 1: Employee data processing (+10) if intake.DataTypes.PersonalData && intake.DataTypes.EmployeeData { score += 10 consultationRequired = true } // Factor 2: System can monitor behavior/performance (+20) if intake.EmployeeMonitoring { score += 20 consultationRequired = true } // Factor 3: Individualized usage data / logging (+15) if intake.Retention.StorePrompts || intake.Retention.StoreResponses { score += 15 } // Factor 4: Communication analysis (+10) if intake.Purpose.CustomerSupport || intake.Purpose.Marketing { // These purposes on employee data suggest communication analysis if intake.DataTypes.EmployeeData { score += 10 } } // Factor 5: HR / Recruiting context (+20) if intake.HRDecisionSupport { score += 20 consultationRequired = true } // Factor 6: Scoring / Ranking of employees (+10) if intake.Outputs.RankingsOrScores || intake.Outputs.RecommendationsToUsers { if intake.DataTypes.EmployeeData { score += 10 } } // Factor 7: Fully automated decisions (+10) if intake.Automation == "fully_automated" { score += 10 } // Factor 8: Works council NOT consulted (+5) if consultationRequired && !intake.WorksCouncilConsulted { score += 5 } // Cap at 100 if score > 100 { score = 100 } return score, consultationRequired }