package graymatter import ( "context" "encoding/json" "errors" "fmt" "net/http" "net/http/httptest" "testing" "time" "github.com/angelnicolasc/graymatter/pkg/memory" ) func TestMemoryConfidenceOptionsAndNamespaces(t *testing.T) { m, err := NewWithConfig(Config{DataDir: t.TempDir(), EmbeddingMode: EmbeddingKeyword, TopK: 1, CandidateRetrieval: true}) if err != nil { t.Fatal(err) } defer m.Close() ctx := context.Background() verified, unverified := "verified", "unverified" a, err := m.RememberWithOptions(ctx, "a", "deployment approved", memory.WriteOptions{Confidence: &verified}) if err != nil { t.Fatal(err) } if _, err := m.RememberWithOptions(ctx, "a", "deployment rumored", memory.WriteOptions{Confidence: &unverified}); err != nil { t.Fatal(err) } s, err := m.RememberSharedWithOptions(ctx, "deployment global policy", memory.WriteOptions{Confidence: &verified}) if err != nil { t.Fatal(err) } if a.ID == s.ID || a.Confidence != verified || s.AgentID != memory.SharedAgentID { t.Fatalf("invalid receipts: %+v / %+v", a, s) } zero := 0.0 opts := memory.RecallOptions{MinConfidence: &verified, ConfidenceWeight: &zero} r, err := m.RecallWithOptions(ctx, "a", "deployment", opts) if err != nil || len(r.Facts) != 1 || r.Facts[0] != a.Text || r.Retrieval == nil { t.Fatalf("agent options: %+v %v", r, err) } e, err := m.RecallExplainWithOptions(ctx, "a", "deployment", opts) if err != nil || len(e.Receipts) != 1 || e.Receipts[0].Provenance.FactID != a.ID || e.Retrieval == nil { t.Fatalf("explain options: %+v %v", e, err) } r, err = m.RecallSharedWithOptions(ctx, "deployment", opts) if err != nil || len(r.Facts) != 1 || r.Facts[0] != s.Text { t.Fatalf("shared options: %+v %v", r, err) } r, err = m.RecallAllWithOptions(ctx, "a", "deployment", opts) if err != nil || len(r.Facts) != 1 || r.Retrieval == nil { t.Fatalf("all options: %+v %v", r, err) } bad := " verified" if _, err := m.RememberWithOptions(ctx, "a", "invalid", memory.WriteOptions{Confidence: &bad}); err == nil { t.Fatal("invalid write accepted") } facts, _ := m.Advanced().List("a") if len(facts) != 2 { t.Fatal("invalid write had effects") } } func confidenceAnthropicMessage(text string) string { content, _ := json.Marshal([]map[string]string{{"type": "text", "text": text}}) return fmt.Sprintf(`{"id":"msg_test","type":"message","role":"assistant","model":"claude-test","content":%s,"stop_reason":"end_turn","usage":{"input_tokens":1,"output_tokens":1}}`, content) } func TestMemoryRememberExtractedConfidenceFollowsProvenance(t *testing.T) { for _, tc := range []struct{ name, key, response, want string }{ {"offline", "", "", ""}, {"generated identical text", "test-key", `["source"]`, "unverified"}, {"malformed model fallback", "test-key", `not JSON`, ""}, } { t.Run(tc.name, func(t *testing.T) { if tc.key != "" { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.Header().Set("Content-Type", "application/json") fmt.Fprint(w, confidenceAnthropicMessage(tc.response)) })) defer srv.Close() t.Setenv("ANTHROPIC_BASE_URL", srv.URL) } m, err := NewWithConfig(Config{DataDir: t.TempDir(), EmbeddingMode: EmbeddingKeyword, AnthropicAPIKey: tc.key, ConsolidateModel: "claude-test"}) if err != nil { t.Fatal(err) } defer m.Close() if err := m.RememberExtracted(context.Background(), "a", "source"); err != nil { t.Fatal(err) } facts, err := m.Advanced().List("a") if err != nil || len(facts) != 1 || facts[0].Text != "source" || facts[0].Confidence != tc.want { t.Fatalf("stored extraction: %+v %v", facts, err) } }) } } func TestMemoryRememberWithOptionsConsolidatesAfterMetadataCommit(t *testing.T) { reported := make(chan error, 2) requests := make(chan struct{}, 1) srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { requests <- struct{}{} w.Header().Set("Content-Type", "application/json") fmt.Fprint(w, confidenceAnthropicMessage("")) })) defer srv.Close() t.Setenv("ANTHROPIC_BASE_URL", srv.URL) m, err := NewWithConfig(Config{DataDir: t.TempDir(), EmbeddingMode: EmbeddingKeyword, AsyncConsolidate: true, ConsolidateThreshold: 2, ConsolidateLLM: "anthropic", AnthropicAPIKey: "test-key", ConsolidateModel: "claude-test", OnConsolidateError: func(_ string, err error) { reported <- err }, }) if err != nil { t.Fatal(err) } defer m.Close() for _, text := range []string{"source one", "source two"} { if err := m.Advanced().Put(context.Background(), "a", text); err != nil { t.Fatal(err) } } label := "unverified" f, err := m.RememberWithOptions(context.Background(), "a", "final atomic label", memory.WriteOptions{Confidence: &label}) if err != nil { t.Fatal(err) } select { case err := <-reported: if !errors.Is(err, memory.ErrInvalidProposal) { t.Fatalf("unexpected consolidation error: %v", err) } case <-time.After(5 * time.Second): t.Fatal("new high-level write failed to trigger consolidation") } select { case <-requests: default: t.Fatal("consolidation never reached configured model") } facts, _ := m.Advanced().List("a") for _, stored := range facts { if stored.ID == f.ID { if stored.Confidence != label { t.Fatalf("consolidation restored transient confidence: %+v", stored) } return } } t.Fatal("committed confidence write disappeared") } func TestMemoryConfidenceOptionsRejectNoOpReceipt(t *testing.T) { m := New("invalid\x00path") if _, err := m.RememberWithOptions(context.Background(), "a", "source", memory.WriteOptions{}); err == nil { t.Fatal("unavailable store fabricated durable write") } if _, err := m.RecallWithOptions(context.Background(), "a", "source", memory.RecallOptions{}); err == nil { t.Fatal("unavailable store fabricated policy result") } }