package semantic import ( "context" "sync" "time" "github.com/kami/maven/internal/router" ) // ExperimentActVerbs is the act allowlist shared by the experiment's legacy // baseline (the eval fixture and the slice-22 harness), the routed-heads // harness, and the corpus fast-path derivation. One non-test list so the // corpus derivation can never drift from what the measurement router accepts. func ExperimentActVerbs() []string { return []string{ "перезапусти", "перезагрузи", "выключи", "включи", "останови", "запусти", "закрой", "открой", "сделай", "поставь", "restart", "reboot", "stop", "start", "turn off", "turn on", "open", "close", } } func experimentActMatcher() router.ActMatcher { return router.DefaultActMatcher{Fns: ExperimentActVerbs()} } // FastPathOutcome is the derived fast-path classification for one surface. // Matched mirrors TryFastPath; Grammar names the winning stage-0 grammar for // attribution, or the empty string when nothing resolved. type FastPathOutcome struct { Matched bool Grammar string } var ( fastPathOnce sync.Once fastPathRouter *router.Router fastPathGrammars []router.Grammar ) // DeriveFastPath mirrors the production fast path for a surface: it runs // TryFastPath over the daemon's ordered stage-0 grammar list with the // experiment's act allowlist — the exact router the legacy baseline measures — // and reports whether a grammar resolved the utterance. This is the // authoritative derivation for corpus fast_path_resolved metadata. The // classifier, threshold and LLM never run on the fast path, so they are not // wired here. func DeriveFastPath(text string) FastPathOutcome { fastPathOnce.Do(func() { acts := experimentActMatcher() fastPathGrammars = router.StageZeroGrammars(acts) fastPathRouter = router.New(router.Config{ Grammars: fastPathGrammars, Extractor: router.Extractor{ Time: router.StubDateTimeParser{}, Acts: acts, Facts: router.DefaultFactParser{}, }, }) }) res, err := fastPathRouter.TryFastPath(context.Background(), router.NormalizedInput{ Text: text, MatchText: router.NormalizeMatchText(text), }, time.Now()) if err != nil || !res.Matched { return FastPathOutcome{} } // Attribute the winner by replaying TryFastPath's ordered first-accept // walk, including the wake-stripped alternate. A matcher that matched the // shape but declined the content falls through, exactly as the router does. stripped, hadWake := router.StripWakeToken(text) for _, g := range fastPathGrammars { _, matched, ok := g.Evaluate(text) if !matched && hadWake { _, matched, ok = g.Evaluate(stripped) } if matched && ok { return FastPathOutcome{Matched: true, Grammar: g.Name} } } return FastPathOutcome{Matched: true} }