package store import ( "context" "database/sql" "testing" "time" ) // The write-path half of #493: recall of a fact must read back the fact, not // the sentence he happened to say. func TestFactRecallText(t *testing.T) { for _, tc := range []struct { name, key, value, want string }{ {"json value", "go_version", `"1.20"`, "go version — 1.20"}, {"plain value", "water", "выпил", "water — выпил"}, {"no value", "shower", "", "shower"}, {"underscores are spoken as spaces", "espresso_machine", `"чистая"`, "espresso machine — чистая"}, } { t.Run(tc.name, func(t *testing.T) { if got := FactRecallText(tc.key, tc.value); got != tc.want { t.Fatalf("FactRecallText(%q, %q) = %q; want %q", tc.key, tc.value, got, tc.want) } }) } } // A correction left the superseded value in the index, so recall answered with // the value he had just corrected (#493). func TestCorrectValueDropsMemoryVectors(t *testing.T) { ctx := context.Background() s := newTestStore(t) now := time.Now() mem := s.VectorMemory() if _, err := s.WriteFact(ctx, now, KindSelf, "go_version", `"1.20"`, "tap:voice", 1.0, sql.NullInt64{}); err != nil { t.Fatalf("WriteFact: %v", err) } if err := mem.Insert(ctx, "fact:go_version:1", []float32{1, 0, 0}, map[string]string{ "type": "fact", "text": "go version — 1.20", }); err != nil { t.Fatalf("Insert: %v", err) } if _, err := s.CorrectValue(ctx, "go_version", "feedback", "1.25", now.Add(time.Minute)); err != nil { t.Fatalf("CorrectValue: %v", err) } got, err := mem.ByPrefix(ctx, "fact:") if err != nil { t.Fatalf("ByPrefix: %v", err) } if len(got) != 0 { t.Fatalf("after the correction the index still holds %+v; the superseded value must not answer", got) } } // The recovery path a poisoned box had none of (#470 point 4, #493): rows // written before the fix hold utterances, voided junk and superseded values, // and no write-path change reaches any of them. func TestRepairFactVectors(t *testing.T) { ctx := context.Background() s := newTestStore(t) now := time.Now() mem := s.VectorMemory() embed := func(ctx context.Context, text string) ([]float32, error) { return []float32{float32(len(text)), 1, 0}, nil } // A live fact indexed under the question that wrote it — the defect. if _, err := s.WriteFact(ctx, now, KindSelf, "water", `"выпил"`, "tap:voice", 1.0, sql.NullInt64{}); err != nil { t.Fatalf("WriteFact water: %v", err) } if err := mem.Insert(ctx, "fact:water:100", []float32{9, 9, 9}, map[string]string{ "type": "fact", "source": "voice", "text": "запиши что я пил воду", }); err != nil { t.Fatalf("Insert water: %v", err) } // A voided fact whose vector survived the void. if _, err := s.WriteFact(ctx, now, KindSelf, "go_version", `"1.20"`, "tap:voice", 1.0, sql.NullInt64{}); err != nil { t.Fatalf("WriteFact go_version: %v", err) } if _, _, err := s.VoidLatestFact(ctx, "go_version", "feedback", now.Add(time.Minute)); err != nil { t.Fatalf("VoidLatestFact: %v", err) } if err := mem.Insert(ctx, "fact:go_version:100", []float32{9, 9, 9}, map[string]string{ "type": "fact", "text": "какая последняя версия языка Go?", }); err != nil { t.Fatalf("Insert go_version: %v", err) } // A key with two vectors: only the newest may answer. if _, err := s.WriteFact(ctx, now, KindSelf, "mood", `"устал"`, "tap:voice", 1.0, sql.NullInt64{}); err != nil { t.Fatalf("WriteFact mood: %v", err) } for _, ts := range []string{"100", "200"} { if err := mem.Insert(ctx, "fact:mood:"+ts, []float32{9, 9, 9}, map[string]string{ "type": "fact", "text": "мне грустно", }); err != nil { t.Fatalf("Insert mood %s: %v", ts, err) } } // A note must be left entirely alone. if err := mem.Insert(ctx, "note:7", []float32{5, 5, 5}, map[string]string{ "type": "note", "text": "сеть тормозит по вечерам", }); err != nil { t.Fatalf("Insert note: %v", err) } res, err := s.RepairFactVectors(ctx, embed) if err != nil { t.Fatalf("RepairFactVectors: %v", err) } if res.Rewritten != 2 || res.Dropped != 2 { t.Fatalf("repair reported %+v; want 2 rewritten (water, newest mood) and 2 dropped (voided go_version, superseded mood)", res) } got, err := mem.ByPrefix(ctx, "fact:") if err != nil { t.Fatalf("ByPrefix: %v", err) } texts := map[string]string{} for _, r := range got { texts[r.ID] = r.Meta["text"] } if len(texts) != 2 { t.Fatalf("the index holds %+v; want only fact:water:100 and fact:mood:200", texts) } if texts["fact:water:100"] != "water — выпил" { t.Fatalf("water reads back %q; want the fact, not the utterance", texts["fact:water:100"]) } if texts["fact:mood:200"] != "mood — устал" { t.Fatalf("mood reads back %q", texts["fact:mood:200"]) } // Provenance the row already carried must survive the rewrite. for _, r := range got { if r.ID == "fact:water:100" && r.Meta["source"] != "voice" { t.Fatalf("water lost its source meta: %+v", r.Meta) } } if notes, err := mem.ByPrefix(ctx, "note:"); err != nil || len(notes) != 1 { t.Fatalf("the note row was touched: %+v (err %v)", notes, err) } // Marker written, so a second run is free and changes nothing. again, err := s.RepairFactVectors(ctx, embed) if err != nil { t.Fatalf("second RepairFactVectors: %v", err) } if !again.Skipped { t.Fatalf("second run did work: %+v; the marker must make it a no-op", again) } }