@fayz-ai/plugin-scribe 0.12.2 → 0.12.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -2652,297 +2652,180 @@ function ScribeHeaderAction({ labels, item, entityId }) {
2652
2652
  }
2653
2653
 
2654
2654
  // src/migrations/index.ts
2655
- var MIGRATION_001_SCRIBE_BASE = `-- plugin-scribe 001: gravar um atendimento inteiro sem poder perd\xEA-lo.
2655
+ var MIGRATION_000_BASELINE = `-- ============================================================================
2656
+ -- plugins/plugin-scribe/src/migrations/000_baseline.sql \u2014 what installing this provisions, as one file.
2656
2657
  --
2657
- -- O problema que este schema resolve n\xE3o \xE9 "guardar \xE1udio". \xC9 que o profissional
2658
- -- aperta gravar, navega o app por 40 minutos, e QUALQUER coisa pode acontecer no
2659
- -- meio: reload, aba morta, wifi caindo, tampa fechando. Perder a consulta \xE9 uma
2660
- -- falha irrecuper\xE1vel \u2014 n\xE3o d\xE1 para pedir para o paciente repetir.
2658
+ -- GENERATED by scripts/emit-unit-baselines.mjs (#338). Do not edit by hand:
2659
+ -- change the schema, regenerate, and let scripts/check-chain-equivalence.mjs
2660
+ -- prove the result still matches.
2661
2661
  --
2662
- -- Por isso o desenho n\xE3o tem "o arquivo da sess\xE3o". Tem um MANIFESTO: a sess\xE3o
2663
- -- \xE9 uma linha, e cada 30 segundos de \xE1udio \xE9 OUTRA linha que sobe sozinha e \xE9
2664
- -- transcrita sozinha. O que chegou, chegou. O cliente pode morrer entre dois
2665
- -- segmentos e o que j\xE1 subiu continua \xEDntegro e j\xE1 transcrito.
2662
+ -- This replaced 3 migration files. They are kept, unapplied, in
2663
+ -- migrations.archive/ \u2014 a year of decisions is worth reading even when it is no
2664
+ -- longer worth replaying.
2666
2665
  --
2667
- -- ---------------------------------------------------------------------------
2668
- -- Duas dura\xE7\xF5es, e elas discordam de prop\xF3sito
2669
- -- ---------------------------------------------------------------------------
2670
- -- \`wall_duration_ms\` \xE9 o rel\xF3gio de parede que o browser reportou. Serve para o
2671
- -- cron\xF4metro na tela e para mais nada.
2672
- --
2673
- -- \`audio_duration_ms\` \xE9 SUM(segments.duration_ms) \u2014 soma do \xE1udio que de fato
2674
- -- existe, com a dura\xE7\xE3o vinda da resposta do STT, n\xE3o de um \`setInterval\`.
2675
- --
2676
- -- Elas divergem sempre que a aba congela: notebook dormindo por 10 minutos faz
2677
- -- o rel\xF3gio de parede contar a soneca inteira enquanto o MediaRecorder n\xE3o
2678
- -- emitiu um byte. Registro cl\xEDnico, medidor de cota e fatura usam a SEGUNDA.
2679
- -- Confundir as duas \xE9 cobrar do cliente pelo cochilo do notebook.
2680
- --
2681
- -- ---------------------------------------------------------------------------
2682
- -- O contador nunca \xE9 escrito pelo browser
2683
- -- ---------------------------------------------------------------------------
2684
- -- \`scribe_record_segment\` (se\xE7\xE3o 5) faz o upsert do segmento e RECALCULA os
2685
- -- contadores da sess\xE3o a partir da tabela de segmentos. Um cliente que sobe o
2686
- -- mesmo segmento duas vezes (replay depois de queda de rede) converge para o
2687
- -- mesmo n\xFAmero em vez de somar duas vezes.
2688
-
2689
- -- ---------------------------------------------------------------------------
2690
- -- 1. sessions \u2014 o encontro
2691
- -- ---------------------------------------------------------------------------
2692
- -- \`id\` N\xC3O tem DEFAULT gen_random_uuid(). \xC9 gerado no cliente, antes do primeiro
2693
- -- byte, e usado como chave no IndexedDB e no caminho do Storage. Sem isso a
2694
- -- grava\xE7\xE3o teria que esperar um round-trip para saber onde escrever \u2014 e uma
2695
- -- grava\xE7\xE3o que depende da rede para COME\xC7AR j\xE1 nasce fr\xE1gil.
2696
- CREATE TABLE IF NOT EXISTS public.plg_scribe_sessions (
2697
- id uuid PRIMARY KEY,
2698
- tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
2699
-
2700
- -- O titular. SET NULL e n\xE3o CASCADE: apagar a pessoa n\xE3o pode apagar em
2701
- -- sil\xEAncio o registro do atendimento que aconteceu.
2702
- subject_id uuid REFERENCES public.people(id) ON DELETE SET NULL,
2703
- context_kind text NOT NULL DEFAULT 'person'
2704
- CHECK (context_kind IN ('person', 'appointment', 'standalone')),
2705
- appointment_id uuid REFERENCES public.appointments(id) ON DELETE SET NULL,
2706
- owner_user_id uuid NOT NULL,
2707
-
2708
- status text NOT NULL DEFAULT 'recording'
2709
- CHECK (status IN ('recording','paused','interrupted','uploading','transcribing',
2710
- 'ready','generating','completed','abandoned','failed')),
2711
- locale text NOT NULL DEFAULT 'pt-BR',
2712
- mime_type text,
2713
-
2714
- started_at timestamptz NOT NULL DEFAULT now(),
2715
- ended_at timestamptz,
2716
- wall_duration_ms integer NOT NULL DEFAULT 0,
2717
- audio_duration_ms integer NOT NULL DEFAULT 0,
2718
-
2719
- segment_count integer NOT NULL DEFAULT 0,
2720
- uploaded_segment_count integer NOT NULL DEFAULT 0,
2721
- transcribed_segment_count integer NOT NULL DEFAULT 0,
2722
- transcript_chars integer NOT NULL DEFAULT 0,
2723
-
2724
- -- Base legal, carimbada antes do primeiro byte. "Quando" e "como" \xE9 o que se
2725
- -- prova depois; guardar isso na mesma linha da grava\xE7\xE3o \xE9 o que torna a prova
2726
- -- barata.
2727
- consent_at timestamptz,
2728
- consent_mode text CHECK (consent_mode IN ('verbal','written','implied_contract')),
2729
-
2730
- -- Reten\xE7\xE3o: o \xE1udio some, a transcri\xE7\xE3o e o documento assinado ficam.
2731
- retention_until date,
2732
- audio_deleted_at timestamptz,
2733
-
2734
- error text,
2735
- metadata jsonb NOT NULL DEFAULT '{}'::jsonb,
2736
- created_at timestamptz NOT NULL DEFAULT now(),
2737
- updated_at timestamptz NOT NULL DEFAULT now()
2666
+ -- Object order is fixed rather than dependency-sorted: schemas, types,
2667
+ -- sequences, tables, defaults, functions, constraints, views, indexes, foreign
2668
+ -- keys, triggers, row security, policies, grants. Dependencies BETWEEN units
2669
+ -- are carried by the order packages/db/chain.json declares.
2670
+ -- ============================================================================
2671
+
2672
+ -- A LANGUAGE sql function binds its body at CREATE time, and 570 functions
2673
+ -- sorted by name are not sorted by who calls whom. This is what lets them load
2674
+ -- in any order; every body is still compiled on first call, so a genuinely
2675
+ -- broken reference surfaces then rather than never. Session-scoped.
2676
+ SET check_function_bodies = false;
2677
+
2678
+
2679
+
2680
+ -- \u2500\u2500 default privileges: reset \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
2681
+ -- Cleared BEFORE anything is created, so every object below gets the ACL the
2682
+ -- dump describes rather than the image's defaults on top of it. Restored at
2683
+ -- the end of this file.
2684
+
2685
+ DO $dp$ BEGIN
2686
+ EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public REVOKE ALL ON SEQUENCES FROM anon, authenticated, service_role, fayz_connector$stmt$;
2687
+ EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
2688
+ RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public REVOKE ALL ON SEQUENCES FROM anon, authenticated, service_role, fayz_connector$stmt$;
2689
+ END $dp$;
2690
+
2691
+ DO $dp$ BEGIN
2692
+ EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public REVOKE ALL ON SEQUENCES FROM anon, authenticated, service_role, fayz_connector$stmt$;
2693
+ EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
2694
+ RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public REVOKE ALL ON SEQUENCES FROM anon, authenticated, service_role, fayz_connector$stmt$;
2695
+ END $dp$;
2696
+
2697
+ DO $dp$ BEGIN
2698
+ EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public REVOKE ALL ON FUNCTIONS FROM anon, authenticated, service_role, fayz_connector$stmt$;
2699
+ EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
2700
+ RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public REVOKE ALL ON FUNCTIONS FROM anon, authenticated, service_role, fayz_connector$stmt$;
2701
+ END $dp$;
2702
+
2703
+ DO $dp$ BEGIN
2704
+ EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public REVOKE ALL ON FUNCTIONS FROM anon, authenticated, service_role, fayz_connector$stmt$;
2705
+ EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
2706
+ RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public REVOKE ALL ON FUNCTIONS FROM anon, authenticated, service_role, fayz_connector$stmt$;
2707
+ END $dp$;
2708
+
2709
+ DO $dp$ BEGIN
2710
+ EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public REVOKE ALL ON TABLES FROM anon, authenticated, service_role, fayz_connector$stmt$;
2711
+ EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
2712
+ RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public REVOKE ALL ON TABLES FROM anon, authenticated, service_role, fayz_connector$stmt$;
2713
+ END $dp$;
2714
+
2715
+ DO $dp$ BEGIN
2716
+ EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public REVOKE ALL ON TABLES FROM anon, authenticated, service_role, fayz_connector$stmt$;
2717
+ EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
2718
+ RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public REVOKE ALL ON TABLES FROM anon, authenticated, service_role, fayz_connector$stmt$;
2719
+ END $dp$;
2720
+
2721
+
2722
+ -- \u2500\u2500 table \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
2723
+
2724
+ CREATE TABLE public.plg_scribe_generations (
2725
+ id uuid DEFAULT gen_random_uuid() NOT NULL,
2726
+ tenant_id uuid NOT NULL,
2727
+ session_id uuid NOT NULL,
2728
+ template_id uuid,
2729
+ template_key text,
2730
+ tab_order integer DEFAULT 0 NOT NULL,
2731
+ title text,
2732
+ status text DEFAULT 'pending'::text NOT NULL,
2733
+ markdown text,
2734
+ markdown_edited text,
2735
+ facts jsonb,
2736
+ model text,
2737
+ prompt_version text,
2738
+ input_tokens integer,
2739
+ output_tokens integer,
2740
+ error text,
2741
+ document_id uuid,
2742
+ created_by uuid,
2743
+ created_at timestamp with time zone DEFAULT now() NOT NULL,
2744
+ updated_at timestamp with time zone DEFAULT now() NOT NULL,
2745
+ CONSTRAINT plg_scribe_generations_status_check CHECK ((status = ANY (ARRAY['pending'::text, 'running'::text, 'ready'::text, 'failed'::text, 'committed'::text, 'discarded'::text])))
2738
2746
  );
2739
- ALTER TABLE public.plg_scribe_sessions ENABLE ROW LEVEL SECURITY;
2740
2747
 
2741
- CREATE INDEX IF NOT EXISTS idx_plg_scribe_sessions_tenant
2742
- ON public.plg_scribe_sessions(tenant_id);
2743
- CREATE INDEX IF NOT EXISTS idx_plg_scribe_sessions_subject
2744
- ON public.plg_scribe_sessions(subject_id) WHERE subject_id IS NOT NULL;
2745
- CREATE INDEX IF NOT EXISTS idx_plg_scribe_sessions_appointment
2746
- ON public.plg_scribe_sessions(appointment_id) WHERE appointment_id IS NOT NULL;
2747
-
2748
- -- A consulta da recupera\xE7\xE3o: "este usu\xE1rio tem alguma sess\xE3o aberta?". Roda em
2749
- -- todo boot do app, ent\xE3o \xE9 \xEDndice parcial e n\xE3o varredura.
2750
- CREATE INDEX IF NOT EXISTS idx_plg_scribe_sessions_open
2751
- ON public.plg_scribe_sessions(tenant_id, owner_user_id, status)
2752
- WHERE status IN ('recording','paused','interrupted','uploading','transcribing','ready','generating');
2753
-
2754
- -- A consulta do job de reten\xE7\xE3o.
2755
- CREATE INDEX IF NOT EXISTS idx_plg_scribe_sessions_retention
2756
- ON public.plg_scribe_sessions(retention_until)
2757
- WHERE audio_deleted_at IS NULL AND retention_until IS NOT NULL;
2758
-
2759
- -- ---------------------------------------------------------------------------
2760
- -- 2. segments \u2014 o manifesto dos peda\xE7os
2761
- -- ---------------------------------------------------------------------------
2762
- -- A PK \xE9 (session_id, seg_index) e n\xE3o um uuid: o \xEDndice do segmento j\xE1 \xE9
2763
- -- \xFAnico dentro da sess\xE3o, e usar isso como chave faz o re-upload do mesmo
2764
- -- peda\xE7o ser um ON CONFLICT em vez de uma linha duplicada. Idempot\xEAncia de
2765
- -- gra\xE7a, que \xE9 exatamente o que um pump com retry infinito precisa.
2766
- CREATE TABLE IF NOT EXISTS public.plg_scribe_segments (
2767
- session_id uuid NOT NULL REFERENCES public.plg_scribe_sessions(id) ON DELETE CASCADE,
2768
- seg_index integer NOT NULL,
2769
- tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
2770
-
2771
- storage_path text,
2772
- bytes integer,
2773
- start_offset_ms integer NOT NULL,
2774
- -- Vem da resposta do STT. O rel\xF3gio do JS n\xE3o \xE9 testemunha confi\xE1vel de
2775
- -- quanto \xE1udio existe: ele conta tempo, n\xE3o amostras.
2776
- duration_ms integer,
2777
-
2778
- -- Montado a partir de pieces de um segmento cortado no meio (crash). Vale
2779
- -- transcrever mesmo assim: 22 segundos de consulta \xE9 melhor que zero.
2780
- partial boolean NOT NULL DEFAULT false,
2781
-
2782
- -- Linha SINT\xC9TICA: n\xE3o tem \xE1udio, marca um buraco (sono, mic perdido,
2783
- -- permiss\xE3o revogada). Existe para a transcri\xE7\xE3o ADMITIR o buraco em vez de
2784
- -- emendar dois momentos como se fossem cont\xEDnuos. Isso \xE9 requisito
2785
- -- m\xE9dico-legal: um registro que esconde a costura \xE9 pior que um com furo.
2786
- gap boolean NOT NULL DEFAULT false,
2787
-
2788
- upload_state text NOT NULL DEFAULT 'pending'
2789
- CHECK (upload_state IN ('pending','uploaded','missing')),
2790
- uploaded_at timestamptz,
2791
-
2792
- stt_state text NOT NULL DEFAULT 'pending'
2793
- CHECK (stt_state IN ('pending','running','done','failed','skipped')),
2794
- stt_provider text,
2795
- stt_attempts smallint NOT NULL DEFAULT 0,
2796
- stt_error text,
2797
-
2798
- text text,
2799
- -- [{w,s,e,c,sp}] com offsets RELATIVOS a este segmento. Absoluto se calcula
2800
- -- somando start_offset_ms; guardar relativo \xE9 o que deixa reprocessar um
2801
- -- segmento sozinho sem reescrever os vizinhos.
2802
- words jsonb,
2803
- confidence real,
2804
-
2805
- created_at timestamptz NOT NULL DEFAULT now(),
2806
- updated_at timestamptz NOT NULL DEFAULT now(),
2807
- PRIMARY KEY (session_id, seg_index)
2748
+ CREATE TABLE public.plg_scribe_segments (
2749
+ session_id uuid NOT NULL,
2750
+ seg_index integer NOT NULL,
2751
+ tenant_id uuid NOT NULL,
2752
+ storage_path text,
2753
+ bytes integer,
2754
+ start_offset_ms integer NOT NULL,
2755
+ duration_ms integer,
2756
+ partial boolean DEFAULT false NOT NULL,
2757
+ gap boolean DEFAULT false NOT NULL,
2758
+ upload_state text DEFAULT 'pending'::text NOT NULL,
2759
+ uploaded_at timestamp with time zone,
2760
+ stt_state text DEFAULT 'pending'::text NOT NULL,
2761
+ stt_provider text,
2762
+ stt_attempts smallint DEFAULT 0 NOT NULL,
2763
+ stt_error text,
2764
+ text text,
2765
+ words jsonb,
2766
+ confidence real,
2767
+ created_at timestamp with time zone DEFAULT now() NOT NULL,
2768
+ updated_at timestamp with time zone DEFAULT now() NOT NULL,
2769
+ CONSTRAINT plg_scribe_segments_stt_state_check CHECK ((stt_state = ANY (ARRAY['pending'::text, 'running'::text, 'done'::text, 'failed'::text, 'skipped'::text]))),
2770
+ CONSTRAINT plg_scribe_segments_upload_state_check CHECK ((upload_state = ANY (ARRAY['pending'::text, 'uploaded'::text, 'missing'::text])))
2808
2771
  );
2809
- ALTER TABLE public.plg_scribe_segments ENABLE ROW LEVEL SECURITY;
2810
-
2811
- CREATE INDEX IF NOT EXISTS idx_plg_scribe_segments_tenant
2812
- ON public.plg_scribe_segments(tenant_id);
2813
2772
 
2814
- -- A fila do STT. Parcial porque s\xF3 interessa o que j\xE1 subiu e ainda n\xE3o foi
2815
- -- transcrito \u2014 que \xE9 uma fra\xE7\xE3o min\xFAscula da tabela em regime permanente.
2816
- CREATE INDEX IF NOT EXISTS idx_plg_scribe_segments_stt_queue
2817
- ON public.plg_scribe_segments(tenant_id, stt_state, created_at)
2818
- WHERE upload_state = 'uploaded' AND stt_state IN ('pending','failed');
2819
-
2820
- -- ---------------------------------------------------------------------------
2821
- -- 3. generations \u2014 uma tentativa de documento
2822
- -- ---------------------------------------------------------------------------
2823
- -- DUAS colunas de markdown, e isso n\xE3o \xE9 redund\xE2ncia.
2824
- --
2825
- -- \`markdown\` sa\xEDda do modelo, escrita uma vez, nunca mutada
2826
- -- \`markdown_edited\` a c\xF3pia de trabalho do humano
2827
- --
2828
- -- A diferen\xE7a entre elas \xE9 a \xFAnica prova de que algu\xE9m revisou. Ela sustenta
2829
- -- tr\xEAs coisas: o selo "revisado pelo profissional", o diff que diz se o prompt
2830
- -- est\xE1 bom (documento muito editado = prompt ruim), e a cadeia de proveni\xEAncia
2831
- -- que um registro m\xE9dico-legal precisa ter. Guardar s\xF3 o texto final apaga tudo
2832
- -- isso para economizar uma coluna de texto.
2833
- CREATE TABLE IF NOT EXISTS public.plg_scribe_generations (
2834
- id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
2835
- tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
2836
- session_id uuid NOT NULL REFERENCES public.plg_scribe_sessions(id) ON DELETE CASCADE,
2837
-
2838
- -- Um dos dois. \`template_id\` quando o tenant forkou e tem linha pr\xF3pria;
2839
- -- \`template_key\` quando \xE9 preset de c\xF3digo (que de prop\xF3sito n\xE3o tem linha,
2840
- -- para n\xE3o consumir a cota de templates do plano).
2841
- template_id uuid,
2842
- template_key text,
2843
-
2844
- tab_order integer NOT NULL DEFAULT 0,
2845
- title text,
2846
- status text NOT NULL DEFAULT 'pending'
2847
- CHECK (status IN ('pending','running','ready','failed','committed','discarded')),
2848
-
2849
- markdown text,
2850
- markdown_edited text,
2851
-
2852
- -- Extra\xE7\xE3o stage-1 do modo two-stage. A coluna entra agora, vazia, porque a
2853
- -- interface j\xE1 nasce com esse formato \u2014 quando as abas de multi-documento
2854
- -- chegarem, \xE9 preencher, n\xE3o migrar.
2855
- facts jsonb,
2856
-
2857
- model text,
2858
- prompt_version text,
2859
- input_tokens integer,
2860
- output_tokens integer,
2861
- error text,
2862
-
2863
- document_id uuid,
2864
- created_by uuid,
2865
- created_at timestamptz NOT NULL DEFAULT now(),
2866
- updated_at timestamptz NOT NULL DEFAULT now()
2773
+ CREATE TABLE public.plg_scribe_sessions (
2774
+ id uuid NOT NULL,
2775
+ tenant_id uuid NOT NULL,
2776
+ subject_id uuid,
2777
+ context_kind text DEFAULT 'person'::text NOT NULL,
2778
+ appointment_id uuid,
2779
+ owner_user_id uuid NOT NULL,
2780
+ status text DEFAULT 'recording'::text NOT NULL,
2781
+ locale text DEFAULT 'pt-BR'::text NOT NULL,
2782
+ mime_type text,
2783
+ started_at timestamp with time zone DEFAULT now() NOT NULL,
2784
+ ended_at timestamp with time zone,
2785
+ wall_duration_ms integer DEFAULT 0 NOT NULL,
2786
+ audio_duration_ms integer DEFAULT 0 NOT NULL,
2787
+ segment_count integer DEFAULT 0 NOT NULL,
2788
+ uploaded_segment_count integer DEFAULT 0 NOT NULL,
2789
+ transcribed_segment_count integer DEFAULT 0 NOT NULL,
2790
+ transcript_chars integer DEFAULT 0 NOT NULL,
2791
+ consent_at timestamp with time zone,
2792
+ consent_mode text,
2793
+ retention_until date,
2794
+ audio_deleted_at timestamp with time zone,
2795
+ error text,
2796
+ metadata jsonb DEFAULT '{}'::jsonb NOT NULL,
2797
+ created_at timestamp with time zone DEFAULT now() NOT NULL,
2798
+ updated_at timestamp with time zone DEFAULT now() NOT NULL,
2799
+ CONSTRAINT plg_scribe_sessions_consent_mode_check CHECK ((consent_mode = ANY (ARRAY['verbal'::text, 'written'::text, 'implied_contract'::text]))),
2800
+ CONSTRAINT plg_scribe_sessions_context_kind_check CHECK ((context_kind = ANY (ARRAY['person'::text, 'appointment'::text, 'standalone'::text]))),
2801
+ CONSTRAINT plg_scribe_sessions_status_check CHECK ((status = ANY (ARRAY['recording'::text, 'paused'::text, 'interrupted'::text, 'uploading'::text, 'transcribing'::text, 'ready'::text, 'generating'::text, 'completed'::text, 'abandoned'::text, 'failed'::text])))
2867
2802
  );
2868
- ALTER TABLE public.plg_scribe_generations ENABLE ROW LEVEL SECURITY;
2869
2803
 
2870
- CREATE INDEX IF NOT EXISTS idx_plg_scribe_generations_session
2871
- ON public.plg_scribe_generations(session_id, tab_order);
2872
- CREATE INDEX IF NOT EXISTS idx_plg_scribe_generations_tenant
2873
- ON public.plg_scribe_generations(tenant_id);
2874
2804
 
2875
- -- FKs para plugin-forms e para o arqu\xE9tipo de documentos s\xE3o condicionais: o
2876
- -- scribe declara \`dependencies: ['custom_forms']\`, mas a ordem de aplica\xE7\xE3o
2877
- -- entre plugins n\xE3o \xE9 garantida e um pool pode aplicar este arquivo antes.
2878
- -- Guardado, o re-apply converge; sem guarda, quebraria o passo inteiro.
2879
- DO $$
2880
- BEGIN
2881
- IF to_regclass('public.plg_forms_templates') IS NOT NULL
2882
- AND NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'plg_scribe_generations_template_fk') THEN
2883
- ALTER TABLE public.plg_scribe_generations
2884
- ADD CONSTRAINT plg_scribe_generations_template_fk
2885
- FOREIGN KEY (template_id) REFERENCES public.plg_forms_templates(id) ON DELETE SET NULL;
2886
- END IF;
2805
+ -- \u2500\u2500 function \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
2887
2806
 
2888
- IF to_regclass('public.documents') IS NOT NULL
2889
- AND NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'plg_scribe_generations_document_fk') THEN
2890
- ALTER TABLE public.plg_scribe_generations
2891
- ADD CONSTRAINT plg_scribe_generations_document_fk
2892
- FOREIGN KEY (document_id) REFERENCES public.documents(id) ON DELETE SET NULL;
2893
- END IF;
2894
- END $$;
2895
-
2896
- -- ---------------------------------------------------------------------------
2897
- -- 4. RLS \u2014 a forma can\xF4nica deste repo, tr\xEAs tabelas x quatro verbos
2898
- -- ---------------------------------------------------------------------------
2899
- DROP POLICY IF EXISTS plg_scribe_sessions_select ON public.plg_scribe_sessions;
2900
- CREATE POLICY plg_scribe_sessions_select ON public.plg_scribe_sessions FOR SELECT TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
2901
- DROP POLICY IF EXISTS plg_scribe_sessions_insert ON public.plg_scribe_sessions;
2902
- CREATE POLICY plg_scribe_sessions_insert ON public.plg_scribe_sessions FOR INSERT TO authenticated WITH CHECK (tenant_id IN (SELECT public.user_tenant_ids()));
2903
- DROP POLICY IF EXISTS plg_scribe_sessions_update ON public.plg_scribe_sessions;
2904
- CREATE POLICY plg_scribe_sessions_update ON public.plg_scribe_sessions FOR UPDATE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
2905
- DROP POLICY IF EXISTS plg_scribe_sessions_delete ON public.plg_scribe_sessions;
2906
- CREATE POLICY plg_scribe_sessions_delete ON public.plg_scribe_sessions FOR DELETE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
2907
- GRANT SELECT, INSERT, UPDATE, DELETE ON public.plg_scribe_sessions TO authenticated;
2908
-
2909
- DROP POLICY IF EXISTS plg_scribe_segments_select ON public.plg_scribe_segments;
2910
- CREATE POLICY plg_scribe_segments_select ON public.plg_scribe_segments FOR SELECT TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
2911
- DROP POLICY IF EXISTS plg_scribe_segments_insert ON public.plg_scribe_segments;
2912
- CREATE POLICY plg_scribe_segments_insert ON public.plg_scribe_segments FOR INSERT TO authenticated WITH CHECK (tenant_id IN (SELECT public.user_tenant_ids()));
2913
- DROP POLICY IF EXISTS plg_scribe_segments_update ON public.plg_scribe_segments;
2914
- CREATE POLICY plg_scribe_segments_update ON public.plg_scribe_segments FOR UPDATE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
2915
- DROP POLICY IF EXISTS plg_scribe_segments_delete ON public.plg_scribe_segments;
2916
- CREATE POLICY plg_scribe_segments_delete ON public.plg_scribe_segments FOR DELETE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
2917
- GRANT SELECT, INSERT, UPDATE, DELETE ON public.plg_scribe_segments TO authenticated;
2918
-
2919
- DROP POLICY IF EXISTS plg_scribe_generations_select ON public.plg_scribe_generations;
2920
- CREATE POLICY plg_scribe_generations_select ON public.plg_scribe_generations FOR SELECT TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
2921
- DROP POLICY IF EXISTS plg_scribe_generations_insert ON public.plg_scribe_generations;
2922
- CREATE POLICY plg_scribe_generations_insert ON public.plg_scribe_generations FOR INSERT TO authenticated WITH CHECK (tenant_id IN (SELECT public.user_tenant_ids()));
2923
- DROP POLICY IF EXISTS plg_scribe_generations_update ON public.plg_scribe_generations;
2924
- CREATE POLICY plg_scribe_generations_update ON public.plg_scribe_generations FOR UPDATE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
2925
- DROP POLICY IF EXISTS plg_scribe_generations_delete ON public.plg_scribe_generations;
2926
- CREATE POLICY plg_scribe_generations_delete ON public.plg_scribe_generations FOR DELETE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
2927
- GRANT SELECT, INSERT, UPDATE, DELETE ON public.plg_scribe_generations TO authenticated;
2928
-
2929
- -- ---------------------------------------------------------------------------
2930
- -- 5. scribe_record_segment \u2014 o browser registra, o banco conta
2931
- -- ---------------------------------------------------------------------------
2932
- -- SECURITY INVOKER: a RLS acima j\xE1 amarra o tenant. N\xE3o h\xE1 nada aqui que
2933
- -- precise de privil\xE9gio elevado, e uma fun\xE7\xE3o SECURITY DEFINER que n\xE3o precisa
2934
- -- ser \xE9 s\xF3 superf\xEDcie de ataque a mais.
2935
- --
2936
- -- O ponto da fun\xE7\xE3o \xE9 a segunda metade: recalcular os contadores da sess\xE3o a
2937
- -- partir de um SELECT sobre os segmentos, nunca de um \`+1\` vindo do cliente.
2938
- -- Um pump com retry infinito vai reenviar o mesmo segmento, e um contador
2939
- -- incremental transformaria isso em n\xFAmeros que crescem sem o \xE1udio crescer.
2940
- CREATE OR REPLACE FUNCTION public.scribe_record_segment(p_payload jsonb)
2941
- RETURNS jsonb
2942
- LANGUAGE plpgsql
2943
- SECURITY INVOKER
2944
- SET search_path TO 'public'
2945
- AS $function$
2807
+ CREATE FUNCTION public.scribe_audio_path_ok(p_name text) RETURNS boolean
2808
+ LANGUAGE sql STABLE SECURITY DEFINER
2809
+ SET search_path TO 'public'
2810
+ AS $_$
2811
+ SELECT
2812
+ p_name IS NOT NULL
2813
+ AND length(p_name) <= 200
2814
+ AND p_name ~ ('^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'
2815
+ || '/[0-9]{4}/[0-9]{2}'
2816
+ || '/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'
2817
+ || '/[0-9]{5}\\.(webm|ogg|m4a|mp3)$')
2818
+ -- compara\xE7\xE3o em texto (nunca \`::uuid\`): entrada malformada devolve falso em
2819
+ -- vez de derrubar a policy com erro de cast.
2820
+ AND EXISTS (
2821
+ SELECT 1 FROM public.tenants t WHERE t.id::text = split_part(p_name, '/', 1)
2822
+ );
2823
+ $_$;
2824
+
2825
+ CREATE FUNCTION public.scribe_record_segment(p_payload jsonb) RETURNS jsonb
2826
+ LANGUAGE plpgsql
2827
+ SET search_path TO 'public'
2828
+ AS $$
2946
2829
  DECLARE
2947
2830
  v_session_id uuid;
2948
2831
  v_seg_index integer;
@@ -3021,115 +2904,324 @@ BEGIN
3021
2904
 
3022
2905
  RETURN v_result;
3023
2906
  END;
3024
- $function$;
2907
+ $$;
3025
2908
 
3026
- COMMENT ON FUNCTION public.scribe_record_segment(jsonb) IS
3027
- 'Idempotent upsert of one audio segment + recomputation of the parent session counters from the segments table. Called by the browser upload pump after each successful Storage write.';
3028
2909
 
3029
- GRANT EXECUTE ON FUNCTION public.scribe_record_segment(jsonb) TO authenticated;
2910
+ -- \u2500\u2500 constraint \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
3030
2911
 
3031
- -- ---------------------------------------------------------------------------
3032
- -- 6. o bucket
3033
- -- ---------------------------------------------------------------------------
3034
- -- PRIVADO, sem discuss\xE3o: \xE9 a voz de uma pessoa falando da pr\xF3pria sa\xFAde. URL
3035
- -- p\xFAblica seria um endere\xE7o eterno para isso. O backoffice l\xEA por URL assinada
3036
- -- de curta dura\xE7\xE3o; a edge function de transcri\xE7\xE3o l\xEA com service key.
3037
- --
3038
- -- 5 MB por SEGMENTO \xE9 folgado por design: 30 s de Opus mono a 24 kbps d\xE1 ~90 KB.
3039
- -- O limite existe para barrar um cliente com bug mandando um arquivo inteiro,
3040
- -- n\xE3o para apertar o caso normal.
3041
- INSERT INTO storage.buckets (id, name, public, file_size_limit, allowed_mime_types)
3042
- VALUES (
3043
- 'scribe-audio',
3044
- 'scribe-audio',
3045
- false,
3046
- 5242880,
3047
- ARRAY['audio/webm','audio/ogg','audio/mp4','audio/mpeg','audio/m4a','audio/x-m4a']
3048
- )
3049
- ON CONFLICT (id) DO UPDATE SET
3050
- public = EXCLUDED.public,
3051
- file_size_limit = EXCLUDED.file_size_limit,
3052
- allowed_mime_types = EXCLUDED.allowed_mime_types;
3053
-
3054
- -- ---------------------------------------------------------------------------
3055
- -- 7. o caminho \xE9 v\xE1lido?
3056
- -- ---------------------------------------------------------------------------
3057
- -- Gram\xE1tica (o SDK constr\xF3i, o banco confere):
3058
- --
3059
- -- {tenant_id}/{yyyy}/{mm}/{session_id}/{seg_index:05d}.{ext}
3060
- -- 1 2 3 4 5
3061
- --
3062
- -- 1 tenant_id chave do isolamento \u2014 toda policy pendura aqui
3063
- -- 2,3 yyyy/mm varredura de reten\xE7\xE3o por prefixo (LGPD), pasta list\xE1vel
3064
- -- 4 session_id uuid do cliente: agrupa a sess\xE3o e \xE9 imprevis\xEDvel
3065
- -- 5 seg_index zero-padded, ent\xE3o list() j\xE1 volta em ordem cronol\xF3gica
3066
- --
3067
- -- SECURITY DEFINER porque o EXISTS olha \`tenants\`, que tem RLS.
3068
- CREATE OR REPLACE FUNCTION public.scribe_audio_path_ok(p_name text)
3069
- RETURNS boolean
3070
- LANGUAGE sql
3071
- STABLE
3072
- SECURITY DEFINER
3073
- SET search_path TO 'public'
3074
- AS $function$
3075
- SELECT
3076
- p_name IS NOT NULL
3077
- AND length(p_name) <= 200
3078
- AND p_name ~ ('^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'
3079
- || '/[0-9]{4}/[0-9]{2}'
3080
- || '/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'
3081
- || '/[0-9]{5}\\.(webm|ogg|m4a|mp3)$')
3082
- -- compara\xE7\xE3o em texto (nunca \`::uuid\`): entrada malformada devolve falso em
3083
- -- vez de derrubar a policy com erro de cast.
3084
- AND EXISTS (
3085
- SELECT 1 FROM public.tenants t WHERE t.id::text = split_part(p_name, '/', 1)
3086
- );
3087
- $function$;
2912
+ ALTER TABLE ONLY public.plg_scribe_generations
2913
+ ADD CONSTRAINT plg_scribe_generations_pkey PRIMARY KEY (id);
3088
2914
 
3089
- COMMENT ON FUNCTION public.scribe_audio_path_ok(text) IS
3090
- 'True when a storage object name follows the scribe-audio grammar {tenant}/{yyyy}/{mm}/{session}/{segIndex}.{ext} and segment 1 is a real tenant. Used by the INSERT policy on that bucket.';
2915
+ ALTER TABLE ONLY public.plg_scribe_segments
2916
+ ADD CONSTRAINT plg_scribe_segments_pkey PRIMARY KEY (session_id, seg_index);
3091
2917
 
3092
- GRANT EXECUTE ON FUNCTION public.scribe_audio_path_ok(text) TO authenticated;
2918
+ ALTER TABLE ONLY public.plg_scribe_sessions
2919
+ ADD CONSTRAINT plg_scribe_sessions_pkey PRIMARY KEY (id);
3093
2920
 
3094
- -- ---------------------------------------------------------------------------
3095
- -- 8. pol\xEDticas do bucket
3096
- -- ---------------------------------------------------------------------------
3097
- DROP POLICY IF EXISTS plg_scribe_audio_insert ON storage.objects;
3098
- DROP POLICY IF EXISTS plg_scribe_audio_read ON storage.objects;
3099
- DROP POLICY IF EXISTS plg_scribe_audio_delete ON storage.objects;
3100
2921
 
3101
- -- Escrever: s\xF3 logado (diferente do bucket do CRM, que aceita visitante an\xF4nimo
3102
- -- \u2014 aqui quem grava \xE9 sempre um profissional autenticado), dentro da gram\xE1tica
3103
- -- e da pr\xF3pria pasta.
3104
- --
3105
- -- N\xC3O existe policy de UPDATE, e isso \xE9 a trava mais importante das tr\xEAs: sem
3106
- -- ela o \`upsert\` falha, e um caminho j\xE1 gravado NUNCA pode ser trocado por
3107
- -- outro conte\xFAdo. \xC9 o que torna o \xE1udio uma testemunha e n\xE3o um rascunho.
3108
- CREATE POLICY plg_scribe_audio_insert ON storage.objects
3109
- FOR INSERT TO authenticated
3110
- WITH CHECK (
3111
- bucket_id = 'scribe-audio'
3112
- AND public.scribe_audio_path_ok(name)
3113
- AND (storage.foldername(name))[1] IN (SELECT public.user_tenant_ids()::text)
3114
- );
2922
+ -- \u2500\u2500 view \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
3115
2923
 
3116
- CREATE POLICY plg_scribe_audio_read ON storage.objects
3117
- FOR SELECT TO authenticated
3118
- USING (
3119
- bucket_id = 'scribe-audio'
3120
- AND (storage.foldername(name))[1] IN (SELECT public.user_tenant_ids()::text)
3121
- );
2924
+ CREATE VIEW public.v_scribe_generations WITH (security_invoker='true') AS
2925
+ SELECT g.id,
2926
+ g.tenant_id,
2927
+ g.session_id,
2928
+ g.template_id,
2929
+ g.template_key,
2930
+ g.tab_order,
2931
+ g.title,
2932
+ g.status,
2933
+ g.markdown,
2934
+ g.markdown_edited,
2935
+ g.facts,
2936
+ g.model,
2937
+ g.prompt_version,
2938
+ g.input_tokens,
2939
+ g.output_tokens,
2940
+ g.error,
2941
+ g.document_id,
2942
+ g.created_by,
2943
+ g.created_at,
2944
+ g.updated_at,
2945
+ (t.id IS NOT NULL) AS template_exists,
2946
+ t.name AS template_name
2947
+ FROM (public.plg_scribe_generations g
2948
+ LEFT JOIN public.plg_forms_templates t ON ((t.id = g.template_id)));
3122
2949
 
3123
- -- Apagar: mesma regra da leitura. \xC9 o que torna "apagar o \xE1udio desta sess\xE3o"
3124
- -- execut\xE1vel por quem \xE9 dono dele \u2014 o direito de elimina\xE7\xE3o do art. 18 VI n\xE3o
3125
- -- vale nada se ningu\xE9m no tenant consegue exerc\xEA-lo.
3126
- CREATE POLICY plg_scribe_audio_delete ON storage.objects
3127
- FOR DELETE TO authenticated
3128
- USING (
3129
- bucket_id = 'scribe-audio'
3130
- AND (storage.foldername(name))[1] IN (SELECT public.user_tenant_ids()::text)
3131
- );
2950
+
2951
+ -- \u2500\u2500 index \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
2952
+
2953
+ CREATE INDEX idx_plg_scribe_generations_session ON public.plg_scribe_generations USING btree (session_id, tab_order);
2954
+
2955
+ CREATE INDEX idx_plg_scribe_generations_tenant ON public.plg_scribe_generations USING btree (tenant_id);
2956
+
2957
+ CREATE INDEX idx_plg_scribe_segments_stt_queue ON public.plg_scribe_segments USING btree (tenant_id, stt_state, created_at) WHERE ((upload_state = 'uploaded'::text) AND (stt_state = ANY (ARRAY['pending'::text, 'failed'::text])));
2958
+
2959
+ CREATE INDEX idx_plg_scribe_segments_tenant ON public.plg_scribe_segments USING btree (tenant_id);
2960
+
2961
+ CREATE INDEX idx_plg_scribe_sessions_appointment ON public.plg_scribe_sessions USING btree (appointment_id) WHERE (appointment_id IS NOT NULL);
2962
+
2963
+ CREATE INDEX idx_plg_scribe_sessions_open ON public.plg_scribe_sessions USING btree (tenant_id, owner_user_id, status) WHERE (status = ANY (ARRAY['recording'::text, 'paused'::text, 'interrupted'::text, 'uploading'::text, 'transcribing'::text, 'ready'::text, 'generating'::text]));
2964
+
2965
+ CREATE INDEX idx_plg_scribe_sessions_retention ON public.plg_scribe_sessions USING btree (retention_until) WHERE ((audio_deleted_at IS NULL) AND (retention_until IS NOT NULL));
2966
+
2967
+ CREATE INDEX idx_plg_scribe_sessions_subject ON public.plg_scribe_sessions USING btree (subject_id) WHERE (subject_id IS NOT NULL);
2968
+
2969
+ CREATE INDEX idx_plg_scribe_sessions_tenant ON public.plg_scribe_sessions USING btree (tenant_id);
2970
+
2971
+
2972
+ -- \u2500\u2500 fk constraint \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
2973
+
2974
+ ALTER TABLE ONLY public.plg_scribe_generations
2975
+ ADD CONSTRAINT plg_scribe_generations_document_fk FOREIGN KEY (document_id) REFERENCES public.documents(id) ON DELETE SET NULL;
2976
+
2977
+ ALTER TABLE ONLY public.plg_scribe_generations
2978
+ ADD CONSTRAINT plg_scribe_generations_session_id_fkey FOREIGN KEY (session_id) REFERENCES public.plg_scribe_sessions(id) ON DELETE CASCADE;
2979
+
2980
+ ALTER TABLE ONLY public.plg_scribe_generations
2981
+ ADD CONSTRAINT plg_scribe_generations_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE;
2982
+
2983
+ ALTER TABLE ONLY public.plg_scribe_segments
2984
+ ADD CONSTRAINT plg_scribe_segments_session_id_fkey FOREIGN KEY (session_id) REFERENCES public.plg_scribe_sessions(id) ON DELETE CASCADE;
2985
+
2986
+ ALTER TABLE ONLY public.plg_scribe_segments
2987
+ ADD CONSTRAINT plg_scribe_segments_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE;
2988
+
2989
+ ALTER TABLE ONLY public.plg_scribe_sessions
2990
+ ADD CONSTRAINT plg_scribe_sessions_appointment_id_fkey FOREIGN KEY (appointment_id) REFERENCES public.appointments(id) ON DELETE SET NULL;
2991
+
2992
+ ALTER TABLE ONLY public.plg_scribe_sessions
2993
+ ADD CONSTRAINT plg_scribe_sessions_subject_id_fkey FOREIGN KEY (subject_id) REFERENCES public.people(id) ON DELETE SET NULL;
2994
+
2995
+ ALTER TABLE ONLY public.plg_scribe_sessions
2996
+ ADD CONSTRAINT plg_scribe_sessions_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE;
2997
+
2998
+
2999
+ -- \u2500\u2500 row security \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
3000
+
3001
+ ALTER TABLE public.plg_scribe_generations ENABLE ROW LEVEL SECURITY;
3002
+
3003
+ ALTER TABLE public.plg_scribe_segments ENABLE ROW LEVEL SECURITY;
3004
+
3005
+ ALTER TABLE public.plg_scribe_sessions ENABLE ROW LEVEL SECURITY;
3006
+
3007
+
3008
+ -- \u2500\u2500 policy \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
3009
+
3010
+ CREATE POLICY plg_scribe_generations_delete ON public.plg_scribe_generations FOR DELETE TO authenticated USING ((tenant_id IN ( SELECT public.user_tenant_ids() AS user_tenant_ids)));
3011
+
3012
+ CREATE POLICY plg_scribe_generations_insert ON public.plg_scribe_generations FOR INSERT TO authenticated WITH CHECK ((tenant_id IN ( SELECT public.user_tenant_ids() AS user_tenant_ids)));
3013
+
3014
+ CREATE POLICY plg_scribe_generations_select ON public.plg_scribe_generations FOR SELECT TO authenticated USING ((tenant_id IN ( SELECT public.user_tenant_ids() AS user_tenant_ids)));
3015
+
3016
+ CREATE POLICY plg_scribe_generations_update ON public.plg_scribe_generations FOR UPDATE TO authenticated USING ((tenant_id IN ( SELECT public.user_tenant_ids() AS user_tenant_ids)));
3017
+
3018
+ CREATE POLICY plg_scribe_segments_delete ON public.plg_scribe_segments FOR DELETE TO authenticated USING ((tenant_id IN ( SELECT public.user_tenant_ids() AS user_tenant_ids)));
3019
+
3020
+ CREATE POLICY plg_scribe_segments_insert ON public.plg_scribe_segments FOR INSERT TO authenticated WITH CHECK ((tenant_id IN ( SELECT public.user_tenant_ids() AS user_tenant_ids)));
3021
+
3022
+ CREATE POLICY plg_scribe_segments_select ON public.plg_scribe_segments FOR SELECT TO authenticated USING ((tenant_id IN ( SELECT public.user_tenant_ids() AS user_tenant_ids)));
3023
+
3024
+ CREATE POLICY plg_scribe_segments_update ON public.plg_scribe_segments FOR UPDATE TO authenticated USING ((tenant_id IN ( SELECT public.user_tenant_ids() AS user_tenant_ids)));
3025
+
3026
+ CREATE POLICY plg_scribe_sessions_delete ON public.plg_scribe_sessions FOR DELETE TO authenticated USING ((tenant_id IN ( SELECT public.user_tenant_ids() AS user_tenant_ids)));
3027
+
3028
+ CREATE POLICY plg_scribe_sessions_insert ON public.plg_scribe_sessions FOR INSERT TO authenticated WITH CHECK ((tenant_id IN ( SELECT public.user_tenant_ids() AS user_tenant_ids)));
3029
+
3030
+ CREATE POLICY plg_scribe_sessions_select ON public.plg_scribe_sessions FOR SELECT TO authenticated USING ((tenant_id IN ( SELECT public.user_tenant_ids() AS user_tenant_ids)));
3031
+
3032
+ CREATE POLICY plg_scribe_sessions_update ON public.plg_scribe_sessions FOR UPDATE TO authenticated USING ((tenant_id IN ( SELECT public.user_tenant_ids() AS user_tenant_ids)));
3033
+
3034
+
3035
+ -- \u2500\u2500 acl \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
3036
+
3037
+ GRANT ALL ON FUNCTION public.scribe_audio_path_ok(p_name text) TO authenticated;
3038
+ GRANT ALL ON FUNCTION public.scribe_audio_path_ok(p_name text) TO service_role;
3039
+
3040
+ GRANT ALL ON FUNCTION public.scribe_record_segment(p_payload jsonb) TO authenticated;
3041
+ GRANT ALL ON FUNCTION public.scribe_record_segment(p_payload jsonb) TO service_role;
3042
+
3043
+ GRANT MAINTAIN ON TABLE public.plg_scribe_generations TO anon;
3044
+ GRANT ALL ON TABLE public.plg_scribe_generations TO authenticated;
3045
+ GRANT ALL ON TABLE public.plg_scribe_generations TO service_role;
3046
+
3047
+ GRANT MAINTAIN ON TABLE public.plg_scribe_segments TO anon;
3048
+ GRANT ALL ON TABLE public.plg_scribe_segments TO authenticated;
3049
+ GRANT ALL ON TABLE public.plg_scribe_segments TO service_role;
3050
+
3051
+ GRANT MAINTAIN ON TABLE public.plg_scribe_sessions TO anon;
3052
+ GRANT ALL ON TABLE public.plg_scribe_sessions TO authenticated;
3053
+ GRANT ALL ON TABLE public.plg_scribe_sessions TO service_role;
3054
+
3055
+ GRANT MAINTAIN ON TABLE public.v_scribe_generations TO anon;
3056
+ GRANT ALL ON TABLE public.v_scribe_generations TO authenticated;
3057
+ GRANT ALL ON TABLE public.v_scribe_generations TO service_role;
3058
+
3059
+
3060
+ -- \u2500\u2500 comment \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
3061
+
3062
+ COMMENT ON FUNCTION public.scribe_audio_path_ok(p_name text) IS 'True when a storage object name follows the scribe-audio grammar {tenant}/{yyyy}/{mm}/{session}/{segIndex}.{ext} and segment 1 is a real tenant. Used by the INSERT policy on that bucket.';
3063
+
3064
+ COMMENT ON FUNCTION public.scribe_record_segment(p_payload jsonb) IS 'Idempotent upsert of one audio segment + recomputation of the parent session counters from the segments table. Called by the browser upload pump after each successful Storage write.';
3065
+
3066
+ COMMENT ON COLUMN public.plg_scribe_generations.template_id IS 'The forms template this generation came from, by id and not by foreign key (003, ADR 0015). A soft reference on purpose: forms owns that table and must be able to release it without scribe''s permission. A row whose template is gone reads as absent, which is what the old ON DELETE SET NULL was arranging.';
3067
+
3068
+ COMMENT ON VIEW public.v_scribe_generations IS 'Generations with their forms template resolved by LEFT JOIN rather than by foreign key (003). \`template_exists\` is the honest answer on a cluster where forms is not installed, or where the template was deleted.';
3069
+
3070
+
3071
+ -- \u2500\u2500 default privileges: restore \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
3072
+ -- Put back, so the NEXT thing installed inherits what the chain had.
3073
+
3074
+ DO $dp$ BEGIN
3075
+ EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON SEQUENCES TO postgres$stmt$;
3076
+ EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
3077
+ RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON SEQUENCES TO postgres$stmt$;
3078
+ END $dp$;
3079
+
3080
+ DO $dp$ BEGIN
3081
+ EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON SEQUENCES TO anon$stmt$;
3082
+ EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
3083
+ RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON SEQUENCES TO anon$stmt$;
3084
+ END $dp$;
3085
+
3086
+ DO $dp$ BEGIN
3087
+ EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON SEQUENCES TO authenticated$stmt$;
3088
+ EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
3089
+ RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON SEQUENCES TO authenticated$stmt$;
3090
+ END $dp$;
3091
+
3092
+ DO $dp$ BEGIN
3093
+ EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON SEQUENCES TO service_role$stmt$;
3094
+ EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
3095
+ RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON SEQUENCES TO service_role$stmt$;
3096
+ END $dp$;
3097
+
3098
+ DO $dp$ BEGIN
3099
+ EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON SEQUENCES TO postgres$stmt$;
3100
+ EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
3101
+ RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON SEQUENCES TO postgres$stmt$;
3102
+ END $dp$;
3103
+
3104
+ DO $dp$ BEGIN
3105
+ EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON SEQUENCES TO anon$stmt$;
3106
+ EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
3107
+ RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON SEQUENCES TO anon$stmt$;
3108
+ END $dp$;
3109
+
3110
+ DO $dp$ BEGIN
3111
+ EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON SEQUENCES TO authenticated$stmt$;
3112
+ EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
3113
+ RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON SEQUENCES TO authenticated$stmt$;
3114
+ END $dp$;
3115
+
3116
+ DO $dp$ BEGIN
3117
+ EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON SEQUENCES TO service_role$stmt$;
3118
+ EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
3119
+ RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON SEQUENCES TO service_role$stmt$;
3120
+ END $dp$;
3121
+
3122
+ DO $dp$ BEGIN
3123
+ EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON FUNCTIONS TO postgres$stmt$;
3124
+ EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
3125
+ RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON FUNCTIONS TO postgres$stmt$;
3126
+ END $dp$;
3127
+
3128
+ DO $dp$ BEGIN
3129
+ EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON FUNCTIONS TO authenticated$stmt$;
3130
+ EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
3131
+ RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON FUNCTIONS TO authenticated$stmt$;
3132
+ END $dp$;
3133
+
3134
+ DO $dp$ BEGIN
3135
+ EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON FUNCTIONS TO service_role$stmt$;
3136
+ EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
3137
+ RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON FUNCTIONS TO service_role$stmt$;
3138
+ END $dp$;
3139
+
3140
+ DO $dp$ BEGIN
3141
+ EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON FUNCTIONS TO postgres$stmt$;
3142
+ EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
3143
+ RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON FUNCTIONS TO postgres$stmt$;
3144
+ END $dp$;
3145
+
3146
+ DO $dp$ BEGIN
3147
+ EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON FUNCTIONS TO anon$stmt$;
3148
+ EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
3149
+ RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON FUNCTIONS TO anon$stmt$;
3150
+ END $dp$;
3151
+
3152
+ DO $dp$ BEGIN
3153
+ EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON FUNCTIONS TO authenticated$stmt$;
3154
+ EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
3155
+ RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON FUNCTIONS TO authenticated$stmt$;
3156
+ END $dp$;
3157
+
3158
+ DO $dp$ BEGIN
3159
+ EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON FUNCTIONS TO service_role$stmt$;
3160
+ EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
3161
+ RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON FUNCTIONS TO service_role$stmt$;
3162
+ END $dp$;
3163
+
3164
+ DO $dp$ BEGIN
3165
+ EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON TABLES TO postgres$stmt$;
3166
+ EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
3167
+ RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON TABLES TO postgres$stmt$;
3168
+ END $dp$;
3169
+
3170
+ DO $dp$ BEGIN
3171
+ EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT MAINTAIN ON TABLES TO anon$stmt$;
3172
+ EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
3173
+ RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT MAINTAIN ON TABLES TO anon$stmt$;
3174
+ END $dp$;
3175
+
3176
+ DO $dp$ BEGIN
3177
+ EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON TABLES TO authenticated$stmt$;
3178
+ EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
3179
+ RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON TABLES TO authenticated$stmt$;
3180
+ END $dp$;
3181
+
3182
+ DO $dp$ BEGIN
3183
+ EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON TABLES TO service_role$stmt$;
3184
+ EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
3185
+ RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON TABLES TO service_role$stmt$;
3186
+ END $dp$;
3187
+
3188
+ DO $dp$ BEGIN
3189
+ EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON TABLES TO postgres$stmt$;
3190
+ EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
3191
+ RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON TABLES TO postgres$stmt$;
3192
+ END $dp$;
3193
+
3194
+ DO $dp$ BEGIN
3195
+ EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON TABLES TO anon$stmt$;
3196
+ EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
3197
+ RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON TABLES TO anon$stmt$;
3198
+ END $dp$;
3199
+
3200
+ DO $dp$ BEGIN
3201
+ EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON TABLES TO authenticated$stmt$;
3202
+ EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
3203
+ RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON TABLES TO authenticated$stmt$;
3204
+ END $dp$;
3205
+
3206
+ DO $dp$ BEGIN
3207
+ EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON TABLES TO service_role$stmt$;
3208
+ EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
3209
+ RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON TABLES TO service_role$stmt$;
3210
+ END $dp$;
3211
+
3212
+ DO $dp$ BEGIN
3213
+ EXECUTE $stmt$--
3214
+ -- PostgreSQL database dump complete
3215
+ --$stmt$;
3216
+ EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
3217
+ RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$--
3218
+ -- PostgreSQL database dump complete
3219
+ --$stmt$;
3220
+ END $dp$;
3132
3221
  `;
3222
+ var MIGRATIONS = [
3223
+ { id: "000_baseline", sql: MIGRATION_000_BASELINE }
3224
+ ];
3133
3225
  var STATUS_LABELS = {
3134
3226
  recording: "Gravando",
3135
3227
  paused: "Pausado",
@@ -3576,14 +3668,15 @@ function createScribePlugin(options) {
3576
3668
  ]
3577
3669
  }
3578
3670
  ],
3579
- migrations: [
3580
- {
3581
- id: "scribe-001-base",
3582
- version: "0.1.0",
3583
- sql: MIGRATION_001_SCRIBE_BASE,
3584
- description: "Cria plg_scribe_sessions/_segments/_generations, o bucket privado scribe-audio com guard de caminho, e o RPC scribe_record_segment"
3585
- }
3586
- ],
3671
+ // One baseline per unit since #338 — read from the generated barrel
3672
+ // rather than listed here, so regenerating the SQL never needs a
3673
+ // manifest edit to match.
3674
+ migrations: MIGRATIONS.map((entry) => ({
3675
+ id: `scribe-${entry.id}`,
3676
+ version: "1.0.0",
3677
+ sql: entry.sql,
3678
+ description: "The whole schema this plugin provisions, as one file: every table, view, function, policy and grant, plus the reference rows it seeds. Replaced the per-change chain in the 1.0.0 squash (#338); the files it collapsed are kept, unapplied, in src/migrations.archive/."
3679
+ })),
3587
3680
  locales: scribeLocales
3588
3681
  };
3589
3682
  }