From 59a976be639e1dede8acec65c1c14166ca71cadb Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Mon, 13 Jul 2026 17:06:24 +0800 Subject: [PATCH] fix(db): reconcile deep-memory commit body --- ...713090500_reconcile_deep_memory_commit.sql | 394 ++++++++++++++++++ tests/supabase-schema.test.ts | 14 + 2 files changed, 408 insertions(+) create mode 100644 supabase/migrations/20260713090500_reconcile_deep_memory_commit.sql diff --git a/supabase/migrations/20260713090500_reconcile_deep_memory_commit.sql b/supabase/migrations/20260713090500_reconcile_deep_memory_commit.sql new file mode 100644 index 000000000..28d0ae8e1 --- /dev/null +++ b/supabase/migrations/20260713090500_reconcile_deep_memory_commit.sql @@ -0,0 +1,394 @@ +-- Forward-apply the canonical deep-memory commit body after the original +-- production migration version was recorded with an earlier implementation. +-- This changes only the function definition and its service-role-only ACL. + +create or replace function public.commit_document_deep_memory_generation( + p_document_id uuid, + p_producer text, + p_artifact_generation_id uuid, + p_rag_memory_version text, + p_document_intelligence_version text, + p_section_count integer, + p_memory_card_count integer, + p_index_unit_counts_by_type jsonb, + p_repaired_anchor_count integer default 0 +) +returns jsonb +language plpgsql +security definer +set search_path = public, extensions, pg_temp +as $$ +declare + v_document_metadata jsonb; + v_document_owner_id uuid; + v_committed_index_generation uuid; + v_total_section_count integer; + v_total_memory_card_count integer; + v_total_index_unit_count integer; + v_section_count integer; + v_memory_card_count integer; + v_index_unit_count integer; + v_expected_index_unit_count integer; + v_index_unit_counts_by_type jsonb; +begin + if nullif(btrim(p_producer), '') is null + or nullif(btrim(p_rag_memory_version), '') is null + or nullif(btrim(p_document_intelligence_version), '') is null + then + raise exception 'Deep-memory producer must be non-empty.' using errcode = '22023'; + end if; + if p_artifact_generation_id is null then + raise exception 'Deep-memory artifact generation must be set.' using errcode = '22023'; + end if; + if p_section_count is null or p_memory_card_count is null or p_repaired_anchor_count is null + or p_section_count < 0 or p_memory_card_count < 0 or p_repaired_anchor_count < 0 + then + raise exception 'Deep-memory counts cannot be negative.' using errcode = '22023'; + end if; + if p_index_unit_counts_by_type is null or jsonb_typeof(p_index_unit_counts_by_type) <> 'object' then + raise exception 'Deep-memory index-unit counts must be an object.' using errcode = '22023'; + end if; + if exists ( + select 1 + from jsonb_each(p_index_unit_counts_by_type) item + where jsonb_typeof(item.value) <> 'number' + or (item.value #>> '{}')::numeric < 0 + or trunc((item.value #>> '{}')::numeric) <> (item.value #>> '{}')::numeric + or (item.value #>> '{}')::numeric > 2147483647 + ) then + raise exception 'Deep-memory index-unit counts must be nonnegative integers.' using errcode = '22023'; + end if; + + select coalesce(d.metadata, '{}'::jsonb), d.owner_id + into v_document_metadata, v_document_owner_id + from public.documents d + where d.id = p_document_id + for update; + + if not found then + raise exception 'Document % does not exist.', p_document_id using errcode = 'P0002'; + end if; + + begin + v_committed_index_generation := nullif(v_document_metadata->>'index_generation_id', '')::uuid; + exception when invalid_text_representation then + raise exception 'Document % has an invalid committed index generation.', p_document_id using errcode = '22023'; + end; + if v_committed_index_generation is null then + raise exception 'Document % has no committed index generation.', p_document_id using errcode = '23514'; + end if; + if v_committed_index_generation = p_artifact_generation_id then + raise exception 'Staged deep-memory generation must differ from the committed index generation.' using errcode = '23514'; + end if; + + -- A generation UUID is a single-producer staging boundary. Reject collisions + -- rather than interpreting another producer's rows as part of this commit. + if exists ( + select 1 from public.document_sections + where document_id = p_document_id + and artifact_generation_id = p_artifact_generation_id + and producer is distinct from p_producer + ) or exists ( + select 1 from public.document_memory_cards + where document_id = p_document_id + and artifact_generation_id = p_artifact_generation_id + and producer is distinct from p_producer + ) or exists ( + select 1 from public.document_index_units + where document_id = p_document_id + and artifact_generation_id = p_artifact_generation_id + and producer is distinct from p_producer + ) then + raise exception 'Deep-memory artifact generation belongs to another producer.' using errcode = '23514'; + end if; + + -- Re-check producer evidence inside the transaction. Legacy NULL-generation + -- local-worker rows predate explicit producer metadata and are the only + -- unlabelled shape eligible for producer-scoped replacement. + if exists ( + select 1 from public.document_sections + where document_id = p_document_id + and ( + (producer is not null and nullif(metadata->>'generated_by', '') is distinct from producer) + or (producer is null and nullif(metadata->>'generated_by', '') is not null and metadata->>'generated_by' <> p_producer) + or (producer is null and artifact_generation_id is not null) + or ( + producer is null + and nullif(metadata->>'generated_by', '') is null + and not ( + p_producer = 'local-worker' + and artifact_generation_id is null + and metadata->>'rag_indexing_version' = 'rag-deep-memory-v1' + ) + ) + ) + ) or exists ( + select 1 from public.document_memory_cards + where document_id = p_document_id + and ( + (producer is not null and nullif(metadata->>'generated_by', '') is distinct from producer) + or (producer is null and nullif(metadata->>'generated_by', '') is not null and metadata->>'generated_by' <> p_producer) + or (producer is null and artifact_generation_id is not null) + or ( + producer is null + and nullif(metadata->>'generated_by', '') is null + and not (p_producer = 'local-worker' and artifact_generation_id is null) + ) + ) + ) or exists ( + select 1 from public.document_index_units + where document_id = p_document_id + and ( + (producer is not null and nullif(metadata->>'generated_by', '') is distinct from producer) + or (producer is null and nullif(metadata->>'generated_by', '') is not null and metadata->>'generated_by' <> p_producer) + or (producer is null and artifact_generation_id is not null) + or ( + producer is null + and nullif(metadata->>'generated_by', '') is null + and not (p_producer = 'local-worker' and artifact_generation_id is null) + ) + ) + ) then + raise exception 'Deep-memory artifact producer evidence is contradictory or ambiguous.' using errcode = '23514'; + end if; + + select count(*) into v_total_section_count + from public.document_sections + where document_id = p_document_id + and artifact_generation_id = p_artifact_generation_id; + + select count(*) into v_total_memory_card_count + from public.document_memory_cards + where document_id = p_document_id + and artifact_generation_id = p_artifact_generation_id; + + select count(*) into v_total_index_unit_count + from public.document_index_units + where document_id = p_document_id + and artifact_generation_id = p_artifact_generation_id; + + select count(*) into v_section_count + from public.document_sections + where document_id = p_document_id + and producer = p_producer + and artifact_generation_id = p_artifact_generation_id + and index_generation_id = p_artifact_generation_id + and owner_id is not distinct from v_document_owner_id + and metadata->>'generated_by' = p_producer + and metadata->>'artifact_generation_id' = p_artifact_generation_id::text + and metadata->>'index_generation_id' = p_artifact_generation_id::text; + + select count(*) into v_memory_card_count + from public.document_memory_cards + where document_id = p_document_id + and producer = p_producer + and artifact_generation_id = p_artifact_generation_id + and index_generation_id = p_artifact_generation_id + and owner_id is not distinct from v_document_owner_id + and metadata->>'generated_by' = p_producer + and metadata->>'artifact_generation_id' = p_artifact_generation_id::text + and metadata->>'index_generation_id' = p_artifact_generation_id::text; + + select coalesce(sum(unit_count), 0)::integer, coalesce(jsonb_object_agg(unit_type, unit_count), '{}'::jsonb) + into v_index_unit_count, v_index_unit_counts_by_type + from ( + select unit_type, count(*)::integer as unit_count + from public.document_index_units + where document_id = p_document_id + and producer = p_producer + and artifact_generation_id = p_artifact_generation_id + and index_generation_id = p_artifact_generation_id + and owner_id is not distinct from v_document_owner_id + and metadata->>'generated_by' = p_producer + and metadata->>'artifact_generation_id' = p_artifact_generation_id::text + and metadata->>'index_generation_id' = p_artifact_generation_id::text + group by unit_type + ) staged_index_units; + + select coalesce(sum(value::integer), 0) + into v_expected_index_unit_count + from jsonb_each_text(coalesce(p_index_unit_counts_by_type, '{}'::jsonb)); + + if v_total_section_count <> v_section_count + or v_total_memory_card_count <> v_memory_card_count + or v_total_index_unit_count <> coalesce(v_index_unit_count, 0) + or v_section_count <> p_section_count + or v_memory_card_count <> p_memory_card_count + or coalesce(v_index_unit_count, 0) <> v_expected_index_unit_count + or v_index_unit_counts_by_type <> coalesce(p_index_unit_counts_by_type, '{}'::jsonb) + then + raise exception 'Staged deep-memory artifact counts do not match the commit contract.' using errcode = '23514'; + end if; + + if exists ( + select 1 + from public.document_memory_cards card + left join public.document_sections section + on section.id = card.section_id + and section.document_id = p_document_id + and section.producer = p_producer + and section.artifact_generation_id = p_artifact_generation_id + where card.document_id = p_document_id + and card.producer = p_producer + and card.artifact_generation_id = p_artifact_generation_id + and card.section_id is not null + and section.id is null + ) then + raise exception 'Staged memory cards reference a section outside the staged generation.' using errcode = '23514'; + end if; + + -- Refuse to null another producer's section reference through ON DELETE SET + -- NULL. This keeps the "other producers untouched" guarantee literal. + if exists ( + select 1 + from public.document_memory_cards card + join public.document_sections section on section.id = card.section_id + where section.document_id = p_document_id + and section.artifact_generation_id is distinct from p_artifact_generation_id + and ( + section.producer = p_producer + or ( + section.producer is null + and ( + section.metadata->>'generated_by' = p_producer + or ( + p_producer = 'local-worker' + and nullif(section.metadata->>'generated_by', '') is null + and section.metadata->>'rag_indexing_version' = 'rag-deep-memory-v1' + ) + ) + ) + ) + and not ( + (card.producer = p_producer and card.metadata->>'generated_by' = p_producer) + or (card.producer is null and card.metadata->>'generated_by' = p_producer) + or ( + p_producer = 'local-worker' + and card.producer is null + and card.artifact_generation_id is null + and nullif(card.metadata->>'generated_by', '') is null + ) + ) + ) then + raise exception 'Another producer references an older section owned by this producer.' using errcode = '23514'; + end if; + + update public.document_sections + set + index_generation_id = v_committed_index_generation, + metadata = coalesce(metadata, '{}'::jsonb) || jsonb_build_object( + 'generated_by', p_producer, + 'artifact_generation_id', p_artifact_generation_id, + 'index_generation_id', v_committed_index_generation + ), + updated_at = now() + where document_id = p_document_id + and producer = p_producer + and artifact_generation_id = p_artifact_generation_id; + + update public.document_memory_cards + set + index_generation_id = v_committed_index_generation, + metadata = coalesce(metadata, '{}'::jsonb) || jsonb_build_object( + 'generated_by', p_producer, + 'artifact_generation_id', p_artifact_generation_id, + 'index_generation_id', v_committed_index_generation + ), + updated_at = now() + where document_id = p_document_id + and producer = p_producer + and artifact_generation_id = p_artifact_generation_id; + + update public.document_index_units + set + index_generation_id = v_committed_index_generation, + metadata = coalesce(metadata, '{}'::jsonb) || jsonb_build_object( + 'generated_by', p_producer, + 'artifact_generation_id', p_artifact_generation_id, + 'index_generation_id', v_committed_index_generation + ), + updated_at = now() + where document_id = p_document_id + and producer = p_producer + and artifact_generation_id = p_artifact_generation_id; + + delete from public.document_memory_cards + where document_id = p_document_id + and artifact_generation_id is distinct from p_artifact_generation_id + and ( + (producer = p_producer and metadata->>'generated_by' = p_producer) + or (producer is null and metadata->>'generated_by' = p_producer) + or ( + p_producer = 'local-worker' + and producer is null + and artifact_generation_id is null + and nullif(metadata->>'generated_by', '') is null + ) + ); + + delete from public.document_index_units + where document_id = p_document_id + and artifact_generation_id is distinct from p_artifact_generation_id + and ( + (producer = p_producer and metadata->>'generated_by' = p_producer) + or (producer is null and metadata->>'generated_by' = p_producer) + or ( + p_producer = 'local-worker' + and producer is null + and artifact_generation_id is null + and nullif(metadata->>'generated_by', '') is null + ) + ); + + delete from public.document_sections + where document_id = p_document_id + and artifact_generation_id is distinct from p_artifact_generation_id + and ( + (producer = p_producer and metadata->>'generated_by' = p_producer) + or ( + producer is null + and ( + metadata->>'generated_by' = p_producer + or ( + p_producer = 'local-worker' + and nullif(metadata->>'generated_by', '') is null + and metadata->>'rag_indexing_version' = 'rag-deep-memory-v1' + ) + ) + ) + ); + + -- This is deliberately the final logical mutation. Any error above rolls + -- back both activation and producer-scoped cleanup before metadata advertises + -- the new deep-memory generation. + perform public.apply_document_metadata_patch( + p_document_id, + jsonb_build_object( + 'rag_indexing_version', p_rag_memory_version, + 'rag_memory_version', p_rag_memory_version, + 'rag_memory_updated_at', now(), + 'document_intelligence_version', p_document_intelligence_version, + 'document_intelligence_updated_at', now(), + 'section_count', p_section_count, + 'memory_card_count', p_memory_card_count, + 'index_unit_count', v_expected_index_unit_count, + 'index_unit_counts_by_type', coalesce(p_index_unit_counts_by_type, '{}'::jsonb), + 'repaired_anchor_count', p_repaired_anchor_count, + 'deep_memory_generations', jsonb_build_object(p_producer, p_artifact_generation_id) + ) + ); + + return jsonb_build_object( + 'document_id', p_document_id, + 'producer', p_producer, + 'artifact_generation_id', p_artifact_generation_id, + 'index_generation_id', v_committed_index_generation, + 'section_count', p_section_count, + 'memory_card_count', p_memory_card_count, + 'index_unit_count', v_expected_index_unit_count + ); +end; +$$; + +revoke execute on function public.commit_document_deep_memory_generation(uuid, text, uuid, text, text, integer, integer, jsonb, integer) from public, anon, authenticated; +grant execute on function public.commit_document_deep_memory_generation(uuid, text, uuid, text, text, integer, integer, jsonb, integer) to service_role; diff --git a/tests/supabase-schema.test.ts b/tests/supabase-schema.test.ts index 25c9022a6..125551aa7 100644 --- a/tests/supabase-schema.test.ts +++ b/tests/supabase-schema.test.ts @@ -104,6 +104,10 @@ const ragRemediationFunctionReconciliationMigration = readFileSync( new URL("../supabase/migrations/20260713083000_reconcile_rag_remediation_functions.sql", import.meta.url), "utf8", ).replace(/\s+/g, " "); +const deepMemoryCommitReconciliationMigration = readFileSync( + new URL("../supabase/migrations/20260713090500_reconcile_deep_memory_commit.sql", import.meta.url), + "utf8", +).replace(/\s+/g, " "); const clinicalRegistryRecordsMigration = readFileSync( new URL("../supabase/migrations/20260703020000_clinical_registry_records.sql", import.meta.url), "utf8", @@ -956,6 +960,16 @@ describe("Supabase Preview replay guards", () => { ); expect(ragRemediationFunctionReconciliationMigration).toContain("on conflict (document_id) do nothing"); expect(ragRemediationFunctionReconciliationMigration).toContain("for update"); + expect(deepMemoryCommitReconciliationMigration).toContain( + "create or replace function public.commit_document_deep_memory_generation(", + ); + expect(deepMemoryCommitReconciliationMigration).toContain( + "Re-check producer evidence inside the transaction. Legacy NULL-generation", + ); + expect(deepMemoryCommitReconciliationMigration).toContain("local-worker rows predate explicit producer metadata"); + expect(deepMemoryCommitReconciliationMigration).toContain( + "and metadata->>'artifact_generation_id' = p_artifact_generation_id::text", + ); }); it("keeps retrieval_synopsis when adding lexical_score to match_document_chunks_text", () => {