//go:build integration // Snippet on the hit, exercised against a REAL Postgres with pgvector, full text, and RLS in force. It // proves the added ts_headline snippet expression on the hybrid retrieval query: a candidate seeded with a // distinctive headline term, retrieved under RLS with that term as the text query, comes back with a non // empty Snippet that contains the matched term, so the faithfulness gate and the narration can cite WHY the // candidate matched, the matched TEXT, not only the name. It also proves the degrade contract: a pure // semantic query (an empty text query, so an empty tsquery) still returns hits through the vector arm with // an empty, no highlight snippet and no error. The snippet is built over the SAME text the search_tsv is // built from and bound to the SAME tsquery the full text leg already uses, so it never diverges from the // query that produced the match. This tests the query directly via dbgen with a deterministic embedding, so // it needs no inference client, exactly like the sibling hybrid search tests. package integration import ( "context" "strings" "testing" "github.com/google/uuid" "github.com/jackc/pgx/v5" "github.com/pgvector/pgvector-go" "github.com/missionworkforce/missionmatch/internal/platform/database/dbgen" "github.com/missionworkforce/missionmatch/internal/platform/embeddings" ) func TestHybridSearchSnippetOnHit(t *testing.T) { orgA := seedOrg(t) emb := embeddings.NewDeterministic(384) // A distinctive headline term that the snippet must surface. It lives in the headline, one of the three // fields the search_tsv (and the matching ts_headline source text) is built from in migration 0002. const term = "cryptanalysis" seed := func(name, headline string) uuid.UUID { vec, _ := emb.Embed(context.Background(), name+" "+headline) pv := pgvector.NewVector(vec) hn := headline c, err := dbgen.New(migrator).CreateCandidate(context.Background(), dbgen.CreateCandidateParams{ OrganizationID: orgA, FullName: name, Headline: &hn, ClearanceLevel: "top_secret", ClearancePoly: "none", ClearanceStatus: "active", Available: true, ResumeEmbedding: &pv, }) if err != nil { t.Fatalf("seed candidate %q: %v", name, err) } return c.ID } wantID := seed("Alice Analyst", "Senior cryptanalysis lead for SIGINT collection") seed("Bob Builder", "Kubernetes and AWS platform engineer") // A text matching query: the FTS leg matches Alice on the term, so her snippet must surface it. var rows []dbgen.SearchCandidatesHybridRow qv := pgvector.NewVector(mustEmbed(emb, term)) err := appDB.RunInTenantTx(ctxOrg(orgA), func(ctx context.Context, tx pgx.Tx) error { if _, e := tx.Exec(ctx, "SET LOCAL hnsw.ef_search = 200"); e != nil { return e } var e error rows, e = dbgen.New(tx).SearchCandidatesHybrid(ctx, dbgen.SearchCandidatesHybridParams{ Column1: qv, Limit: 10, WebsearchToTsquery: term, }) return e }) if err != nil { t.Fatalf("hybrid search with snippet failed: %v", err) } var found *dbgen.SearchCandidatesHybridRow for i := range rows { if rows[i].ID == wantID { found = &rows[i] } if rows[i].OrganizationID != orgA { t.Fatalf("hybrid search leaked a foreign tenant row: %s", rows[i].OrganizationID) } } if found == nil { t.Fatalf("the term matching candidate %s was not retrieved for query %q", wantID, term) } if strings.TrimSpace(found.Snippet) == "" { t.Fatalf("the text matching hit has an empty snippet; the matched span was not attached") } if !strings.Contains(strings.ToLower(found.Snippet), term) { t.Fatalf("the snippet %q does not contain the matched term %q", found.Snippet, term) } // The degrade contract: a pure semantic query (an empty text query yields an empty tsquery) must still // return hits through the vector arm, with an empty, no highlight snippet, and no error. ts_headline on an // empty tsquery returns an unhighlighted head, never an error, so an empty snippet is a valid result. var semRows []dbgen.SearchCandidatesHybridRow err = appDB.RunInTenantTx(ctxOrg(orgA), func(ctx context.Context, tx pgx.Tx) error { if _, e := tx.Exec(ctx, "SET LOCAL hnsw.ef_search = 200"); e != nil { return e } var e error semRows, e = dbgen.New(tx).SearchCandidatesHybrid(ctx, dbgen.SearchCandidatesHybridParams{ Column1: qv, Limit: 10, WebsearchToTsquery: "", }) return e }) if err != nil { t.Fatalf("pure semantic (empty query) hybrid search must not error, got: %v", err) } if len(semRows) == 0 { t.Fatal("pure semantic search returned no rows; the vector arm should still recall the seeded candidates") } for _, r := range semRows { if r.OrganizationID != orgA { t.Fatalf("semantic search leaked a foreign tenant row: %s", r.OrganizationID) } if strings.TrimSpace(r.Snippet) != "" { t.Fatalf("a pure semantic hit must carry an empty snippet (no tsquery to highlight), got %q for %s", r.Snippet, r.FullName) } } }