# Migrazione server: filtro `kinds` nella RPC `search_similar` **Contesto.** Il vectorstore ThothII espone la similarity search via PostgREST/Supabase: `POST /rpc/search_similar` con payload `{query_embedding, limit_count, table_name}`. Da quando `memory` e `solved_question` condividono la tabella `vectors.memory` (feature "active memory", 2026-07-07), il top-k della tabella mista diluisce i risultati: il filtro per kind avviene client-side DOPO il taglio a `limit_count`. Serve il filtro nel `WHERE` della funzione SQL. **Lato client: già pronto.** Il harness manda `kinds` nel payload quando filtra (`tht memory search`, `tht memory solved-search`); se il server risponde 404 (funzione a 3 argomenti, pre-migrazione) ritenta senza `kinds` e filtra client-side. Quindi **l'ordine di deploy è libero** — ma finché la migrazione non è fatta il filtro resta client-side e la diluizione persiste. ## Cosa fare (istruzioni per il Claude del server) Obiettivo: aggiungere a `search_similar` un quarto parametro `kinds text[] DEFAULT NULL` che filtra `kind = ANY(kinds)` quando valorizzato, preservando il comportamento attuale quando assente. 1. **Recupera la definizione corrente** (non riscriverla a memoria): ```sql SELECT pg_get_functiondef(oid) FROM pg_proc WHERE proname = 'search_similar'; ``` Prendi nota di: schema della funzione, `LANGUAGE`, `SECURITY DEFINER` e `SET search_path` se presenti, shape del risultato (le righe devono restare `{id, similarity, metadata}`), e come viene usato `table_name` (quasi certamente SQL dinamico con `EXECUTE format(...)`). 2. **Sostituisci la funzione in una sola transazione.** In Postgres non si può aggiungere un parametro con ALTER: `CREATE OR REPLACE` con firma diversa creerebbe un **overload**, e due overload con lo stesso nome mandano PostgREST in ambiguità (PGRST203) per le chiamate senza `kinds`. Quindi: ```sql BEGIN; DROP FUNCTION search_similar(vector, integer, text); -- adatta la firma esatta trovata al punto 1 CREATE FUNCTION search_similar( query_embedding vector, limit_count integer, table_name text, kinds text[] DEFAULT NULL ) RETURNS ... -- stessa shape di prima ... COMMIT; ``` Nel corpo, la condizione deve essere **null-safe** così i chiamanti legacy (senza `kinds`) mantengono il comportamento attuale: ```sql WHERE (kinds IS NULL OR t.kind = ANY(kinds)) ``` Se il corpo usa SQL dinamico, passa `kinds` come parametro (`USING`), non interpolarlo: ```sql EXECUTE format( 'SELECT metadata, 1 - (embedding <=> $1) AS similarity FROM vectors.%I WHERE ($3::text[] IS NULL OR kind = ANY($3)) ORDER BY embedding <=> $1 LIMIT $2', table_name) USING query_embedding, limit_count, kinds; ``` (Adatta al corpo reale: l'esempio mostra solo dove inserire la condizione.) 3. **Ripristina i GRANT.** Il DROP cancella i grant della funzione: ri-esegui gli `GRANT EXECUTE` che la definizione originale aveva (ruolo reader della REST, es. `vector_reader`, e il ruolo con cui PostgREST esegue le RPC). Verifica con: ```sql SELECT proacl FROM pg_proc WHERE proname = 'search_similar'; ``` 4. **Ricarica lo schema cache di PostgREST** — senza questo la nuova firma risponde 404: ```sql NOTIFY pgrst, 'reload schema'; ``` (oppure riavvia il servizio PostgREST). 5. **Verifica indice.** La colonna `kind` dovrebbe già avere un indice (`_kind_idx`); se manca sulla tabella `memory`: ```sql CREATE INDEX IF NOT EXISTS vectors_memory_kind_idx ON vectors.memory (kind); ``` 6. **Test end-to-end** (con la API key reader): ```bash # senza kinds (comportamento legacy) — deve rispondere come prima curl -s -X POST "/rpc/search_similar" -H "X-API-Key: $KEY" \ -H "Content-Type: application/json" \ -d '{"query_embedding": [...], "limit_count": 3, "table_name": "memory"}' # con kinds — deve restituire SOLO righe con metadata.kind = "solved_question" curl -s -X POST "/rpc/search_similar" -H "X-API-Key: $KEY" \ -H "Content-Type: application/json" \ -d '{"query_embedding": [...], "limit_count": 3, "table_name": "memory", "kinds": ["solved_question"]}' ``` Poi dal workstation: `tht memory solved-search "" --json` e `tht memory search "" --json` devono continuare a funzionare (il secondo ora senza righe solved nei top-k). ## Funzioni di scrittura: `existing_vector_hashes` e `upsert_vector_records` Entrambe hanno una validazione interna che ammette solo i kind storici per ogni tabella. Per la tabella `memory`, il set ammesso va ampliato a `{"memory", "solved_question"}`. **Come trovare la validazione.** Recupera la definizione di ciascuna funzione: ```sql SELECT pg_get_functiondef(oid) FROM pg_proc WHERE proname = 'existing_vector_hashes'; SELECT pg_get_functiondef(oid) FROM pg_proc WHERE proname = 'upsert_vector_records'; ``` Cerca la riga che controlla i kind ammessi (es. `IF kind NOT IN ('memory') THEN RAISE`, oppure un array literal `ARRAY['memory']`, oppure una lookup su una tabella di configurazione). La modifica è semplicemente aggiungere `'solved_question'` alla lista ammessa per `memory`. **Esempio di pattern tipico:** ```sql -- PRIMA: IF NOT (kind = ANY(ARRAY['memory'])) THEN RAISE EXCEPTION 'Invalid kind for %', table_name; END IF; -- DOPO: IF NOT (kind = ANY(ARRAY['memory', 'solved_question'])) THEN RAISE EXCEPTION 'Invalid kind for %', table_name; END IF; ``` Applica la stessa tecnica DROP+CREATE in transazione già usata per `search_similar` (mai `CREATE OR REPLACE` con firma diversa). Ripristina i GRANT dopo il DROP. **Test delle funzioni di scrittura** (con la API key writer): ```bash # existing_vector_hashes — deve accettare kinds = ['solved_question'] curl -s -X POST "/rpc/existing_vector_hashes" -H "X-API-Key: $WRITE_KEY" \ -H "Content-Type: application/json" \ -d '{"table_name": "memory", "kinds": ["solved_question"]}' # atteso: 200 con array (vuoto o con hash) # upsert_vector_records — deve accettare kind = 'solved_question' curl -s -X POST "/rpc/upsert_vector_records" -H "X-API-Key: $WRITE_KEY" \ -H "Content-Type: application/json" \ -d '{"table_name": "memory", "rows": [{"record_key": "solved:__test__", "kind": "solved_question", "content_hash": "test", "metadata": {"id": "solved:__test__", "kind": "solved_question"}, "embedding": [0.0]}]}' # atteso: 200 (upsert riuscito); l'embedding dummy verrà sovrascritto dal test vero ``` Dopo aver applicato le modifiche e verificato con curl, ritesta dal workstation: ```bash tht memory solved-index -c workspaces/psd.yaml tht memory solved-search -c workspaces/psd.yaml "domanda di test" --json ```