119 lines
6.2 KiB
Markdown
119 lines
6.2 KiB
Markdown
# Slice 1: Typed Ingress Boundary and Route Producer Observability
|
|
|
|
## 1. Files changed
|
|
|
|
**New types:**
|
|
- `internal/router/source.go` — `InputSource`, `InputSourceVoice`, `InputSourceText`, `NormalizedInput`
|
|
- `internal/router/intent.go` — `RouteProducer`, `RouteProducerGrammar/Heads/LLM/Classifier`, `Producer` field on `Decision`
|
|
|
|
**Cascade wiring:**
|
|
- `internal/router/router.go` — `Producer` set at each of the four cascade stages
|
|
|
|
**Observability:**
|
|
- `internal/decision/decision.go` — `InputSource` and `RouteProducer` fields on `Record`; `With()` accepts `inputSource`
|
|
- `internal/store/routingtraces.go` — `RouteProducer` field on `RoutingTrace`
|
|
- `internal/store/migrations.go` — migration #27: `ALTER TABLE routing_traces ADD COLUMN route_producer`
|
|
- `cmd/mavend/routingtrace.go` — persists `RouteProducer` from the decision record
|
|
|
|
**Turn lifecycle:**
|
|
- `cmd/mavend/voice.go` — `turnSource` is now `type turnSource = router.InputSource`; `runTurn` takes `NormalizedInput`; `HandlePushToTalk` and `handleText` construct `NormalizedInput`
|
|
- `cmd/mavend/turnroute.go` — `turnRoute` carries `NormalizedInput`; `newTurnRoute` and `resolve` use it
|
|
|
|
**Test updates (signature适应):**
|
|
- `cmd/mavend/clarify_test.go`, `reactive_notes_test.go`, `reminder_cancel_test.go`, `repair_test.go`, `simulator_test.go`, `turnrole_test.go` — `runTurn` calls updated to `NormalizedInput`
|
|
|
|
**New tests:**
|
|
- `internal/router/boundary_test.go` — 7 tests: type shape, constants, producer per stage
|
|
- `cmd/mavend/boundary_test.go` — 5 tests: convergence, source preservation, producer on record, pre-route empty producer, stage-0 unchanged
|
|
|
|
## 2. Boundary types introduced/reused
|
|
|
|
| Type | Package | Kind | Purpose |
|
|
|---|---|---|---|
|
|
| `NormalizedInput` | `router` | new struct | Typed ingress boundary: `Text string` + `Source InputSource` |
|
|
| `InputSource` | `router` | new `string` type | Channel provenance: `tap:voice`, `tap:text` |
|
|
| `RouteProducer` | `router` | new `string` type | Cascade stage provenance: `grammar`, `heads`, `llm`, `classifier` |
|
|
| `turnSource` | `main` | **alias** for `router.InputSource` | Convenience alias; all existing call sites unchanged |
|
|
|
|
Reused: `router.Source` (destination), `router.Intent`, `router.Decision`, `decision.Record`.
|
|
|
|
## 3. Before/after flow diagram
|
|
|
|
```
|
|
BEFORE:
|
|
HandlePushToTalk → stt → runTurn(ctx, text, sourceVoice)
|
|
handleText → runTurn(ctx, text, sourceText)
|
|
runTurn(ctx, text, src):
|
|
decision.With(ctx, text)
|
|
newTurnRoute(text, now) → rt.text = text
|
|
rt.resolve() → router.Route(ctx, text, now)
|
|
Decision.Utterance = utterance
|
|
[no producer field]
|
|
|
|
AFTER:
|
|
HandlePushToTalk → stt → runTurn(ctx, NormalizedInput{text, sourceVoice})
|
|
handleText → runTurn(ctx, NormalizedInput{text, sourceText})
|
|
runTurn(ctx, input):
|
|
decision.With(ctx, input.Text, input.Source)
|
|
newTurnRoute(input, now) → rt.input = input
|
|
rt.resolve() → router.Route(ctx, input.Text, now)
|
|
Decision.Utterance = utterance
|
|
Decision.Producer = grammar|heads|llm|classifier
|
|
rec.RouteProducer = dec.Producer
|
|
```
|
|
|
|
## 4. Tests added
|
|
|
|
**internal/router/boundary_test.go** (7 tests):
|
|
- `TestNormalizedInputIsMinimalValueObject` — shape pin
|
|
- `TestInputSourceConstants` — tap:voice, tap:text
|
|
- `TestRouteProducerConstants` — grammar, heads, llm, classifier
|
|
- `TestStage0SetsGrammarProducer` — grammar win carries grammar producer
|
|
- `TestClassifierSetsProducer` — classifier floor sets its producer
|
|
- `TestClarifyProducerIsClassifier` — clarified turn carries classifier producer
|
|
- `TestStage0ProducerOnEveryGrammar` — property test over multiple grammars
|
|
|
|
**cmd/mavend/boundary_test.go** (5 tests):
|
|
- `TestTextAndVoiceConvergeOnNormalizedInput` — same utterance, same route intent
|
|
- `TestNormalizedInputSourcePreserved` — source survives to decision record
|
|
- `TestRouteProducerOnDecisionRecord` — producer carried to record
|
|
- `TestPreRouteClaimHasNoRouteProducer` — confirm-claimed turn has empty producer
|
|
- `TestStage0ProducerUnchanged` — grammar stage-0 still produces same intents
|
|
|
|
## 5. Full test/eval results
|
|
|
|
| Suite | Before | After |
|
|
|---|---|---|
|
|
| `go test ./internal/router/` | PASS (1.058s) | PASS (1.568s) |
|
|
| `go test ./internal/router/eval/` | PASS (0.783s) | PASS (1.219s) |
|
|
| `go test ./cmd/mavend/ -run Simulator` | PASS (1.451s) | PASS (1.753s) |
|
|
| `go test ./cmd/mavend/ -run PersonalBoundary` | PASS (0.147s) | PASS (0.183s) |
|
|
| `go test ./cmd/mavend/ -run Eval` | PASS (0.015s) | PASS (0.012s) |
|
|
| `go test ./cmd/mavend/` (full) | PASS (1.451s) | PASS (22.311s) |
|
|
|
|
The timing increase in `cmd/mavend` full suite is from the new boundary tests (293 lines of new test code), not from a regression.
|
|
|
|
## 6. Confirmation that routing outputs and action behavior are unchanged
|
|
|
|
- `internal/router/eval/` scores the held-out fixture against the same cascade; the number did not move.
|
|
- `cmd/mavend -run Simulator` replays deterministic scripted days; all scenario assertions pass identically.
|
|
- `cmd/mavend -run PersonalBoundary` exercises the personal boundary query chain; passes identically.
|
|
- Stage-0 grammars: same ordering in `StageZeroGrammars()`, same matching semantics, same confidence 1.0.
|
|
- `RouteProducer` is a new field with zero value `""` for existing code paths that don't set it; no existing consumer reads it.
|
|
- `NormalizedInput` is the same `(text string, source turnSource)` pair passed as a struct; no transformation applied.
|
|
|
|
## 7. Semantic changes required
|
|
|
|
**None.** The refactoring is purely structural:
|
|
- `turnSource` became a type alias for `router.InputSource` — identical underlying type, no conversion needed at any call site.
|
|
- `runTurn` takes `NormalizedInput` instead of `(text, src)` — the destructuring `text := input.Text; src := input.Source` at the top of the function body produces identical local variables.
|
|
- `decision.With` gained an `inputSource` parameter — a string stored on the record, never read back during routing.
|
|
- `RouteProducer` is a new field on `Decision` — set after the decision is already produced, never consumed by the cascade.
|
|
|
|
## 8. Commit hashes
|
|
|
|
```
|
|
87a3b16 router: introduce typed ingress boundary and route producer observability
|
|
a55d909 router: add boundary tests for typed ingress and route producer
|
|
```
|