Neuroredes
Plataforma

Backend (Supabase)

Schema mydb, edge functions deployadas, padrão RPC, migrations versionadas e automações Postgres. É a contraparte server-side das Lambdas (AWS): tudo que mora no Supabase e sustenta o produto.

Capítulo 01

Onde mora o módulo

O lado Supabase do Neuroredes tem três pilares: o schema mydb com as tabelas da aplicação, as edge functions em supabase/functions/ e as RPCs em rpc/. Tudo que não cabe em Lambda nem em Server Component vive aqui.

Diretórios no repositório

supabase/functions/<nome>/index.ts
Edge functions versionadas — Deno + TypeScript. Hoje, 30 funções no repo contra 49 deployadas (ver Capítulo 08).
supabase/migrations/*.sql
Migrations versionadas. 17 arquivos cobrindo mudanças posteriores ao reset do versionamento. Migrations antigas (criação inicial de mydb.*, RPCs históricas) não estão rastreáveis no repo.
rpc/<domínio>/*.ts
Wrappers de supabase.rpc() direto, sem passar por edge function. Domínios: surveys, companies, surveyors, viewers. Detalhe no Capítulo 04.
lib/supabase/{client,server,middleware}.ts
Três variantes do @supabase/ssr usadas pelo Next.js. Coberto em Autenticação — Capítulo 05.

Conexões com o resto da stack

  • Next.js → Supabase: Server Components leem perfil do cookie e chamam ou RPCs (rpc/*) ou edges (via services/*.client.service.ts e services/*.service.ts).
  • Lambdas → Supabase: as cinco Lambdas em ../lambda/ escrevem em mydb usando a SERVICE_ROLE_KEY guardada no AWS Secrets Manager. Ver Lambdas — Capítulo 05.
  • Edge → Lambda: a edge whatsapp-send-bulk publica em SQS, consumida por bulk-whatsapp-consumer (Lambda).

Dois schemas, papéis distintos

mydb é onde a aplicação vive — tabelas de pesquisa, identidade, mensagens. public é a interface da Data API (PostgREST e GraphQL): hospeda funções RPC, três views que projetam tabelas de mydb (public.surveys, public.survey_respondents, public.survey_responses) e duas tabelas próprias (public.relatorios, public.survey_cache). Detalhamento no Capítulo 02.

Capítulo 02

Schema mydb

Vinte e cinco tabelas no total, divididas em três blocos: aplicação ativa, mensagens/envio em massa e legado do Laravel. A coluna “Página” aponta o capítulo canônico — quando aplicável, backend.html só lista; a profundidade fica na página de origem.

Pesquisa

Tabela RLS Propósito
mydb.surveysSimCabeçalho da pesquisa, perguntas e status. Pesquisas — Cap. 02.
mydb.survey_attributesSimNomes dos sete atributos categorizadores da pesquisa.
mydb.survey_respondentsSimPessoas que respondem. Valores de atributo + flags de envio.
mydb.survey_responsesSimRespostas. PK composta (respondent_id, evaluatee_id).
mydb.survey_respondent_tokensNãoMagic links: hash do token, expiração, revogação. Envio.
mydb.survey_processing_resultsSimCache atual de Neurocalc e grafo, com processing_status.
mydb.survey_processing_logSimHistórico de processamentos.
mydb.survey_cacheSimCache legado. Lambdas HTTP ainda gravam aqui — ver Lambdas — Cap. 08.

Identidade e empresa

Detalhe completo em Autenticação — Capítulo 02. Resumo: papel é derivado de em qual das três tabelas-perfil o supabase_auth_id aparece.

Tabela RLS Propósito
mydb.usersSimTabela-perfil de ADMIN.
mydb.surveyorsSimTabela-perfil de SURVEYOR, com company_id.
mydb.survey_viewersSimTabela-perfil de VIEWER, com survey_id.
mydb.companiesSimOrganizações. CRUD via companies-crud e RPCs de empresa.

Envio em massa e mensagens

Tabela RLS Propósito
mydb.bulk_email_batchesNãoBatches de envio de email. Coberto em Lambdas — Cap. 04.
mydb.bulk_whatsapp_batchesNãoBatches de envio de WhatsApp.
mydb.survey_messagesNãoCabeçalho da campanha de envio do ponto de vista do produto.
mydb.survey_message_respondentsNãoStatus por destinatário, por canal.

Laravel legacy

Oito tabelas remanescentes da aplicação Laravel anterior ao Supabase. Confirmado via pg_stat_user_tables: zero linhas e zero atividade desde o cutover. Têm RLS habilitada com policy apenas para service_role, então não vazam pela Data API, mas continuam ocupando o catálogo.

TabelaFunção original (Laravel)
mydb.cache e mydb.cache_locksCache framework-level.
mydb.jobs, mydb.job_batches, mydb.failed_jobsFila de jobs do Laravel.
mydb.sessionsSessões web.
mydb.password_reset_tokens, mydb.personal_access_tokensTokens de senha e Sanctum.
mydb.migrationsTracking de migrations do Laravel — distinto do tracking do Supabase.

Schema public

public é a interface da Data API. Além das funções descritas no Capítulo 06, contém:

Views public.surveys, public.survey_respondents, public.survey_responses
Projeções 1:1 (sem WHERE/JOIN) das tabelas de mydb. São updatable por padrão — é por isso que public.close_expired_surveys() consegue rodar UPDATE public.surveys diretamente.
public.relatorios
Índice de relatórios gerados: id uuid, nome_arquivo text, gerado_em timestamptz. Distinto do bucket de Storage homônimo — ver Capítulo 07.
public.survey_cache
Par de mydb.survey_cache exposto via Data API. Tem a única RLS realmente escopada do projeto: a policy survey_cache_select_consolidated faz JOIN com as três tabelas-perfil pra liberar leitura só pra quem pertence à pesquisa.
Capítulo 03

Edge Functions deployadas

Quarenta e nove edge functions ativas no projeto Supabase (bcrxccejmzgwylfyetle) em 2026-05-21. Trinta delas estão versionadas em supabase/functions/; as outras ~19 vivem só no deploy — quem precisar do código real usa mcp__supabase__get_edge_function. A divergência completa está no Capítulo 08.

Esta seção agrupa as funções por propósito. Os detalhes de cada fluxo (payload, validação, side-effects) pertencem às páginas dos módulos que as consomem.

CRUD e escrita de domínio

Doze funções que cobrem criação, edição e exclusão das entidades principais. As de pesquisa (create-survey, edit-survey, delete-survey, close-survey) têm fluxo detalhado em Pesquisas — Capítulo 05.

Função O que faz
companies-crudOperações de organização. Substituídas em leitura por rpc/companies/*; escrita ainda passa pela edge.
surveyors-crud, viewers-crudAtualização e exclusão das tabelas-perfil. Listagem usa RPC.
create-user-surveyor, create-user-viewerConvite + insert na tabela-perfil. Precisam de auth.admin.inviteUserByEmail. Auth — Cap. 04.
create-respondent, update-respondent, delete-respondentCRUD de respondentes de uma pesquisa.
create-survey, edit-survey, delete-survey, close-surveyCiclo de vida da pesquisa, incluindo a cascata de exclusão em sete passos.

Magic link e respondente público

Seis funções que sustentam o fluxo de resposta sem login. Detalhe em Envio (Magic Links) e Resposta Pública.

  • magic-link-send — envia link individual por email.
  • magic-link-send-bulk — versão em lote. Hoje envia direto por SMTP (Brevo), não enfileira em SQS — ver Lambdas — Cap. 08.
  • magic-link-validate — valida o hash SHA-256 do token contra mydb.survey_respondent_tokens.
  • magic-link-revoke — marca revoked_at = NOW().
  • get-respondents — listagem para o frontend autenticado.
  • save-responses — gravação das respostas do formulário público.

Envio em massa e status

  • whatsapp-send-bulk — produtor SQS. Cria batch, gera tokens, enfileira em lotes de 10.
  • get-bulk-email-status — consulta de progresso para a UI.

Análise legado (fallback das Lambdas HTTP)

Onze funções que respondem dashboard, grafo e Excel quando as Lambdas HTTP não estão configuradas: survey-handler, survey-handler-front, calc-new, calc-new-front, graph, graph-handler, graph-complete, excel, export-excel, download-excel-from-bucket, list-excel-files. Em produção, a UI lê a URL da Lambda equivalente em NEXT_PUBLIC_*_PROCESSOR_URL e usa a edge como fallback documentado. Detalhe em Lambdas — Cap. 02.

Neurocalc e experimentação

Cerca de dezenove funções que pertencem ao motor do Neurocalc ou a cenários de experimentação (neurocalc-*, grafo-results, survey-results-query, save-graph-cache, save-neurocalc-cache, save-report, get-dataset, get-research, get-post, create-test-survey). Não estão no caminho principal do produto. Várias não têm código versionado no repo. Ver Neurocalc para a fronteira de responsabilidade desta doc.

Identidade

  • get-profile — chamada por /verify-user para resolver papel após login. Desde 2026-06-10 deriva a identidade do JWT (ignora userId do corpo) e devolve o perfil do próprio chamador. Auth — Cap. 03.

Flag verify_jwt

Quando uma edge tem verify_jwt: false, o Supabase aceita a chamada sem JWT — a validação do chamador é responsabilidade exclusiva do código da edge. Quando está em true, o gateway rejeita qualquer requisição sem um JWT de usuário válido antes de ela chegar ao código.

Atualizado 2026-06-10

As 30 funções do repo passaram por um endurecimento de auth (commit 98deda8 na branch dev). Hoje, das 30, apenas 7 mantêm verify_jwt: false — todas chamadas sem um JWT de usuário. As outras 23 estão em verify_jwt: true e derivam a identidade do chamador do próprio JWT via _shared/auth.ts (getAuthedUsergetUser(token) + resolveRole), com autorização deny-by-default. O profile/userId do corpo da requisição não é mais confiável para autorização.

As 7 funções do repo que continuam em verify_jwt: false:

Edge Por que é isenta
magic-link-validate, save-responsesFluxos públicos no browser, sem JWT de usuário — protegidos pelo token do magic link. Ver Resposta Pública.
excel, neurocalc, graph-handler, graph-complete, survey-handlerCallbacks máquina-a-máquina das Lambdas AWS — não enviam JWT de usuário nem Origin. Ver Lambdas.

As edges sensíveis que antes validavam o papel manualmente a partir do corpo (companies-crud, surveyors-crud, viewers-crud, create-user-surveyor, create-user-viewer, create-full-user, get-profile, o CRUD de pesquisa e de respondentes, os magic-link-* de envio/revogação e as funções de leitura) agora estão todas em verify_jwt: true e resolvem identidade + escopo pelo JWT. Histórico do vetor antigo na seção Pegadinhas — Capítulo 08.

Atenção

Edge nova com verify_jwt: false e sem checagem manual é buraco de segurança imediato — qualquer cliente que conheça a URL invoca sem credencial. Padrão é manter o flag em true a menos que o caso de uso exija (frontend público token-seguro, ou callback de Lambda máquina-a-máquina).

CORS: allowlist de origem

O wildcard Access-Control-Allow-Origin: * que todas as edges usavam foi substituído por uma allowlist via o helper compartilhado _shared/cors.ts (buildCorsHeaders(req)). O helper espelha o Origin da requisição de volta como ACAO somente quando ele está na allowlist; caso contrário o header ACAO é omitido (o browser bloqueia a leitura cross-origin). As quatro origens permitidas:

  • http://localhost:3000
  • http://localhost:3001
  • https://pesquisa-neuroredes.vercel.app
  • https://neuroredes-pesquisa.vercel.app

Vary: Origin é sempre enviado (evita contaminação de cache). Callers servidor-a-servidor (Lambdas) não mandam Origin e não são afetados. Não confundir com o CORS das Lambda Function URLs, que é configurado no CDK/console AWS, não neste helper.

Deploy: config.toml como fonte única

O flag verify_jwt não é mais passado por comando (--no-verify-jwt por função). Ele vive em supabase/config.toml, com um bloco [functions.<nome>] verify_jwt = false apenas para as 7 funções isentas; o default é true. Isso elimina o risco de esquecer um flag em deploy individual. Todas as funções vão para produção em um comando, da pasta pesquisa/:

supabase functions deploy --project-ref eahqtrsdkqmjfsbmukqa

Sem nomes, sem flags. O código compartilhado fica em functions/_shared/ — o prefixo _ faz o CLI pular a pasta no deploy em massa (ela é empacotada em cada função que a importa, não deployada como função).

Capítulo 04

Padrão RPC

Edge functions cobravam latência por dois saltos (rede + função Deno + RPC interna). O padrão atual é, quando possível, chamar a função Postgres diretamente do Server Component via supabase.rpc(), eliminando a edge intermediária. A regra de decisão está em docs/rpc-approach.md; o resumo abaixo é suficiente para o dia a dia.

Quando usar cada caminho

Operação Caminho Por quê
Listar pesquisas por papelRPCSem efeito externo. rpc/surveys/get-surveys.ts chama public.get_surveys_by_profile em uma ida ao Postgres.
CRUD de organização (listar)RPCrpc/companies/get-companies.ts. Idem.
CRUD de organização (criar/editar/excluir)RPCSem auth.admin nem efeito externo.
Convidar SURVEYOR ou VIEWEREdgePrecisa auth.admin.inviteUserByEmail com SERVICE_ROLE_KEY.
Excluir pesquisaEdgeCascata de sete passos com rollback aplicativo.
Enviar magic linksEdgeEfeito externo (SMTP ou SQS).
Gerar relatório ExcelLambdaComputação pesada e geração de signed URL. Lambdas — Cap. 02.

Estrutura do código

Cada RPC é um arquivo pequeno em rpc/<domínio>/<verbo>-<substantivo>.ts com uma função exportada <verboSubstantivo>Rpc:

rpc/companies/get-companies.ts (esqueleto) typescript
import { createClient } from '@/lib/supabase/server';

export async function getCompaniesRpc(params: GetCompaniesParams) {
  const supabase = await createClient();
  const { data, error } = await supabase.rpc('get_companies_paginated', {
    p_schema: 'mydb',
    p_search: params.search,
    p_page: params.page,
    p_per_page: params.perPage,
  });
  if (error) throw new Error(error.message);
  return data;
}

Os domínios já convertidos são surveys, companies, surveyors e viewers. Server Actions em app/(main)/<domínio>/actions.ts chamam essas funções e disparam revalidatePath quando precisam invalidar o cache do Next.js.

Para o exemplo canônico de despacho por papel em getSurveysRpc (ADMIN → get_all_surveys, SURVEYOR → get_surveys_by_company, VIEWER → get_survey_by_id), ver Pesquisas — Capítulo 05.

Capítulo 05

Migrations & Data API Grants

Dezessete migrations versionadas em supabase/migrations/, todas datadas a partir de outubro/2025. A consulta mcp__supabase__list_migrations devolve exatamente essas dezessete — ou seja, o tracking interno do Supabase só conhece o que foi versionado depois do reset.

Versão Migração
20251027221300create_get_survey_attributes_rpc
202512011430add_magic_link_rpcs
20260119171501add_end_date_to_magic_get_survey_payload
202601221000add_bulk_email_batches
202601241200fix_magic_update_email_sent
202602051430add_bulk_whatsapp_batches
20260310120000fix_security_advisor_errors
20260310120100fix_search_path_warnings
20260310120200fix_overly_permissive_rls_policies
20260313120000invalidate_graph_cache_on_data_change
20260316120000fix_disk_io_performance
20260407120000auto_close_expired_surveys
20260407150000reduce_disk_io
20260413185612add_survey_messages
20260430203459reset_failed_message_respondents
20260430204929fix_reset_failed_schema
20260513191724preventive_explicit_grants_data_api
Atenção

A criação inicial das tabelas em mydb, as RPCs antigas e parte das edge functions deployadas não têm migration rastreável: arquivos foram perdidos antes do reset. Rodar Postgres local só com as 17 migrations do repo não reconstrói o schema atual. A consulta direta ao banco em produção via Supabase MCP é a fonte de verdade.

Padrão obrigatório de Data API Grants

Em 30/10/2026, o Supabase muda o default da Data API: tabelas, sequences e views novas em public deixam de ser auto-expostas via PostgREST/GraphQL sem GRANT explícito para anon, authenticated e service_role. A migration 20260513191724 adota o novo comportamento antes do prazo, revogando os defaults para que migrations sem grant falhem rápido em desenvolvimento.

migration boilerplate sql
create table public.foo ( ... );

grant select, insert, update, delete, truncate, references, trigger
  on public.foo to anon, authenticated, service_role;

-- se houver sequence (serial/bigserial)
grant usage, select on sequence public.foo_id_seq
  to anon, authenticated, service_role;

alter table public.foo enable row level security;

create policy "..." on public.foo for select to authenticated using (...);

Se a tabela não deve ser exposta na Data API, manter o grant apenas para service_role. RLS continua sendo a barreira por linha — grant é a barreira de acesso à tabela. O schema mydb tem grants per-tabela intencionais (por exemplo, survey_respondent_tokens restrito a service_role) e não cai sob a regra do public, mas mudanças lá seguem o mesmo padrão.

Capítulo 06

pg_cron, triggers & funções Postgres

pg_cron

Único job ativo: close-expired-surveys. A migration 20260407120000_auto_close_expired_surveys.sql criou a função public.close_expired_surveys() e a agendou com cron.schedule('close-expired-surveys', '*/5 * * * *', ...). Hoje, a tabela cron.job reporta a schedule como */15 * * * * — alteração feita pós-migration, sem arquivo correspondente.

public.close_expired_surveys() sql
UPDATE public.surveys
   SET status = 3,
       updated_at = NOW()
 WHERE status <> 3
   AND end_date IS NOT NULL
   AND end_date <= NOW();

A função opera sobre a view public.surveys, que é updatable por projetar mydb.surveys 1:1. Sem efeito colateral além da mudança de status: dados intactos, pesquisa segue acessível para leitura, relatório e dashboard.

Triggers

Dois triggers, ambos disparando mydb.invalidate_survey_cache() (apaga as linhas correspondentes em mydb.survey_cache):

Trigger Disparo
trigger_invalidate_cache_respondents em mydb.survey_respondentsAFTER INSERT, UPDATE, DELETE
trigger_invalidate_cache em mydb.survey_responsesAFTER INSERT, UPDATE, DELETE

O efeito: qualquer mudança em respondentes ou respostas zera o cache legado da pesquisa, forçando o recálculo no próximo acesso ao dashboard ou grafo. O cache novo (mydb.survey_processing_results) não é alvo dos triggers — segue a regra de invalidação aplicativa descrita no Cap. 08 das Lambdas.

Funções SECURITY DEFINER

A maioria das funções em public.* e mydb.* é marcada como SECURITY DEFINER: a execução acontece com privilégios do owner (tipicamente postgres ou supabase_admin), bypassando RLS. Por isso as RPCs funcionam quando o cliente usa apenas a chave anon: a checagem real de quem pode ver o quê fica dentro da função, via parâmetros como p_role e p_company_id.

Famílias de funções (sem enumerar todas as ~85):

  • get_surveys_*, get_all_surveys, get_surveys_by_company, get_survey_by_id — listagem de pesquisas.
  • magic_* — criação, validação, revogação de tokens de magic link (magic_create_token, magic_get_token_record, magic_revoke_tokens, etc.).
  • bulk_email_* e bulk_whatsapp_* — criação de batch, incremento de contadores.
  • survey_messages_* e survey_message_respondents_* — campanhas e status por destinatário.
  • get_companies_paginated, create_company, update_company, delete_company, get_surveyors_paginated, update_surveyor_profile, delete_surveyor_profile, get_viewers_paginated, update_viewer_profile, delete_viewer_profile — CRUDs.
Capítulo 07

Storage

Dois buckets configurados no projeto:

Bucket Público Uso
relatorios Não Planilhas Excel geradas pela Lambda excel-processor e datasets brutos JSON gravados pela Lambda dashboard-processor. Leitura via signed URL de 3600 s. Detalhe em Lambdas — Capítulo 02.
assets Sim Bucket de propósito geral. Tem dois objetos hoje; uso não documentado em código rastreável.

Ao lado do bucket relatorios existe a tabela public.relatorios (id uuid, nome_arquivo text, gerado_em timestamptz) — índice em SQL dos arquivos gerados. Coincidência de nome com o bucket é proposital: o bucket guarda o arquivo, a tabela guarda o catálogo.

Capítulo 08

Pegadinhas & alertas

Divergência entre repo e deploy de edges

Trinta funções estão em supabase/functions/ (todas deployadas); o total deployado é 52 (consulta MCP em 2026-06-10). As ~22 ausentes do repo cobrem Neurocalc experimental (neurocalc-*), fallbacks de grafo (graph, save-graph-cache, grafo-results) e edges legadas (save-report, survey-results-query, calc-new, get-post, create-test-survey, …). Para ler o código real em produção, usar mcp__supabase__get_edge_function com o slug.

Atenção

O config.toml só governa o verify_jwt das 30 funções do repo. As edges deployadas-mas-sem-código-no-repo não são alcançadas por ele — entre elas, 11 ainda estão em verify_jwt: false (save-report, graph, survey-results-query, save-graph-cache, calc-new, get-post, neurocalc-results, create-test-survey, calc-new-front, survey-handler-front, grafo-results). São legado de experimentação fora do caminho principal, mas continuam acessíveis sem JWT. Confirmar o flag real sempre via list_edge_functions, não pelo repo.

Nota histórica: companies-crud, surveyors-crud e create-user-surveyor já foram catalogadas aqui como “deployadas mas sem código no repo”. Desde o endurecimento de 2026-06-10 elas vivem em supabase/functions/, em verify_jwt: true.

Cinco tabelas operacionais sem RLS

mydb.survey_respondent_tokens, mydb.bulk_email_batches, mydb.bulk_whatsapp_batches, mydb.survey_messages e mydb.survey_message_respondents. Edge functions usam SERVICE_ROLE_KEY e validam manualmente, mas qualquer acesso direto via anon key ignora as validações. Vetor já marcado nos “Alertas de segurança pendentes” do ROADMAP.

RLS habilitada não é RLS escopada

Algumas tabelas em mydb têm RLS habilitada mas a única policy de SELECT é auth.role() = 'authenticated', sem filtro de ownership — por exemplo, mydb.surveys, mydb.users, mydb.companies. Qualquer usuário autenticado consegue ler a tabela inteira via supabase.schema('mydb').from(...).select(). A defesa real do produto vem das RPCs SECURITY DEFINER que recebem p_role/p_company_id e das edges que rodam com service_role e validam o payload. O aprofundamento desse mapa fica como errata futura em Autenticação & Papéis.

public.relatorios com policy aberta para anon

A tabela tem RLS habilitada, mas a única policy é using = true em SELECT para a role anon. Efetivamente leitura aberta para qualquer cliente com a anon key pública.

verify_jwt: false em edges sensíveis

Resolvido 2026-06-10

As quatro edges de identidade que motivaram este alerta (companies-crud, surveyors-crud, create-user-surveyor e get-profile) foram endurecidas: agora rodam em verify_jwt: true e derivam a identidade do JWT via _shared/auth.ts, com autorização deny-by-default — o profile/userId do corpo deixou de ser confiável (commit 98deda8).

Histórico do vetor. Originalmente ~20 edges deployadas tinham o flag desabilitado e validavam papel manualmente a partir do profile no payload — o contrato dependia de cada edge fazer a checagem certa, sem rede a partir do Supabase, e a anon key pública bastava para invocá-las. Hoje, das 30 funções do repo, só 7 mantêm verify_jwt: false (fluxos públicos token-seguros e callbacks de Lambda — ver Capítulo 03). O resíduo de risco está nas ~11 edges legadas fora do repo que continuam em false (catalogadas acima em “Divergência entre repo e deploy”). Edge nova sem JWT e sem validação continua sendo buraco imediato.

Cron */5 na migration, */15 no banco

A migration 20260407120000_auto_close_expired_surveys.sql agendou o job a cada cinco minutos; o banco hoje roda a cada quinze. A alteração foi feita direto na cron.job sem migration de follow-up, então recriar o banco a partir do repo restabelece o schedule original. A página Pesquisas — Capítulo 03 afirma “cinco minutos” — vai precisar de errata.

Seis sobrecargas de get_surveys_by_profile

A função public.get_surveys_by_profile existe em seis assinaturas diferentes (variações de tipo e ordem de parâmetros). Sinal de cleanup pendente: chamar uma assinatura específica é frágil porque o roteamento entre sobrecargas depende dos tipos passados. Hoje a RPC getSurveysRpc despacha para get_all_surveys, get_surveys_by_company ou get_survey_by_id, evitando o problema. Funções nova em rpc/ devem reaproveitar essas três e não criar sobrecarga adicional.

Tabelas Laravel residuais no mydb

Oito tabelas (cache, cache_locks, failed_jobs, job_batches, jobs, migrations, password_reset_tokens, personal_access_tokens, sessions) continuam no schema sem uso — confirmado por contagem zero e ausência de escrita em pg_stat_user_tables. RLS só permite service_role, então não vazam pela Data API, mas poluem o catálogo. mydb.migrations em particular confunde quem espera tracking do Supabase — esse fica em supabase_migrations.schema_migrations.