package audit import ( "sort" "github.com/breakpilot/ai-compliance-sdk/internal/iace" ) // runConsistencyImpl asks: does this component, with its own tags PLUS the // tags of its TypicalEnergySources, actually trigger at least one pattern // in every category listed in its TypicalHazardCategories? // // A component declares "this is what I am dangerous for" and the engine // turns that declaration into hazards through patterns. If no pattern can // fire from the component's tag set, the declaration is decorative — the // engine will never produce a hazard in that category for this component, // even though the library author said it should. func init() { runConsistencyImpl = runConsistency } func runConsistency() ConsistencyReport { comps := iace.GetComponentLibrary() energies := iace.GetEnergySources() patterns := iace.AllPatterns() energyByID := map[string]iace.EnergySourceEntry{} for _, e := range energies { energyByID[e.ID] = e } report := ConsistencyReport{TotalComponents: len(comps)} for _, c := range comps { if len(c.TypicalHazardCategories) == 0 { report.Consistent++ continue } effective := buildEffectiveTags(c, energyByID) covered := categoriesCoveredByPatterns(effective, c.MapsToComponentType, patterns) var missing []string for _, cat := range c.TypicalHazardCategories { if !covered[cat] { missing = append(missing, cat) } } if len(missing) == 0 { report.Consistent++ continue } result := ComponentResult{ ComponentID: c.ID, NameDE: c.NameDE, DeclaredCategories: c.TypicalHazardCategories, } for cat := range covered { result.CoveredCategories = append(result.CoveredCategories, cat) } sort.Strings(result.CoveredCategories) for _, cat := range missing { result.MissingForCategories = append(result.MissingForCategories, CategoryGap{ Category: cat, SuggestedTags: suggestTagsForCategory(cat, effective, patterns), }) } report.Incomplete++ report.IncompleteComponents = append(report.IncompleteComponents, result) } sort.Slice(report.IncompleteComponents, func(i, j int) bool { return report.IncompleteComponents[i].ComponentID < report.IncompleteComponents[j].ComponentID }) return report } func buildEffectiveTags(c iace.ComponentLibraryEntry, energyByID map[string]iace.EnergySourceEntry) map[string]bool { set := map[string]bool{} for _, t := range c.Tags { set[t] = true } for _, eID := range c.TypicalEnergySources { e, ok := energyByID[eID] if !ok { continue } for _, t := range e.Tags { set[t] = true } } return set } // categoriesCoveredByPatterns iterates patterns and finds which // GeneratedHazardCats can fire given the component's effective tags. // We ignore lifecycle, op-state, and human-role filters — those are // project-level. The audit asks "can the library produce ANY hazard in // this category for this component if the project configures everything // reasonably?" func categoriesCoveredByPatterns(tags map[string]bool, _ string, patterns []iace.HazardPattern) map[string]bool { covered := map[string]bool{} for _, p := range patterns { if !tagsCover(tags, p.RequiredComponentTags) { continue } if !tagsCover(tags, p.RequiredEnergyTags) { continue } for _, cat := range p.GeneratedHazardCats { covered[cat] = true } } return covered } func tagsCover(have map[string]bool, required []string) bool { for _, t := range required { if !have[t] { return false } } return true } // suggestTagsForCategory looks at patterns that DO generate this category // and identifies the tags that would close the gap. Returns the tags most // commonly required by patterns in that category, minus what the component // already has. func suggestTagsForCategory(cat string, have map[string]bool, patterns []iace.HazardPattern) []string { counts := map[string]int{} for _, p := range patterns { matchCat := false for _, c := range p.GeneratedHazardCats { if c == cat { matchCat = true break } } if !matchCat { continue } for _, t := range p.RequiredComponentTags { if !have[t] { counts[t]++ } } for _, t := range p.RequiredEnergyTags { if !have[t] { counts[t]++ } } } type kv struct { tag string n int } var sorted []kv for t, n := range counts { sorted = append(sorted, kv{t, n}) } sort.Slice(sorted, func(i, j int) bool { return sorted[i].n > sorted[j].n }) var out []string for i, s := range sorted { if i >= 6 { break } out = append(out, s.tag) } return out }