diff --git a/ai-compliance-sdk/internal/iace/compliance_crossover.go b/ai-compliance-sdk/internal/iace/compliance_crossover.go index f99d1295..0166736a 100644 --- a/ai-compliance-sdk/internal/iace/compliance_crossover.go +++ b/ai-compliance-sdk/internal/iace/compliance_crossover.go @@ -104,39 +104,14 @@ func GetProjectComplianceTriggers(hazards []Hazard, patterns []HazardPattern) *C } } -// AllPatterns returns every hazard pattern from all pattern sources. -// This mirrors the aggregation in NewPatternEngine but returns just the slice. +// AllPatterns returns every registered hazard pattern. Delegates to +// collectAllPatterns() in pattern_registry.go so new pattern sources only +// need to be added in one place. Pre-2026-05-21 this function maintained +// a duplicate enumeration which silently drifted from the registry — +// CRA, ISO12100-gap, robot-cell, CNC, VDMA, textile-agri, GT-bremse and +// secondary-harm patterns were invisible to AllPatterns callers. 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 + return collectAllPatterns() } // extractPatternIDs scans a text for "HP" followed by digits and adds diff --git a/ai-compliance-sdk/internal/iace/hazard_pattern_types.go b/ai-compliance-sdk/internal/iace/hazard_pattern_types.go index 0ea3f0fe..0432bd0b 100644 --- a/ai-compliance-sdk/internal/iace/hazard_pattern_types.go +++ b/ai-compliance-sdk/internal/iace/hazard_pattern_types.go @@ -83,6 +83,12 @@ type HazardPattern struct { // feeds into the PLr (required Performance Level) computation, // see ComputePLr. DefaultAvoidability int `json:"default_avoidability,omitempty"` // 1 or 2 + // SecondaryHarms describes consequential damage chains beyond the + // classical IACE Hazard→Harm step: end-customer safety, product + // liability, food safety, environmental, reputation, financial. + // See secondary_harms.go and the strategy discussion (2026-05-20). + // Empty for hazards with no downstream chain. + SecondaryHarms []SecondaryHarm `json:"secondary_harms,omitempty"` } // ComputePLr returns the required Performance Level (PLr) per EN ISO diff --git a/ai-compliance-sdk/internal/iace/hazard_patterns_secondary_demo.go b/ai-compliance-sdk/internal/iace/hazard_patterns_secondary_demo.go new file mode 100644 index 00000000..79392047 --- /dev/null +++ b/ai-compliance-sdk/internal/iace/hazard_patterns_secondary_demo.go @@ -0,0 +1,127 @@ +package iace + +// Demonstration patterns showing how the SecondaryHarms field carries +// downstream-consequence information through the IACE engine. +// +// Two real-world scenarios are encoded: +// +// HP2000 — Glass-shard injection in carbonated-beverage bottling +// (the "Cola splitter" example from the IACE strategy +// discussion). Primary harm is the operator hit by flying +// shards; the secondary chain is product-liability towards +// supermarket end-customers. +// +// HP2001 — Cross-contamination in pharma fill-finish lines. +// Primary harm is operator exposure; secondary chain is +// patient harm + recall under §74a AMG. +// +// These two patterns are sufficient as a contract test for the +// SecondaryHarms field. Library coverage of more scenarios is a +// follow-up task once the persistence layer (DB migration) lands. + +func GetSecondaryHarmDemoPatterns() []HazardPattern { + return []HazardPattern{ + { + ID: "HP2000", + NameDE: "Glasbruch in Karbonisierungs-Abfueller (Hochdruck)", + NameEN: "Glass shatter in carbonated bottling line", + RequiredComponentTags: []string{"crush_point", "high_pressure"}, + RequiredEnergyTags: []string{"pneumatic_pressure"}, + GeneratedHazardCats: []string{"mechanical_hazard"}, + Priority: 90, + MachineTypes: []string{"bottling", "food_processing", "packaging"}, + ScenarioDE: "Glasflasche platzt unter CO2-Druck waehrend der Abfuellung. " + + "Splitter erreichen den Bediener und koennen ferner in nachfolgende " + + "Flaschen eingetragen werden.", + TriggerDE: "Materialfehler, ueberhoehter Innendruck, Foerderstoss", + HarmDE: "Schnittverletzung Auge/Hand des Bedieners", + AffectedDE: "Abfueller, Mitarbeiter Linie", + ZoneDE: "Karussell, Schutzkapsel, Foerderband-Auslauf", + DefaultSeverity: 4, + DefaultExposure: 3, + ISO12100Section: "6.4.5.5 Schleudernde Teile", + SecondaryHarms: []SecondaryHarm{ + { + Type: SecondaryHarmConsumerSafety, + Description: "Restsplitter in der Folgeflasche erreichen ueber den Handel " + + "den Endkunden. Verletzungsrisiko Mund/Speiseroehre.", + LegalBasis: "ProdHaftG §1, VO (EU) Nr. 178/2002 Art. 14", + SuggestedMitigations: []string{ + "Spueltunnel nach Abfuellung", + "Inline-Kamera mit Glasbrucherkennung", + "Sperrzone fuer 2 Folgeflaschen bei Bruchereignis", + "Glasbruchsensor an Karussell mit Linie-Stopp", + }, + Owner: "product_safety", + }, + { + Type: SecondaryHarmFoodSafety, + Description: "Rueckruf- und Meldepflicht bei Inverkehrbringen unsicherer " + + "Lebensmittel; Rueckverfolgbarkeit Chargen-genau erforderlich.", + LegalBasis: "VO (EU) 178/2002 Art. 18, 19; LFGB §40", + SuggestedMitigations: []string{ + "Chargen-Tracking bis Endhaendler", + "Schnellwarnsystem RASFF aktiviert halten", + "Rueckruf-SOP getestet", + }, + Owner: "qm", + }, + { + Type: SecondaryHarmReputation, + Description: "Pressemitteilung und Aktienkurs-Reaktion bei Verbraucher-" + + "verletzungen / behoerdlichem Rueckruf.", + LegalBasis: "ISO 31000 Unternehmensrisiko", + SuggestedMitigations: []string{ + "Krisenkommunikations-Plan", + "PR-Bereitschaft 24/7", + }, + Owner: "enterprise_risk", + }, + }, + }, + { + ID: "HP2001", + NameDE: "Kreuzkontamination Pharma Fill-Finish", + NameEN: "Cross-contamination pharma fill-finish", + RequiredComponentTags: []string{"chemical_risk"}, + RequiredEnergyTags: []string{"pneumatic_pressure"}, + GeneratedHazardCats: []string{"chemical_hazard"}, + Priority: 92, + MachineTypes: []string{"pharmaceutical", "food_processing"}, + ScenarioDE: "Wirkstoff-Rueckstand aus Vorcharge im Linienzwischenraum kontaminiert " + + "die Folgecharge.", + TriggerDE: "Mangelhaftes CIP, Spuelvolumen unterhalb Validierung", + HarmDE: "Bedienerexposition bei Probennahme", + AffectedDE: "Anlagenbediener, Probenehmer", + ZoneDE: "Abfuelllinie zwischen Vorlage und Filler", + DefaultSeverity: 4, + DefaultExposure: 2, + ISO12100Section: "6.4.4 Chemische und biologische Gefaehrdungen", + SecondaryHarms: []SecondaryHarm{ + { + Type: SecondaryHarmConsumerSafety, + Description: "Patient erhaelt Arzneimittel mit unzulaessiger Beimischung; " + + "Wirkungsbeeintraechtigung oder unerwuenschte Wirkung moeglich.", + LegalBasis: "AMG §5 (Verkehrsfaehigkeit), §74a (Stufenplan)", + SuggestedMitigations: []string{ + "CIP-Validierung mit TOC- und Conductivity-Limits", + "Dedizierte Linien fuer Hochpotente Wirkstoffe", + "Stufenplan-Meldung bei Verdacht", + }, + Owner: "qm", + }, + { + Type: SecondaryHarmProductLiability, + Description: "Haftung des Inverkehrbringers nach AMG §84 (Gefaehrdungshaftung " + + "bei Arzneimittelschaeden, verschuldensunabhaengig).", + LegalBasis: "AMG §84", + SuggestedMitigations: []string{ + "Deckung Produkthaftpflicht ueber gesetzliches Minimum", + "Chargen-Rueckhaltemuster 12 Monate ueber MHD hinaus", + }, + Owner: "legal", + }, + }, + }, + } +} diff --git a/ai-compliance-sdk/internal/iace/pattern_registry.go b/ai-compliance-sdk/internal/iace/pattern_registry.go index 6c54c829..0b7c4e95 100644 --- a/ai-compliance-sdk/internal/iace/pattern_registry.go +++ b/ai-compliance-sdk/internal/iace/pattern_registry.go @@ -42,5 +42,6 @@ func collectAllPatterns() []HazardPattern { patterns = append(patterns, GetGTBremseHazardPatterns()...) // HP1710-HP1729 GT Bremse coverage gaps patterns = append(patterns, GetISO12100GapPatterns()...) // HP1900-HP1909 ISO 12100 Annex B gaps (Vakuum, Federn, Rutsch, Hochdruckinjektion, Ersticken) patterns = append(patterns, GetCRAPatterns()...) // HP1910-HP1918 CRA / DIN EN 40000-1-2 cyber-resilience spur + patterns = append(patterns, GetSecondaryHarmDemoPatterns()...) // HP2000-HP2001 secondary harm chain demos (Cola splitter, Pharma) return patterns } diff --git a/ai-compliance-sdk/internal/iace/secondary_harms.go b/ai-compliance-sdk/internal/iace/secondary_harms.go new file mode 100644 index 00000000..3ad4f008 --- /dev/null +++ b/ai-compliance-sdk/internal/iace/secondary_harms.go @@ -0,0 +1,89 @@ +package iace + +// SecondaryHarm models the consequential damage chain triggered by a primary +// hazard. The classical IACE / ISO-12100 model treats Hazard -> Harm as a +// single step ("operator gets crushed"). BreakPilot extends this with a +// follow-on chain so the risk assessment can address: +// +// - consumer_safety: end customer exposed to defective product +// (e.g. glass shards in a bottled drink that reaches a supermarket) +// - product_liability: manufacturer liability under ProdHaftG / EU PLD +// - food_safety: traceability and recall obligations (VO 178/2002) +// - environmental: spill, contamination, waste-disposal consequence +// - reputation: brand damage that escalates to investor / market level +// - financial: direct cost (lawsuit, recall, fine) +// +// This struct is the data contract; persistence is deferred to a future +// migration. The pattern library can already attach SecondaryHarms to a +// HazardPattern; the API layer surfaces them on hazard generation. +// +// See memory project_attribution_strategy.md plus the "Cola splitter" worked +// example from the IACE strategy discussion (2026-05-20). +type SecondaryHarm struct { + // Type is one of the SecondaryHarmType* constants below. + Type string `json:"type"` + + // Description is a single sentence describing the secondary harm + // scenario in concrete terms ("Splitter in Folgeflasche bei + // Karussell-Abfueller -> Endkunde verletzt"). + Description string `json:"description"` + + // LegalBasis cites the legal framework that turns the secondary harm + // into an actionable obligation (e.g. "ProdHaftG §1" or "VO 178/2002 + // Art. 14"). Helps auditors trace the obligation. + LegalBasis string `json:"legal_basis,omitempty"` + + // SuggestedMitigations is a free-text list of measures specific to + // the secondary chain (e.g. "Spueltunnel", "Inline-Kamera", + // "Glasbruchsensor"). Distinct from the primary-mitigations because + // they protect downstream stakeholders, not the operator. + SuggestedMitigations []string `json:"suggested_mitigations,omitempty"` + + // Owner identifies the role responsible for handling this secondary + // harm in the customer organisation. Common values: + // "qm" / "product_safety" / "enterprise_risk" / "legal" + // Empty if responsibility is shared. + Owner string `json:"owner,omitempty"` +} + +// SecondaryHarmType constants — kept short and stable. +const ( + SecondaryHarmConsumerSafety = "consumer_safety" + SecondaryHarmProductLiability = "product_liability" + SecondaryHarmFoodSafety = "food_safety" + SecondaryHarmEnvironmental = "environmental" + SecondaryHarmReputation = "reputation" + SecondaryHarmFinancial = "financial" +) + +// AllSecondaryHarmTypes returns the canonical six categories in the order +// they should appear in UI dropdowns. +func AllSecondaryHarmTypes() []string { + return []string{ + SecondaryHarmConsumerSafety, + SecondaryHarmProductLiability, + SecondaryHarmFoodSafety, + SecondaryHarmEnvironmental, + SecondaryHarmReputation, + SecondaryHarmFinancial, + } +} + +// SecondaryHarmLabelDE returns the human-readable German label. +func SecondaryHarmLabelDE(t string) string { + switch t { + case SecondaryHarmConsumerSafety: + return "Endkundensicherheit" + case SecondaryHarmProductLiability: + return "Produkthaftung" + case SecondaryHarmFoodSafety: + return "Lebensmittelsicherheit" + case SecondaryHarmEnvironmental: + return "Umweltschaden" + case SecondaryHarmReputation: + return "Reputation/Marke" + case SecondaryHarmFinancial: + return "Finanzieller Schaden" + } + return t +}