package main import ( "context" "testing" "time" "github.com/kami/maven/internal/ipc" "github.com/kami/maven/internal/memory" "github.com/kami/maven/internal/router" "github.com/kami/maven/internal/tool" "github.com/kami/maven/internal/voice" ) func newFactGateHandler(t *testing.T, now time.Time) (*reactiveHandler, ipc.CoreAPI) { t.Helper() st := newTestStore(t) api := ipc.NewStoreAPI(st) emb := router.NewHashEmbedder(1024) h := &reactiveHandler{ api: api, embedder: emb, router: buildRouter(emb, tool.NewMatcher(api), 0.55, nil), replier: voice.NewStubReplier(), now: func() time.Time { return now }, memStore: memory.NewInMemoryStore(), dataStore: st, } return h, api } // The write half of #470: a question routed to IntentFact must not become a // fact about him, and must not leave a vector behind for recall to serve. func TestActionFact_QuestionIsNotWritten(t *testing.T) { ctx := context.Background() h, api := newFactGateHandler(t, time.Now()) reply := h.actionFact(ctx, router.Decision{ Intent: router.IntentFact, Utterance: "какая последняя версия языка Go?", Slots: router.Slots{Key: "go_version", HasKey: true, Value: `"1.20"`}, }) if _, err := api.LatestFact(ctx, "go_version"); err == nil { t.Fatal("a question was stored as a fact about him") } hits, err := h.memStore.Search(ctx, mustEmbedPassage(t, h, "какая последняя версия языка Go?"), 3) if err != nil { t.Fatalf("memory search: %v", err) } if len(hits) != 0 { t.Fatalf("the question was indexed for recall: %+v", hits) } // It went down the query chain instead. Nothing is configured to answer a // world question in this harness, so "не знаю." is the honest outcome — // what matters is that the turn was answered, not stored. if reply == "" { t.Fatal("the turn was neither stored nor answered") } } // The capture that must survive the gate: an explicit instruction to record, // even though it contains an interrogative. func TestActionFact_ExplicitCaptureStillWrites(t *testing.T) { ctx := context.Background() h, api := newFactGateHandler(t, time.Now()) h.actionFact(ctx, router.Decision{ Intent: router.IntentFact, Utterance: "запиши что я пил воду", Slots: router.Slots{Key: "water", HasKey: true, Value: `"вода"`}, }) f, err := api.LatestFact(ctx, "water") if err != nil { t.Fatalf("an explicit capture was refused: %v", err) } if f.Confidence != 1.0 { t.Errorf("confidence = %v, want 1.0 for a value he said", f.Confidence) } // #493: what recall reads back is the fact, not the sentence he said. // queryMemory returns a fact's text verbatim, so the utterance sitting here // meant "запиши что я пил воду" was the answer to "когда я пил воду?". hits, err := h.memStore.Search(ctx, mustEmbedPassage(t, h, "вода"), 3) if err != nil { t.Fatalf("memory search: %v", err) } if len(hits) != 1 { t.Fatalf("the fact was not indexed once: %+v", hits) } if got := hits[0].Meta["text"]; got != "water — вода" { t.Errorf("indexed text = %q, want the fact", got) } if got := hits[0].Meta["utterance"]; got != "запиши что я пил воду" { t.Errorf("utterance provenance = %q, want it kept alongside", got) } } func TestFactConfidence(t *testing.T) { cases := []struct { utterance, value string want float64 }{ {"запиши что я пил воду", `"вода"`, 1.0}, {"я выпил кофе", `"кофе"`, 1.0}, {"поужинал", "", 1.0}, {"отметь что я полил кактус", `"полил кактус"`, 1.0}, {"какая последняя версия языка Go", `"1.20"`, ungroundedConfidence}, {"кто премьер Японии", `"Тонио Озаки"`, ungroundedConfidence}, } for _, c := range cases { if got := factConfidence(c.utterance, c.value); got != c.want { t.Errorf("factConfidence(%q, %q) = %v, want %v", c.utterance, c.value, got, c.want) } } } func mustEmbedPassage(t *testing.T, h *reactiveHandler, text string) []float32 { t.Helper() vec, err := router.EmbedQuery(context.Background(), h.embedder, text) if err != nil { t.Fatalf("embed %q: %v", text, err) } return vec }