feat(iace): generic cross-domain leak gates + norm vocab reconciliation

- Domain-gate ~15 foreign machine classes (pool, amusement, paint booth,
  tank farm, reactor, lathe/chips, saw, film/carton, robot, mobile cab,
  asbestos, playground swing) in pattern_domain_gates.go so ungated hazard
  patterns stop leaking into unrelated machines; matching emit keywords
  added in keyword_dictionary.go (gate+emit share one vocabulary).
- Extend the cross-domain precision guard to 6 machine classes (press,
  cobot, motor, welding + the 2 GTs) with per-case homeDomains, so a
  machine's own domain terms are never flagged. GT coverage stays 100%.
- Reconcile the fine-grained norm machine-type vocabulary (455 keys) with
  the 68 canonical dropdown keys via canonicalMachineType() family folding
  in matchNorm — welding 0->17, robotics_cobot 0->6, press 8->13,
  circular_saw 1->35 machine-specific C-norms. Pattern gating left strict.
- Fix initialize?force=true summary index-shift that mislabeled counts
  (reported matched-patterns as "hazards"); now uses named step vars.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-06-10 22:29:10 +02:00
parent b7a7e70731
commit 005a2ed711
10 changed files with 656 additions and 9 deletions
@@ -37,11 +37,20 @@ func firedHazardsForCase(c gtCase) []PatternMatch {
return fired
}
// TestCrossDomainPrecision asserts that no fired pattern is foreign to the GT
// machine — neither machine-type-incompatible nor matching a foreign-domain term.
// 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) {
for _, c := range gtBenchmarkCases {
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))
@@ -50,6 +59,9 @@ func TestCrossDomainPrecision(t *testing.T) {
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