Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions supabase/applied-migration-hashes.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"note": "sha256 of every migration this repository has shipped, with newlines normalised. An entry whose hash changes means an applied migration was edited, which cannot reach the live database. See scripts/check-migration-immutability.mjs.",
"generator": "scripts/check-migration-immutability.mjs",
"sealed_at": "2026-11-06T22:37:39.020Z",
"sealed_at": "2026-11-07T00:50:50.537Z",
"versions": {
"20260527000000_bulk_ingestion.sql": "813e8e314064a6dcbaaa55e2765a8ce901d5783b304b4a7af7bb7417a07daf7c",
"20260527001000_documents_policy_cleanup.sql": "559c26f3172bf75e545a41adef75538b2bf648ea7d53c8eb3d3ea7c244c65a48",
Expand Down Expand Up @@ -359,6 +359,7 @@
"20261104000000_managed_full_pdf_summary_projection.sql": "568613b9b33116bfe2ce6eff84e2eddb30d05a29b240c46305e51394c8d1e9c7",
"20261105000000_managed_reviewed_rendered_pdf.sql": "db78a109c166c2bce3f60b7cfacd945800a9556898b17035d46481ade347b0f5",
"20261106184724_defer_readmission_ranking_authority.sql": "8d5f352c2f9a9212090e03fd69beff0133f32a4ce75d2dc0274fbe41a5efa2ee",
"20261106223559_full_pdf_packet_parent_lookups.sql": "b1adaefb172f6e857f5dfaae7c432e7f004555f6395b545f7c85837b5bba4eb7"
"20261106223559_full_pdf_packet_parent_lookups.sql": "b1adaefb172f6e857f5dfaae7c432e7f004555f6395b545f7c85837b5bba4eb7",
"20261107001000_pending_readmission_provider_authority.sql": "03bc83824e73b6100274202073dbf90d08eee076e8a2edf86f786ac343b8b786"
}
}
8 changes: 4 additions & 4 deletions supabase/drift-manifest.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"generated_at": "2026-11-05T02:55:57.204Z",
"generated_at": "2026-11-07T00:21:10.881Z",
"generator": "scripts/generate-drift-manifest.ts",
"postgres_image": "supabase/postgres:17.6.1.127@sha256:be60aee15997daca475b710b734bc6bfe52cd544dcd7e9fd2ff58210b6747d83",
"schema_sha256": "f36f7fac5757e214d4cbc2dd86388ff80e25ec1ccb30533070c0e98b61d75ac2",
"replay_seconds": 22,
"schema_sha256": "02a9c65cb1aac72fee3847320103940d073f0f9945e93ca39b0b5319fbe011da",
"replay_seconds": 32,
"snapshot": {
"views": [
{
Expand Down Expand Up @@ -20476,7 +20476,7 @@
"postgres=X/postgres",
"service_role=X/postgres"
],
"def_hash": "21536e668b04513cb0dd89fcff792df9",
"def_hash": "b2706874c70aa052aee195b932f8b57a",
"signature": "public.read_ingestion_generation_inventory(jsonb,uuid)"
},
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
-- DRAFT: provider facts observe a readmission's declared pending authority while
-- retrieval continues to use the old flat ranking metadata until completion.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
-- Validate the pending admission binding and unchanged live snapshot first.
-- Existing event/source/lease/permission checks and ACLs remain unchanged.
begin;
set local lock_timeout='5s';
set local statement_timeout='120s';

do $$
declare signature regprocedure:='public.read_ingestion_generation_inventory(jsonb,uuid)'::regprocedure;
definition text;needle text;replacement text;
begin
definition:=pg_get_functiondef(signature);
needle:=$old$ for pa_pair in select * from (values
('source_role','sourceRole'),('authority_basis','authorityBasis'),('licence_policy','licencePolicy'),$old$;
replacement:=$new$ -- The immutable event above binds the declaration to this document/source.
-- A changed live snapshot (including withdrawn rights) must never be hidden
-- by overlaying pending values. Missing/foreign/malformed blocks fail closed.
if pa_metadata ? 'pending_reindex_authority' then
if not ingestion_generation_evidence.pending_reindex_authority_valid(pa_metadata)
or exists(select 1 from jsonb_each(pa_metadata#>'{pending_reindex_authority,prior}') prior
where coalesce(pa_metadata->prior.key,'null'::jsonb) is distinct from prior.value)
then raise exception 'provider_authority_admission_mismatch';end if;
pa_metadata:=pa_metadata||(pa_metadata#>'{pending_reindex_authority,values}');
end if;
for pa_pair in select * from (values
('source_role','sourceRole'),('authority_basis','authorityBasis'),('licence_policy','licencePolicy'),$new$;
if length(definition)-length(replace(definition,needle,''))<>length(needle)
or strpos(definition,'pending_reindex_authority')<>0
or not exists(select 1 from pg_proc where oid=signature and proowner='postgres'::regrole)
then raise exception 'pending_provider_authority_preimage';end if;
execute replace(definition,needle,replacement);
end $$;
commit;
36 changes: 36 additions & 0 deletions supabase/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -41875,3 +41875,39 @@ end $$;

-- Patched validators retain their postgres ownership and existing ACLs.
commit;

-- Source migration: 20261107001000_pending_readmission_provider_authority.sql
-- DRAFT: provider facts observe a readmission's declared pending authority while
-- retrieval continues to use the old flat ranking metadata until completion.
-- Validate the pending admission binding and unchanged live snapshot first.
-- Existing event/source/lease/permission checks and ACLs remain unchanged.
begin;
set local lock_timeout='5s';
set local statement_timeout='120s';

do $$
declare signature regprocedure:='public.read_ingestion_generation_inventory(jsonb,uuid)'::regprocedure;
definition text;needle text;replacement text;
begin
definition:=pg_get_functiondef(signature);
needle:=$old$ for pa_pair in select * from (values
('source_role','sourceRole'),('authority_basis','authorityBasis'),('licence_policy','licencePolicy'),$old$;
replacement:=$new$ -- The immutable event above binds the declaration to this document/source.
-- A changed live snapshot (including withdrawn rights) must never be hidden
-- by overlaying pending values. Missing/foreign/malformed blocks fail closed.
if pa_metadata ? 'pending_reindex_authority' then
if not ingestion_generation_evidence.pending_reindex_authority_valid(pa_metadata)
or exists(select 1 from jsonb_each(pa_metadata#>'{pending_reindex_authority,prior}') prior
where coalesce(pa_metadata->prior.key,'null'::jsonb) is distinct from prior.value)
then raise exception 'provider_authority_admission_mismatch';end if;
pa_metadata:=pa_metadata||(pa_metadata#>'{pending_reindex_authority,values}');
end if;
for pa_pair in select * from (values
('source_role','sourceRole'),('authority_basis','authorityBasis'),('licence_policy','licencePolicy'),$new$;
if length(definition)-length(replace(definition,needle,''))<>length(needle)
or strpos(definition,'pending_reindex_authority')<>0
or not exists(select 1 from pg_proc where oid=signature and proowner='postgres'::regrole)
then raise exception 'pending_provider_authority_preimage';end if;
execute replace(definition,needle,replacement);
end $$;
commit;
11 changes: 11 additions & 0 deletions tests/pending-reindex-authority.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,17 @@ function functionBody(sql: string, header: string) {
}

describe("deferred readmission ranking authority migration", () => {
it("keeps provider facts pending-aware without changing leases, permissions, completions or readmission", () => {
const name = "20261107001000_pending_readmission_provider_authority.sql";
const forward = read(`supabase/migrations/${name}`);
expect(forward).toContain("public.read_ingestion_generation_inventory(jsonb,uuid)");
expect(forward).toContain("pending_reindex_authority_valid(pa_metadata)");
expect(forward).toContain("coalesce(pa_metadata->prior.key,'null'::jsonb) is distinct from prior.value");
expect(forward).toContain("pa_metadata:=pa_metadata||(pa_metadata#>'{pending_reindex_authority,values}')");
expect(forward).toContain("pending_provider_authority_preimage");
expect(forward).not.toMatch(/\b(?:grant|revoke|create function|update public|insert into)\b/i);
expect(read("supabase/schema.sql")).toContain(`-- Source migration: ${name}\n${forward}`);
});
it("changes the authority digest only by reading pending values for the three flat keys it already bound", () => {
const header = "create or replace function public.lock_ingestion_document_authority(";
const previous = functionBody(
Expand Down
54 changes: 52 additions & 2 deletions tests/sql/pending-reindex-authority.sql
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ insert into public.documents(id,owner_id,title,file_name,file_type,file_size,sto
'{"corpus_scope":"uploaded_local","admission_event_id":"7a000000-0000-4000-8000-000000000020","admission_policy_version":"trusted-document-admission-v1","provider_processing":"permitted","authority":{"providerProcessing":"permitted","licencePolicy":"review_required","sourceRole":"clinical_guideline","rightsEvidenceRef":"synthetic"},"licence_policy":"review_required","source_role":"clinical_guideline","rights_evidence_ref":"synthetic"}'::jsonb),
('7a000000-0000-4000-8000-000000000003','7a000000-0000-4000-8000-000000000009','Synthetic readmission','readmit.pdf','application/pdf',10,
'7a000000-0000-4000-8000-000000000009/readmit.pdf',repeat('c',64),'indexed',
'{"corpus_scope":"uploaded_local","content_mode":"indexed_content","licence_policy":"review_required","source_role":"clinical_reference","publisher":"Superseded synthetic publisher","document_status":"outdated"}'::jsonb);
'{"corpus_scope":"uploaded_local","content_mode":"indexed_content","licence_policy":"review_required","source_role":"clinical_reference","publisher":"Superseded synthetic publisher","document_status":"outdated","content_hash":"cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc","confidentiality_scope":"guidelines-only"}'::jsonb);
set local session_replication_role=origin;

-- 1. Without a pending block the digest input is byte-identical to the previous formula.
Expand Down Expand Up @@ -69,9 +69,53 @@ begin
or not ingestion_generation_evidence.pending_reindex_authority_valid(after) then raise exception 'TEST_PENDING_BLOCK_WRONG %',pending;end if;
end $$;

-- Composed boundary: real readmission -> current leased attempt -> provider facts.
-- No provider, embedding or real source is used. Before the forward fix this
-- raises provider_authority_admission_mismatch on the deferred publisher/role.
do $$
declare doc uuid:='7a000000-0000-4000-8000-000000000003';j public.ingestion_jobs;
generation uuid:='7a000000-0000-4000-8000-000000000041';fence jsonb;facts jsonb;m jsonb;bad jsonb;key text;
begin
select * into strict j from public.ingestion_jobs where document_id=doc;
update public.ingestion_jobs set status='processing',locked_by='synthetic-provider-facts',locked_at=clock_timestamp(),lease_duration_seconds=600,
lease_token='7a000000-0000-4000-8000-000000000040',lease_expires_at=clock_timestamp()+interval '10 minutes',
lease_expected_generation_id=null,lease_write_generation_id=generation,
lease_authority_digest=public.lock_ingestion_document_authority(doc,false)->>'authority_digest'
where id=j.id returning * into j;
fence:=jsonb_build_object('version','ingestion-provider-authority-read-v1','fence',jsonb_build_object(
'job_kind','node','job_id',j.id,'document_id',doc,'worker_id',j.locked_by,'lease_token',j.lease_token,
'expected_generation_id',null,'authority_digest',j.lease_authority_digest));
facts:=public.read_ingestion_generation_inventory(fence,generation);
if facts#>>'{providerAuthority,declaredAuthority,publisher}' is distinct from 'Synthetic publisher'
or facts#>>'{providerAuthority,declaredAuthority,sourceRole}' is distinct from 'clinical_guideline'
or facts#>>'{providerAuthority,declaredAuthority,currentness}' is distinct from 'current'
then raise exception 'TEST_PENDING_PROVIDER_FACTS_MISSING';end if;
select metadata into m from public.documents where id=doc;
if m->>'publisher' is distinct from 'Superseded synthetic publisher' or m->>'document_status' is distinct from 'outdated'
then raise exception 'TEST_PROVIDER_FACTS_CHANGED_LIVE_RANKING';end if;
-- These mutations are transaction-local synthetic data only. Recompute the
-- fence where needed so rejection is proved at the pending/provider boundary.
foreach key in array array['foreign','malformed','values','withdrawal','stale'] loop
bad:=case key
when 'foreign' then jsonb_set(m,'{pending_reindex_authority,admissionEventId}','"7a000000-0000-4000-8000-000000000099"')
when 'malformed' then jsonb_set(m,'{pending_reindex_authority,version}','"unknown"')
when 'values' then jsonb_set(m,'{pending_reindex_authority,values,publisher}','"Unbound synthetic publisher"')
when 'withdrawal' then m||'{"licence_policy":"index_forbidden"}'::jsonb
else m||'{"publisher":"Changed live publisher"}'::jsonb end;
update public.documents set metadata=bad where id=doc;
begin
perform public.read_ingestion_generation_inventory(fence,generation);
raise exception 'TEST_BAD_PROVIDER_PENDING_ACCEPTED %',key;
exception when raise_exception then
if sqlerrm not in ('provider_authority_admission_mismatch','provider_authority_current_attempt_required') then raise;end if;
end;
end loop;
update public.documents set metadata=m where id=doc;
end $$;

-- 3. Applying is digest-neutral, writes the declared values, clears the block and is idempotent.
do $$
declare doc uuid:='7a000000-0000-4000-8000-000000000003';before text;after text;m jsonb;
declare doc uuid:='7a000000-0000-4000-8000-000000000003';before text;after text;m jsonb;j public.ingestion_jobs;facts jsonb;
begin
before:=(public.lock_ingestion_document_authority(doc,false))->>'authority_digest';
if before is null then raise exception 'TEST_PENDING_DOCUMENT_NOT_LOCKABLE';end if;
Expand All @@ -82,6 +126,12 @@ begin
or m->'review_date' is distinct from 'null'::jsonb then raise exception 'TEST_PENDING_NOT_APPLIED %',m;end if;
after:=(public.lock_ingestion_document_authority(doc,false))->>'authority_digest';
if after is distinct from before then raise exception 'TEST_APPLY_MOVED_DIGEST';end if;
select * into strict j from public.ingestion_jobs where document_id=doc;
facts:=public.read_ingestion_generation_inventory(jsonb_build_object('version','ingestion-provider-authority-read-v1','fence',
jsonb_build_object('job_kind','node','job_id',j.id,'document_id',doc,'worker_id',j.locked_by,'lease_token',j.lease_token,
'expected_generation_id',null,'authority_digest',j.lease_authority_digest)),j.lease_write_generation_id);
if facts#>>'{providerAuthority,declaredAuthority,publisher}' is distinct from 'Synthetic publisher'
then raise exception 'TEST_APPLIED_NONPENDING_PROVIDER_FACTS_CHANGED';end if;
perform ingestion_generation_evidence.apply_pending_reindex_authority(doc);
if (select metadata from public.documents where id=doc) is distinct from m then raise exception 'TEST_APPLY_NOT_IDEMPOTENT';end if;
end $$;
Expand Down
Loading