feat(harness): port vectorstore dual-key + reader RPC (D11, §5.4)
Ports vectorstore/{rest_client,rest_writer,store,reader,embeddings,records},
evidence/model (leaf dep of records), and cli/_guards (require_vector_write_allowed
workstation write-guard). Renamed psdwp3->nsp, verbatim.
VectorRestClient gains an api_key property so reader/writer clients carry their
distinct keys visibly (spec D11: vector_reader / vector_writer on the same endpoint).
scripts/create_vector_reader_rpc.sql is NEW: the reader RPCs (search_similar,
list_tables) lived server-side in Supabase and were never versioned. Authored now
mirroring the writer allowlist pattern (table allowlist, security definer, revoke
from anon/authenticated, grant to vector_reader only). Writer RPC ported verbatim.
L1: test_vector_dual_key (7 tests) pins the dual-key construction + the workstation
write-guard (exit 4 without writer key).
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
-- RPC reader per pgvector via Supabase/PostgREST (spec D11, §5.4).
|
||||
-- Eseguire sul database Supabase centrale come owner dello schema `vectors`.
|
||||
-- Prerequisiti:
|
||||
-- - schema `vectors` con tabelle schema_records/evidence/memory
|
||||
-- - colonne: record_key text unique, kind text, content_hash text, metadata jsonb,
|
||||
-- embedding vector(N), indexed_at timestamptz
|
||||
-- - ruolo/key PostgREST dedicato `vector_reader` (lettura: search_similar, list_tables)
|
||||
--
|
||||
-- A differenza del writer RPC (che era gia' nel repo), i reader RPC vivevano lato server
|
||||
-- in Supabase e non erano mai stati versionati. Li si riporta qui per completezza del
|
||||
-- modello dual-key: la key reader riceve solo EXECUTE su queste due funzioni, niente
|
||||
-- SELECT raw sulle tabelle. Il cleanup/rebuild resta solo-server (vedi writer RPC).
|
||||
|
||||
create or replace function public._assert_vector_read_table(table_name text)
|
||||
returns void
|
||||
language plpgsql
|
||||
stable
|
||||
as $$
|
||||
begin
|
||||
if table_name not in ('schema_records', 'evidence', 'memory') then
|
||||
raise sqlstate 'PGRST' using
|
||||
message = json_build_object('message', 'Unsupported vector table')::text,
|
||||
detail = json_build_object('status', 400)::text;
|
||||
end if;
|
||||
end;
|
||||
$$;
|
||||
|
||||
create or replace function public.search_similar(
|
||||
table_name text,
|
||||
query_embedding vector,
|
||||
limit_count integer
|
||||
)
|
||||
returns table(id bigint, similarity real, metadata jsonb)
|
||||
language plpgsql
|
||||
security definer
|
||||
set search_path = public, vectors, extensions
|
||||
as $$
|
||||
begin
|
||||
perform public._assert_vector_read_table(table_name);
|
||||
return query execute format(
|
||||
'select t.id,
|
||||
(1 - (t.embedding <=> $1))::real as similarity,
|
||||
t.metadata
|
||||
from vectors.%I t
|
||||
order by t.embedding <=> $1
|
||||
limit $2',
|
||||
table_name
|
||||
) using query_embedding, limit_count;
|
||||
end;
|
||||
$$;
|
||||
|
||||
create or replace function public.list_tables()
|
||||
returns table(table_name text, vector_dimensions integer, record_count bigint)
|
||||
language plpgsql
|
||||
security definer
|
||||
set search_path = public, vectors, extensions
|
||||
as $$
|
||||
begin
|
||||
return query
|
||||
select c.relname::text as table_name,
|
||||
-- dimensione ricavata dalla colonna embedding (vector(N)) se presente
|
||||
coalesce(
|
||||
(select (regexp_matches(format_type(a.atttypid, a.atttypmod), 'vector\((\d+)\)'))[1])::int,
|
||||
0
|
||||
) as vector_dimensions,
|
||||
c.reltuples::bigint as record_count
|
||||
from pg_class c
|
||||
join pg_namespace n on n.oid = c.relnamespace
|
||||
left join pg_attribute a on a.attrelid = c.oid and a.attname = 'embedding'
|
||||
where n.nspname = 'vectors'
|
||||
and c.relkind in ('r', 'p')
|
||||
and c.relname in ('schema_records', 'evidence', 'memory')
|
||||
order by c.relname;
|
||||
end;
|
||||
$$;
|
||||
|
||||
revoke all on function public._assert_vector_read_table(text) from public;
|
||||
revoke all on function public.search_similar(text, vector, integer) from public;
|
||||
revoke all on function public.list_tables() from public;
|
||||
|
||||
-- Revoca dai ruoli client generici, poi abilita solo il reader dedicato
|
||||
-- (e il writer, che usa existing_vector_hashes per l'upsert incrementale).
|
||||
do $$
|
||||
begin
|
||||
if exists (select 1 from pg_roles where rolname = 'anon') then
|
||||
revoke all on function public.search_similar(text, vector, integer) from anon;
|
||||
revoke all on function public.list_tables() from anon;
|
||||
end if;
|
||||
if exists (select 1 from pg_roles where rolname = 'authenticated') then
|
||||
revoke all on function public.search_similar(text, vector, integer) from authenticated;
|
||||
revoke all on function public.list_tables() from authenticated;
|
||||
end if;
|
||||
if exists (select 1 from pg_roles where rolname = 'vector_reader') then
|
||||
grant execute on function public.search_similar(text, vector, integer) to vector_reader;
|
||||
grant execute on function public.list_tables() to vector_reader;
|
||||
end if;
|
||||
end $$;
|
||||
|
||||
notify pgrst, 'reload schema';
|
||||
Reference in New Issue
Block a user