Split 6 oversized files (719–882 LOC each) into focused files under 500 LOC: - policy_engine.go → types, loader, eval, gen (4 files) - legal_rag.go → types, client, http, context, scroll (5 files) - ai_act_module.go → module, yaml, obligations (3 files) - nis2_module.go → module, yaml, obligations + shared obligation_yaml_types.go (3+1 files) - financial_policy.go → types, engine (2 files) - dsgvo_module.go → module, yaml, obligations (3 files) All in package ucca, zero exported symbol renames, go test ./internal/ucca/... passes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
268 lines
8.1 KiB
Go
268 lines
8.1 KiB
Go
package ucca
|
|
|
|
// ============================================================================
|
|
// DSGVO Module
|
|
// ============================================================================
|
|
//
|
|
// This module implements the GDPR (DSGVO - Datenschutz-Grundverordnung) obligations.
|
|
//
|
|
// Split into:
|
|
// - dsgvo_module.go — struct, constants, classification, derive methods, decision tree
|
|
// - dsgvo_yaml.go — YAML loading and conversion helpers
|
|
// - dsgvo_obligations.go — hardcoded fallback obligations/controls/deadlines
|
|
//
|
|
// ============================================================================
|
|
|
|
// DSGVOModule implements the RegulationModule interface for DSGVO
|
|
type DSGVOModule struct {
|
|
obligations []Obligation
|
|
controls []ObligationControl
|
|
incidentDeadlines []IncidentDeadline
|
|
decisionTree *DecisionTree
|
|
loaded bool
|
|
}
|
|
|
|
var (
|
|
// DSGVOSpecialCategories contains Article 9 special categories of personal data
|
|
DSGVOSpecialCategories = map[string]bool{
|
|
"racial_ethnic_origin": true,
|
|
"political_opinions": true,
|
|
"religious_beliefs": true,
|
|
"trade_union_membership": true,
|
|
"genetic_data": true,
|
|
"biometric_data": true,
|
|
"health_data": true,
|
|
"sexual_orientation": true,
|
|
}
|
|
|
|
// DSGVOHighRiskProcessing contains high risk processing activities (Art. 35)
|
|
DSGVOHighRiskProcessing = map[string]bool{
|
|
"systematic_monitoring": true,
|
|
"automated_decisions": true,
|
|
"large_scale_special": true,
|
|
"public_area_monitoring": true,
|
|
"profiling": true,
|
|
"vulnerable_persons": true,
|
|
}
|
|
)
|
|
|
|
// NewDSGVOModule creates a new DSGVO module, loading obligations from YAML
|
|
func NewDSGVOModule() (*DSGVOModule, error) {
|
|
m := &DSGVOModule{
|
|
obligations: []Obligation{},
|
|
controls: []ObligationControl{},
|
|
incidentDeadlines: []IncidentDeadline{},
|
|
}
|
|
|
|
if err := m.loadFromYAML(); err != nil {
|
|
m.loadHardcodedObligations()
|
|
}
|
|
|
|
m.buildDecisionTree()
|
|
m.loaded = true
|
|
|
|
return m, nil
|
|
}
|
|
|
|
// ID returns the module identifier
|
|
func (m *DSGVOModule) ID() string { return "dsgvo" }
|
|
|
|
// Name returns the human-readable name
|
|
func (m *DSGVOModule) Name() string { return "DSGVO (Datenschutz-Grundverordnung)" }
|
|
|
|
// Description returns a brief description
|
|
func (m *DSGVOModule) Description() string {
|
|
return "EU-Datenschutz-Grundverordnung (Verordnung (EU) 2016/679) - Schutz personenbezogener Daten"
|
|
}
|
|
|
|
// IsApplicable checks if DSGVO applies to the organization
|
|
func (m *DSGVOModule) IsApplicable(facts *UnifiedFacts) bool {
|
|
if !facts.DataProtection.ProcessesPersonalData {
|
|
return false
|
|
}
|
|
if facts.Organization.EUMember || facts.DataProtection.OffersToEU || facts.DataProtection.MonitorsEUIndividuals {
|
|
return true
|
|
}
|
|
return facts.DataProtection.ProcessesPersonalData
|
|
}
|
|
|
|
// GetClassification returns the DSGVO classification as string
|
|
func (m *DSGVOModule) GetClassification(facts *UnifiedFacts) string {
|
|
if !m.IsApplicable(facts) {
|
|
return "nicht_anwendbar"
|
|
}
|
|
if m.hasHighRiskProcessing(facts) {
|
|
if facts.DataProtection.IsController {
|
|
return "verantwortlicher_hohes_risiko"
|
|
}
|
|
return "auftragsverarbeiter_hohes_risiko"
|
|
}
|
|
if facts.DataProtection.IsController {
|
|
return "verantwortlicher"
|
|
}
|
|
return "auftragsverarbeiter"
|
|
}
|
|
|
|
func (m *DSGVOModule) hasHighRiskProcessing(facts *UnifiedFacts) bool {
|
|
for _, cat := range facts.DataProtection.SpecialCategories {
|
|
if DSGVOSpecialCategories[cat] {
|
|
return true
|
|
}
|
|
}
|
|
for _, activity := range facts.DataProtection.HighRiskActivities {
|
|
if DSGVOHighRiskProcessing[activity] {
|
|
return true
|
|
}
|
|
}
|
|
if facts.DataProtection.DataSubjectCount > 10000 {
|
|
return true
|
|
}
|
|
if facts.DataProtection.SystematicMonitoring {
|
|
return true
|
|
}
|
|
if facts.DataProtection.AutomatedDecisions && facts.DataProtection.LegalEffects {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (m *DSGVOModule) requiresDPO(facts *UnifiedFacts) bool {
|
|
if facts.Organization.IsPublicAuthority {
|
|
return true
|
|
}
|
|
if facts.DataProtection.SystematicMonitoring && facts.DataProtection.DataSubjectCount > 10000 {
|
|
return true
|
|
}
|
|
if len(facts.DataProtection.SpecialCategories) > 0 && facts.DataProtection.DataSubjectCount > 10000 {
|
|
return true
|
|
}
|
|
if facts.Organization.Country == "DE" && facts.Organization.EmployeeCount >= 20 {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// DeriveObligations derives all applicable DSGVO obligations
|
|
func (m *DSGVOModule) DeriveObligations(facts *UnifiedFacts) []Obligation {
|
|
if !m.IsApplicable(facts) {
|
|
return []Obligation{}
|
|
}
|
|
|
|
var result []Obligation
|
|
isHighRisk := m.hasHighRiskProcessing(facts)
|
|
needsDPO := m.requiresDPO(facts)
|
|
isController := facts.DataProtection.IsController
|
|
usesProcessors := facts.DataProtection.UsesExternalProcessor
|
|
|
|
for _, obl := range m.obligations {
|
|
if m.obligationApplies(obl, isController, isHighRisk, needsDPO, usesProcessors, facts) {
|
|
customized := obl
|
|
customized.RegulationID = m.ID()
|
|
result = append(result, customized)
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
func (m *DSGVOModule) obligationApplies(obl Obligation, isController, isHighRisk, needsDPO, usesProcessors bool, facts *UnifiedFacts) bool {
|
|
switch obl.AppliesWhen {
|
|
case "always":
|
|
return true
|
|
case "controller":
|
|
return isController
|
|
case "processor":
|
|
return !isController
|
|
case "high_risk":
|
|
return isHighRisk
|
|
case "needs_dpo":
|
|
return needsDPO
|
|
case "uses_processors":
|
|
return usesProcessors
|
|
case "controller_or_processor":
|
|
return true
|
|
case "special_categories":
|
|
return len(facts.DataProtection.SpecialCategories) > 0
|
|
case "cross_border":
|
|
return facts.DataProtection.CrossBorderProcessing
|
|
case "":
|
|
return true
|
|
default:
|
|
return true
|
|
}
|
|
}
|
|
|
|
// DeriveControls derives all applicable DSGVO controls
|
|
func (m *DSGVOModule) DeriveControls(facts *UnifiedFacts) []ObligationControl {
|
|
if !m.IsApplicable(facts) {
|
|
return []ObligationControl{}
|
|
}
|
|
|
|
var result []ObligationControl
|
|
for _, ctrl := range m.controls {
|
|
ctrl.RegulationID = m.ID()
|
|
result = append(result, ctrl)
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// GetDecisionTree returns the DSGVO applicability decision tree
|
|
func (m *DSGVOModule) GetDecisionTree() *DecisionTree { return m.decisionTree }
|
|
|
|
// GetIncidentDeadlines returns DSGVO breach notification deadlines
|
|
func (m *DSGVOModule) GetIncidentDeadlines(facts *UnifiedFacts) []IncidentDeadline {
|
|
if !m.IsApplicable(facts) {
|
|
return []IncidentDeadline{}
|
|
}
|
|
return m.incidentDeadlines
|
|
}
|
|
|
|
func (m *DSGVOModule) buildDecisionTree() {
|
|
m.decisionTree = &DecisionTree{
|
|
ID: "dsgvo_applicability",
|
|
Name: "DSGVO Anwendbarkeits-Entscheidungsbaum",
|
|
RootNode: &DecisionNode{
|
|
ID: "root",
|
|
Question: "Verarbeitet Ihre Organisation personenbezogene Daten?",
|
|
YesNode: &DecisionNode{
|
|
ID: "eu_check",
|
|
Question: "Ist Ihre Organisation in der EU/EWR ansässig?",
|
|
YesNode: &DecisionNode{
|
|
ID: "eu_established",
|
|
Result: "DSGVO anwendbar",
|
|
Explanation: "Die DSGVO gilt für alle in der EU ansässigen Organisationen, die personenbezogene Daten verarbeiten.",
|
|
},
|
|
NoNode: &DecisionNode{
|
|
ID: "offering_check",
|
|
Question: "Bieten Sie Waren oder Dienstleistungen an Personen in der EU an?",
|
|
YesNode: &DecisionNode{
|
|
ID: "offering_to_eu",
|
|
Result: "DSGVO anwendbar",
|
|
Explanation: "Die DSGVO gilt für Organisationen außerhalb der EU, die Waren/Dienstleistungen an EU-Bürger anbieten.",
|
|
},
|
|
NoNode: &DecisionNode{
|
|
ID: "monitoring_check",
|
|
Question: "Beobachten Sie das Verhalten von Personen in der EU?",
|
|
YesNode: &DecisionNode{
|
|
ID: "monitoring_eu",
|
|
Result: "DSGVO anwendbar",
|
|
Explanation: "Die DSGVO gilt für Organisationen, die das Verhalten von Personen in der EU beobachten (z.B. Tracking, Profiling).",
|
|
},
|
|
NoNode: &DecisionNode{
|
|
ID: "not_applicable",
|
|
Result: "DSGVO nicht anwendbar",
|
|
Explanation: "Die DSGVO ist nicht anwendbar, da keine der Anknüpfungskriterien erfüllt ist.",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
NoNode: &DecisionNode{
|
|
ID: "no_personal_data",
|
|
Result: "DSGVO nicht anwendbar",
|
|
Explanation: "Die DSGVO gilt nur für die Verarbeitung personenbezogener Daten.",
|
|
},
|
|
},
|
|
}
|
|
}
|