Skip to content

fix(db): guard DDL on out-of-band (orphan) tables for clean replay#333

Closed
adm01-debug wants to merge 1 commit into
mainfrom
claude/replay-drift-tables
Closed

fix(db): guard DDL on out-of-band (orphan) tables for clean replay#333
adm01-debug wants to merge 1 commit into
mainfrom
claude/replay-drift-tables

Conversation

@adm01-debug
Copy link
Copy Markdown
Owner

@adm01-debug adm01-debug commented May 25, 2026

Audit found only 5 post-baseline migrations doing DDL on tables that exist in prod but were never created by a migration (created out-of-band). Of those, t38 already self-guards (IF EXISTS) and 20260519163704 was a false positive (tags is a column of products). The 3 real blockers are guarded so a fresh replay no-ops on the missing table instead of aborting; production (where the tables exist) behaves identically:

  • onda13: CREATE POLICY on audit_log_gravacao / seo_audit_log -> wrapped in to_regclass(...) IS NOT NULL guards.
  • onda19: DROP/CREATE TRIGGER + ALTER on kit_component_print_areas, supplier_technique_mappings, tabela_preco_gravacao_oficial, variant_supplier_sources, and the view reading tabela_preco_gravacao_oficial -> wrapped in EXCEPTION WHEN undefined_table guards (non-orphan ALTERs left intact).
  • harden_anon_graphql_exposure: all REVOKE ... FROM anon wrapped (idempotent; no-op when the table is absent).

https://claude.ai/code/session_01MBTzmQYmrgwLnwfxRS3PNU

📋 Descrição

🎯 Tipo de mudança

  • 🚀 feat — nova funcionalidade
  • 🐛 fix — correção de bug
  • ♻️ refactor — refatoração (sem mudança de comportamento)
  • 🔧 chore — manutenção, deps, config
  • 📚 docs — documentação
  • ⚡ perf — performance
  • 🔒 security — segurança
  • 🚨 hotfix — correção urgente em produção
  • 💥 breaking change — quebra compatibilidade

🔗 Issues relacionadas

Closes #
Refs #

🌐 Sistemas afetados

  • Bitrix24 (CRM, SPAs, BizProc)
  • Supabase (DB, Edge Functions, RLS, migrations)
  • n8n (workflows)
  • Evolution API / WhatsApp
  • Bling (NFe, OAuth)
  • Cloudflare (Workers, Images, Tunnels)
  • Frontend (UI, dashboards)
  • CI / GitHub Actions
  • Outro: ____

🧪 Como testar

✅ Checklist pré-merge

Qualidade

  • Código segue style guide (ESLint passa)
  • npx tsc --noEmit passa sem erros
  • Testes passam (npm run test)
  • Adicionei testes para novas funcionalidades quando aplicável
  • CodeRabbit revisou o PR (ou justificativa para skip)

Segurança

  • Sem secrets, tokens ou credenciais hardcoded
  • Variáveis de ambiente novas documentadas
  • Sem console.log com payloads sensíveis (usar logger.*)
  • RLS revisado se houve mudança em tabelas
  • Edge functions: input validado com Zod

Documentação

  • Atualizei docs (README / CHANGELOG / docs/) se necessário
  • Memória atualizada (mem://) se a mudança afetar arquitetura/regras
  • Migrations com backup em _backup_*_YYYYMMDD se destrutivas

UI

  • Componentes usam tokens semânticos (sem cores hardcoded)
  • Screenshots / vídeo anexados (se mudança visual)

📸 Screenshots (se UI)

🔄 Plano de rollback

⚠️ Notas para o reviewer


Summary by cubic

Guards DDL in post-baseline migrations so a fresh DB replay no-ops on missing out-of-band tables instead of failing. Production behavior is unchanged.

  • Bug Fixes
    • onda13: Wrapped policy changes on audit tables with to_regclass(...) IS NOT NULL.
    • onda19: Wrapped ALTERs, trigger drops/creates, and view rebuilds that touch kit_component_print_areas, supplier_technique_mappings, tabela_preco_gravacao_oficial(_faixa), and variant_supplier_sources in DO ... EXCEPTION WHEN undefined_table blocks; non-orphan ALTERs left as-is.
    • harden_anon_graphql_exposure: Wrapped all REVOKE ... FROM anon statements in DO ... EXCEPTION WHEN undefined_table to stay idempotent.

Written for commit ba5a589. Summary will update on new commits. Review in cubic

Summary by CodeRabbit

  • Bug Fixes

    • Melhorada estabilidade das migrações de banco de dados em diferentes ambientes.
    • Corrigida tolerância a tabelas ausentes durante atualizações.
  • Chores

    • Reforçadas políticas de segurança para logs de auditoria.
    • Endurecida proteção contra exposição de dados em acessos não autenticados.

Review Change Stack

Audit found only 5 post-baseline migrations doing DDL on tables that exist in
prod but were never created by a migration (created out-of-band). Of those,
t38 already self-guards (IF EXISTS) and 20260519163704 was a false positive
(`tags` is a column of products). The 3 real blockers are guarded so a fresh
replay no-ops on the missing table instead of aborting; production (where the
tables exist) behaves identically:

- onda13: CREATE POLICY on audit_log_gravacao / seo_audit_log -> wrapped in
  to_regclass(...) IS NOT NULL guards.
- onda19: DROP/CREATE TRIGGER + ALTER on kit_component_print_areas,
  supplier_technique_mappings, tabela_preco_gravacao_oficial,
  variant_supplier_sources, and the view reading tabela_preco_gravacao_oficial
  -> wrapped in EXCEPTION WHEN undefined_table guards (non-orphan ALTERs left
  intact).
- harden_anon_graphql_exposure: all REVOKE ... FROM anon wrapped (idempotent;
  no-op when the table is absent).

https://claude.ai/code/session_01MBTzmQYmrgwLnwfxRS3PNU
Copilot AI review requested due to automatic review settings May 25, 2026 11:00
@supabase
Copy link
Copy Markdown

supabase Bot commented May 25, 2026

Updates to Preview Branch (claude/replay-drift-tables) ↗︎

Deployments Status Updated
Database Mon, 25 May 2026 11:01:17 UTC
Services Mon, 25 May 2026 11:01:17 UTC
APIs Mon, 25 May 2026 11:01:17 UTC

Tasks are run on every commit but only new migration files are pushed.
Close and reopen this PR if you want to apply changes from existing seed or migration files.

Tasks Status Updated
Configurations Mon, 25 May 2026 11:01:24 UTC
Migrations Mon, 25 May 2026 11:04:08 UTC
Seeding ⏸️ Mon, 25 May 2026 11:01:11 UTC
Edge Functions ⏸️ Mon, 25 May 2026 11:01:11 UTC

❌ Branch Error • Mon, 25 May 2026 11:04:09 UTC

ERROR: column q.assigned_to does not exist (SQLSTATE 42703)
At statement: 0
-- =================================================================
-- Onda 18a: Isolamento de orcamentos por vendedor (audit gap 6.1 redirecionado)
--
-- Regra de negocio (Joaquim PO):
--   - VENDEDOR/AGENTE: ve SO os PROPRIOS orcamentos (seller_id ou created_by ou assigned_to = self)
--   - SUPERVISOR/COORDENADOR: ve TODOS os orcamentos (via is_coord_or_above)
--   - ADMIN: ve tudo (incluido no is_coord_or_above por decisao Q1=A)
--   - DEV: ve tudo (incluido no is_coord_or_above)
--
-- Antes desta migration: qualquer membro da org via TODOS os orcamentos
-- (gap concreto: comercial03@/comercial05@ viam quotes do adm01@ que NAO eram suas)
--
-- Tabelas afetadas: quotes, quote_items, quote_comments, quote_versions
-- DELETE de quotes mantem is_org_owner_or_admin (controle org-level distinto)
-- INSERT de quotes mantem user_is_org_member (qualquer membro pode criar)
-- =================================================================

-- 1. NOVA FUNCAO: can_access_quote(quote_id)
--    Encapsula logica em SSOT, SECURITY DEFINER pra evitar recursao RLS
CREATE OR REPLACE FUNCTION public.can_access_quote(_quote_id uuid)
RETURNS boolean
LANGUAGE sql
STABLE
SECURITY DEFINER
SET search_path TO 'public'
AS $$
  SELECT EXISTS (
    SELECT 1 FROM public.quotes q
    WHERE q.id = _quote_id
      AND user_is_org_member(q.organization_id)
      AND (
        is_coord_or_above(auth.uid())
        OR q.seller_id  = auth.uid()
        OR q.created_by = auth.uid()
        OR q.assigned_to = auth.uid()
           ^

View logs for this Workflow Run ↗︎.
Learn more about Supabase for Git ↗︎.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 25, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1311ad94-fbd4-45b4-addf-fc131f039f4a

📥 Commits

Reviewing files that changed from the base of the PR and between a48f8b8 and ba5a589.

📒 Files selected for processing (3)
  • supabase/migrations/20260514220543_onda13_rls_audit_logs_admin_only.sql
  • supabase/migrations/20260515030000_onda19_numeric_precision.sql
  • supabase/migrations/20260524210100_harden_anon_graphql_exposure.sql

Walkthrough

PR introduz três migrações SQL que encapsulam operações DDL (políticas RLS, drops de triggers, alterações de colunas, revogações de grants) em blocos DO com tratamento de exceções ou condições de catálogo, garantindo idempotência em re-replays e ambientes com tabelas ausentes.

Changes

Hardening de migrações SQL contra ambientes de replay

Layer / File(s) Summary
RLS audit logs hardening (Onda 13)
supabase/migrations/20260514220543_onda13_rls_audit_logs_admin_only.sql
Políticas RLS para audit_log_gravacao e seo_audit_log envolvem drop/create em DO $g$ com checagem IF to_regclass(...) IS NOT NULL, aplicando restrição SELECT baseada em is_supervisor_or_above(auth.uid()) apenas quando tabelas existem, eliminando falhas em re-replays.
Onda 19 numeric precision & trigger/view recreation
supabase/migrations/20260515030000_onda19_numeric_precision.sql
Drop de trigger trg_kit_print_area_normalizar_eixos e alterações de tipos em markup_percent, confidence, supplier_ipi_rate, max_height, max_width encapsulados em blocos DO ... EXCEPTION WHEN undefined_table, incluindo recriação de trigger e view v_audit_paradoxos_gravacao com proteção contra tabelas ausentes.
Anon role GraphQL exposure hardening
supabase/migrations/20260524210100_harden_anon_graphql_exposure.sql
Revogações de SELECT da role anon migram para série de blocos DO individuais, cada um tratando EXCEPTION WHEN undefined_table para rollbacks tolerantes a tabelas ausentes.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related PRs

  • adm01-debug/promo-gifts-v4#184: Ambas as PRs ajustam DDL de RLS para ser idempotente em replays, encapsulando criação/alteração de políticas em DO $$ com checagens no catálogo (ex.: pg_policies/to_regclass) para evitar falhas por políticas/tabelas já existentes.

Suggested labels

codex

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed O título descreve com precisão a mudança principal: guardar DDL em tabelas órfãs para permitir replay limpo de migrações.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/replay-drift-tables

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Guards post-baseline Supabase migrations so a clean DB replay won’t abort when encountering DDL against “out-of-band” (orphan) tables that exist in prod but not in the migration history.

Changes:

  • Wrapped REVOKE ... FROM anon statements in DO ... EXCEPTION WHEN undefined_table blocks to no-op when target relations don’t exist.
  • Wrapped ALTER TABLE, trigger drop/create, and view rebuild steps (that touch orphan tables) in DO ... EXCEPTION WHEN undefined_table blocks for replay safety.
  • Wrapped audit log RLS policy changes in DO blocks gated by to_regclass(...) IS NOT NULL.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
supabase/migrations/20260524210100_harden_anon_graphql_exposure.sql Makes REVOKE SELECT ... FROM anon idempotent when the underlying relation is missing.
supabase/migrations/20260515030000_onda19_numeric_precision.sql Adds replay guards around orphan-table ALTER/TRIGGER/VIEW statements to prevent clean replay failures.
supabase/migrations/20260514220543_onda13_rls_audit_logs_admin_only.sql Adds to_regclass guards so policy DDL no-ops if the audit tables don’t exist in replay.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 116 to +120
-- 5. RECREATE view (definição idêntica à original — só rebuild para usar novo tipo)
CREATE OR REPLACE VIEW public.v_audit_paradoxos_gravacao AS
WITH faixas_ord AS (
SELECT t.id,
t.codigo_tabela,
t.nome,
t.grupo_tecnica,
t.custo_setup,
t.markup_percent,
f.quantidade_minima,
f.quantidade_maxima,
f.preco_unitario,
lag(f.preco_unitario) OVER (
PARTITION BY t.id,
(COALESCE(f.largura_min, 0::numeric)),
(COALESCE(f.largura_max, 0::numeric)),
(COALESCE(f.altura_min, 0::numeric)),
(COALESCE(f.altura_max, 0::numeric))
ORDER BY f.quantidade_minima
) AS preco_anterior,
lag(f.quantidade_maxima) OVER (
PARTITION BY t.id,
(COALESCE(f.largura_min, 0::numeric)),
(COALESCE(f.largura_max, 0::numeric)),
(COALESCE(f.altura_min, 0::numeric)),
(COALESCE(f.altura_max, 0::numeric))
ORDER BY f.quantidade_minima
) AS qty_max_anterior
FROM public.tabela_preco_gravacao_oficial t
JOIN public.tabela_preco_gravacao_oficial_faixa f
ON f.tabela_preco_gravacao_id = t.id
WHERE t.ativo = true
)
SELECT
codigo_tabela,
nome,
grupo_tecnica,
qty_max_anterior AS qty_pico_ant,
preco_anterior,
round(GREATEST(qty_max_anterior::numeric * preco_anterior, custo_setup) * (1::numeric + markup_percent / 100::numeric), 2) AS venda_no_pico,
quantidade_minima AS qty_inicio,
preco_unitario AS preco_atual,
round(GREATEST(quantidade_minima::numeric * preco_unitario, custo_setup) * (1::numeric + markup_percent / 100::numeric), 2) AS venda_inicio,
round(GREATEST(quantidade_minima::numeric * preco_unitario, custo_setup) - GREATEST(qty_max_anterior::numeric * preco_anterior, custo_setup), 2) AS economia_cliente_se_subir_faixa,
CASE
WHEN preco_anterior IS NULL THEN 'primeira_faixa'::text
WHEN qty_max_anterior IS NULL THEN 'sem_pico_anterior'::text
WHEN GREATEST(quantidade_minima::numeric * preco_unitario, custo_setup) < GREATEST(qty_max_anterior::numeric * preco_anterior, custo_setup) THEN 'PARADOXO_NATURAL'::text
ELSE 'OK'::text
END AS status_natural
FROM faixas_ord
WHERE preco_anterior IS NOT NULL AND qty_max_anterior IS NOT NULL
ORDER BY codigo_tabela, quantidade_minima;
-- Guardada: referencia tabela_preco_gravacao_oficial(_faixa) (out-of-band).
DO $g$ BEGIN
CREATE OR REPLACE VIEW public.v_audit_paradoxos_gravacao AS
WITH faixas_ord AS (
@adm01-debug
Copy link
Copy Markdown
Owner Author

🎯 PLANO MASTER — coordenação das 5 PRs abertas

Bloco mantido idêntico em #327, #331, #332, #333 para preservar contexto entre sessões de chat.

Decisões cravadas (25/05/2026)

# Decisão Motivo
#326 ❌ FECHAR sem merge Superset coberto pelo #332 (mesmo fix DistributiveOmit em bridge-status-events.ts + mesmo fix em kill-switch-client.test.ts + baselines TS/ESLint)
#333 1️⃣ Mergear PRIMEIRO CodeRabbit já aprovou (5 checks ✅, "No actionable comments 🎉"); base é a main atual
#332 2️⃣ Mergear EM SEGUIDA P0 — MFA bypass real em /admin/* e /dev/* em produção; só toca src/**, sem conflito com migrations
#327 3️⃣ Mergear DEPOIS Resolve conflito de baseline contra #332 uma única vez
#331 4️⃣ Mergear POR ÚLTIMO Em draft, depende de baselines TS/ESLint estabilizados; nada crítico
#334 🆕 ABRIR após #333 Coluna órfã quotes.assigned_to (3ª classe de drift, descoberta no preview do #333 com erro 42703 em onda18a_isolamento_orcamentos_vendedor)

Estado atual

Topologia do drift de replay (Facet 2)

Sequência operacional após este comentário

  1. Fechar fix(ci): repair TypeScript gate (decode base64 supabase-untyped, distributive Omit) #326 com nota de duplicação
  2. Rebase fix(db): guard DDL on out-of-band (orphan) tables for clean replay #333 (trivial, +1 commit) → merge
  3. Rebase fix(qa): correção de 11 bugs reais — MFA bypass, fail-open RBAC, carrinho, autosave + 7 #332 em main+fix(db): guard DDL on out-of-band (orphan) tables for clean replay #333 → sair de draft → @coderabbitai review → merge
  4. Rebase fix(db): replay resiliente — guards nos ALTER FUNCTION dos t37b1/b2 (Facet 2, classe funções) #327 em main+fix(qa): correção de 11 bugs reais — MFA bypass, fail-open RBAC, carrinho, autosave + 7 #332+fix(db): guard DDL on out-of-band (orphan) tables for clean replay #333@coderabbitai review → merge
  5. Rebase feat(tests): suíte E2E + integração edge functions + fuzz expandido + coverage por módulo #331 em main+fix(db): replay resiliente — guards nos ALTER FUNCTION dos t37b1/b2 (Facet 2, classe funções) #327+fix(qa): correção de 11 bugs reais — MFA bypass, fail-open RBAC, carrinho, autosave + 7 #332+fix(db): guard DDL on out-of-band (orphan) tables for clean replay #333 → sair de draft → @coderabbitai review → merge
  6. Abrir fix(db): backfill 4 orphan migration versions to repair prod integration #334 com fix para drift de colunas

🤖 Coordenação: Claude (sessão 25/05/2026). Quando reentrar em outra sessão, leia este bloco primeiro antes de fazer qualquer mudança — ele é a fonte da verdade.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 25, 2026

Your plan includes 5 reviews of capacity. Refill in 235 minutes and 7 seconds.

@adm01-debug
Copy link
Copy Markdown
Owner Author

🚪 Fechando esta PR — duplicada do #313 já mergeado em main

Evidência forense

Validação no código (origin/main @ caad622)

$ grep -c "to_regclass\|EXCEPTION WHEN undefined_table" supabase/migrations/20260514220543_onda13_*.sql
3
$ grep -c "to_regclass\|EXCEPTION WHEN undefined_table" supabase/migrations/20260515030000_onda19_*.sql
7
$ grep -c "to_regclass\|EXCEPTION WHEN undefined_table" supabase/migrations/20260524210100_harden_anon_*.sql
27

Os 3 arquivos alvo desta PR já estão em main com os guards aplicados (totalizando 37 statements protegidos).

Análise complementar do erro assigned_to (preview do #333)

Investigado: a migration onda18a_quote_isolation_rls.sql em main já tem ADD COLUMN IF NOT EXISTS assigned_to ... (linha 22) — replay limpo já cria a coluna sem erro. Não há 3ª classe de drift pendente.

Conclusão


🤖 Decisão revisada após inspeção do código fonte. Plano original (mergear #333 primeiro) foi descartado.

@adm01-debug adm01-debug deleted the claude/replay-drift-tables branch May 25, 2026 18:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants