package iace import ( "sort" "strings" "testing" ) // The cross-domain term list lives in gt_benchmark_harness_test.go // (foreignDomainTerms, term → home domain). This precision guard reuses it and, // unlike the diagnostic TestGT_DomainLeakage, runs the FULL production gating // path including the relevance backstop, then ASSERTS zero leaks. It catches // machine-type wiring regressions and weak-tag (structural_part) leaks in CI. // firedHazardsForCase runs the exact production gating path (parse → engine match // → relevance backstop) for one GT case and returns the surviving patterns. func firedHazardsForCase(c gtCase) []PatternMatch { narrative := c.narrativeOverride pr := ParseNarrative(narrative, c.machineType) input := parseResultToMatchInput(pr, c.machineType) compNames := make([]string, 0, len(pr.Components)) for _, comp := range pr.Components { if comp.Negated { continue } compNames = append(compNames, NormalizeDEPublic(comp.NameDE)) } out := NewPatternEngine().Match(input) fired := make([]PatternMatch, 0, len(out.MatchedPatterns)) for _, mp := range out.MatchedPatterns { if IsPatternRelevant(mp, narrative, compNames) { fired = append(fired, mp) } } return fired } // TestCrossDomainPrecision asserts that no fired pattern is foreign to the // machine — neither machine-type-incompatible nor matching a foreign-domain // term. Runs over the 2 ground truths AND the 4 Grenzen-only machines // (press/cobot/motor/welding), so the gating guard is validated across six // machine classes. A term whose domain is in the case's homeDomains is its // OWN domain and never counts as a leak. func TestCrossDomainPrecision(t *testing.T) { cases := append(append([]gtCase{}, gtBenchmarkCases...), precisionOnlyCases...) for _, c := range cases { c := c home := make(map[string]bool, len(c.homeDomains)) for _, d := range c.homeDomains { home[d] = true } t.Run(c.name, func(t *testing.T) { fired := firedHazardsForCase(c) t.Logf("%s (%s): %d patterns fired", c.name, c.machineType, len(fired)) var domainLeaks []string for _, mp := range fired { text := normalizeDE(mp.PatternName + " " + mp.ZoneDE + " " + mp.ScenarioDE) for term, domain := range foreignDomainTerms { if home[domain] { continue // this domain is native to the machine } if strings.Contains(text, term) { domainLeaks = append(domainLeaks, domain+"/"+term+" → "+mp.PatternName) break } } } sort.Strings(domainLeaks) for _, l := range domainLeaks { t.Logf(" FOREIGN-DOMAIN: %s", l) } if len(domainLeaks) > 0 { t.Errorf("%s: %d cross-domain leak(s) — patterns from foreign machine classes fired", c.name, len(domainLeaks)) } }) } }