router: introduce typed ingress boundary and route producer observability

First behavior-preserving slice of the Maven redesign. Establishes
explicit ingress/routing boundaries and enough observability to refactor
later without changing current routing, action, clarification, or
execution semantics.

Types introduced:
- NormalizedInput (internal/router/source.go): Text + InputSource,
  the typed ingress boundary replacing raw string at the turn entry.
- InputSource (internal/router/source.go): channel provenance enum
  (tap:voice, tap:text). Reuses the existing turnSource distinction.
- RouteProducer (internal/router/intent.go): which cascade stage
  produced the decision (grammar, heads, llm, classifier).

Changes:
- Decision carries a Producer RouteProducer field, set at each cascade
  stage (grammar, heads, LLM, classifier).
- turnRoute carries NormalizedInput instead of bare text string.
- runTurn takes NormalizedInput instead of (text, src).
- decision.Record carries InputSource and RouteProducer for
  observability; RoutingTrace persists route_producer (migration #27).
- turnSource is now a type alias for router.InputSource.

Behavior preserved:
- Stage-0 grammars unchanged: same order, same matching, same confidence.
- Cascade fallthrough order unchanged (grammar → heads → llm → classifier).
- Clarification behavior unchanged.
- Action dispatch unchanged.
- No new linguistic normalization.
This commit is contained in:
2026-09-05 20:16:40 +04:00
parent 2f338a1ab6
commit 87a3b163e7
15 changed files with 109 additions and 42 deletions
+23
View File
@@ -76,3 +76,26 @@ func ValidSource(s Source) bool {
}
return false
}
// InputSource — which channel this utterance arrived on. The same provenance
// vocabulary facts use (internal/event). Threaded through the turn because a
// turn can write a fact, and a fact that lies about where it came from is
// worse than no fact: provenance is the first column read when asking why a
// daemon-wide setting is the way it is.
type InputSource string
const (
// InputSourceVoice — a real microphone (PushToTalk).
InputSourceVoice InputSource = "tap:voice"
// InputSourceText — mavweb /api/chat, telegram, or any text entry point.
InputSourceText InputSource = "tap:text"
)
// NormalizedInput — the typed ingress boundary for a turn. Text is the raw
// utterance after STT (voice) or as typed (text). Source identifies the
// channel. This slice performs no new linguistic normalization: text and voice
// paths continue to converge onto the same turn path as they did before.
type NormalizedInput struct {
Text string
Source InputSource
}