fix(#184): o gate de idempotencia deixa de ser decorativo, e a doc para de mentir

O passo "modo UPDATE" do scripts/test-db.sh re-aplicava o baseline SEM
ON_ERROR_STOP e chamava isso de "provando idempotencia" (README:336). Nao era:
sem a flag o psql segue apos cada erro, entao o passo saia VERDE com 301 erros
dentro — inclusive os 4 `policy already exists` que faziam uma mudanca de RLS do
apendice nao chegar ao clone. O update.sh tambem roda sem a flag e filtra esses
erros como benignos, entao nem ele nem o gate viam o defeito. A flag e a diferenca
entre "re-aplicar terminou" e "re-aplicar nao errou".

PROVA de que o gate agora morde: revertendo o baseline para o de ontem,

  TEST_DB_EXIT=3
  ==> modo UPDATE: re-aplicando baseline.sql COM ON_ERROR_STOP=1
  psql:<stdin>:1918: ERROR: multiple primary keys for table "ai_agent_runs"

O mesmo arquivo que passava verde. Com o baseline consertado: EXIT=0, 100
arquivos, 718 passed.

DOC:
- README e docs/SETUP ganham o `create extension` (vector/citext/pg_trgm) ANTES do
  baseline. So o deploy-selfhost tinha; a ausencia nos outros dois e a causa
  imediata do relato da #184 (`type public.vector does not exist`).
- docs/deploy-selfhost para de mandar re-aplicar "sem a flag ON_ERROR_STOP". Essa
  instrucao estava certa quando o arquivo nao era idempotente; agora ela ensina a
  esconder erro de verdade.
- A afirmacao "O baseline.sql e idempotente" passou a ser VERDADE e ganhou a data
  e o numero da issue, para o proximo leitor saber desde quando.
- README:336 perde o "618 invariantes em 98 arquivos": o numero estava podre (sao
  720 casos em 100 arquivos) e nao foi atualizado de proposito — e a quarta fonte
  da mesma verdade, e o proprio job ja conta. Mesma licao do numero de specs do
  e2e, que apodreceu tres vezes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012RfAjBEKuCkL6tpUo77kUe
This commit is contained in:
Rafael Melgaço
2026-08-13 09:01:47 -03:00
co-authored by Claude Opus 5
parent bc3d610fae
commit a8b0928064
4 changed files with 41 additions and 8 deletions
+9 -1
View File
@@ -283,6 +283,14 @@ docker compose up -d # WAHA local (opcional em dev sem WhatsApp)
# O schema real vive no baseline.sql, o mesmo que o install.sh aplica na VPS.
# `supabase db push` "passa" e deixa o banco vazio.
supabase link --project-ref <seu-ref>
# Num projeto Supabase NOVO, habilite antes as extensões que o schema usa —
# sem elas o baseline para em `type public.vector does not exist`.
psql "$SUPABASE_DB_URL" -v ON_ERROR_STOP=1 -c \
'create extension if not exists vector with schema public;
create extension if not exists citext with schema public;
create extension if not exists pg_trgm with schema public;'
psql "$SUPABASE_DB_URL" -v ON_ERROR_STOP=1 -f supabase/baseline.sql
pnpm dev
@@ -333,7 +341,7 @@ pnpm test:e2e # Playwright (requer dev server)
| Check | O que faz |
|---|---|
| `verify` | typecheck + lint + `lint:channels` + `test:unit` + `test:shell` |
| `invariants` | sobe um Postgres limpo, aplica o `baseline.sql` em modo **install** (`ON_ERROR_STOP=1`) e depois em modo **update** (provando idempotência), e roda **618 invariantes em 98 arquivos** — RBAC, atribuição, escopo, roteamento, follow-up, webhooks e automações |
| `invariants` | sobe um Postgres limpo, aplica o `baseline.sql` em modo **install** e depois em modo **update** — as duas passadas com `ON_ERROR_STOP=1`, que é o que torna a segunda uma prova de idempotência e não só um "terminou" —, e roda os invariantes de RBAC, atribuição, escopo, roteamento, follow-up, webhooks e automações |
| `build-and-size` | `pnpm build` em Node 22 |
| `e2e` | sobe Supabase local, aplica o `baseline.sql` e roda **44 das 45 specs** Playwright pelo frontend |
+9 -1
View File
@@ -113,7 +113,15 @@ supabase login
# Conecte ao seu projeto (project-ref está na URL do dashboard)
supabase link --project-ref <seu-project-ref>
# Aplica o SCHEMA — o baseline, não a cadeia de migrations
# Num projeto Supabase NOVO, habilite antes as extensões que o schema usa —
# sem elas o baseline para em `type public.vector does not exist`.
psql "$SUPABASE_DB_URL" -v ON_ERROR_STOP=1 -c \
'create extension if not exists vector with schema public;
create extension if not exists citext with schema public;
create extension if not exists pg_trgm with schema public;'
# Aplica o SCHEMA — o baseline, não a cadeia de migrations.
# Re-aplicar é seguro e não erra: o arquivo é idempotente (issue #184).
psql "$SUPABASE_DB_URL" -v ON_ERROR_STOP=1 -f supabase/baseline.sql
```
+10 -3
View File
@@ -65,13 +65,20 @@ psql "$SUPABASE_DB_URL" -v ON_ERROR_STOP=1 -f supabase/baseline.sql
```
O `baseline.sql` é idempotente — cria o CRM inteiro + as tabelas do agente
(migrations 0001→atual).
(migrations 0001→atual). Para **atualizar** uma instalação existente, rode o mesmo
comando de novo, **com a mesma flag**: re-aplicar não erra.
Isso passou a ser verdade em 2026-08-13 (issue #184). Antes o arquivo era
idempotente só em parte — as tabelas tinham `IF NOT EXISTS`, índices, constraints e
policies não —, e re-aplicar com `ON_ERROR_STOP=1` parava em
`multiple primary keys for table "ai_agent_runs"`. Sem a flag saía "verde" com 301
erros dentro, e quatro deles faziam uma mudança de RLS **não chegar** ao clone.
O gate que prova isso é o job `invariants`, que agora re-aplica com a flag.
> **Usando Postgres próprio em vez de Supabase?** Aplique ANTES o
> `scripts/selfhost-prelude.sql` (roles/schemas/extensões que o dump supõe).
> Limite: auth/storage viram stubs — o login do app exige Supabase real; o
> worker/agente funcionam integralmente. Para ATUALIZAR uma instalação existente, rode o mesmo
comando de novo (sem a flag `ON_ERROR_STOP`): só o que falta é aplicado.
> worker/agente funcionam integralmente.
Crie também a role dedicada do worker (mais seguro que usar o superusuário):
+13 -3
View File
@@ -218,9 +218,19 @@ echo "==> modo INSTALL: aplicando baseline.sql com ON_ERROR_STOP=1"
psql_install < "$BASELINE"
echo " ✓ install ok"
echo "==> modo UPDATE: re-aplicando baseline.sql sem ON_ERROR_STOP (idempotência)"
docker exec -i "$CONTAINER" psql -U postgres -d postgres -q -f - < "$BASELINE" >/dev/null
echo " ✓ update ok (re-apply terminou; erros tolerados por contrato)"
# COM `ON_ERROR_STOP=1`, e é isto que torna o passo uma prova (issue #184).
#
# Antes ele re-aplicava SEM a flag e chamava o resultado de "idempotência". Não
# era: sem a flag o psql segue após cada erro, então o passo saía verde com 301
# erros dentro — inclusive os 4 `policy already exists` que faziam uma mudança de
# RLS do apêndice NÃO chegar ao clone. O `update.sh` também roda sem a flag e
# filtra esses erros como benignos, então nem ele nem este gate viam o defeito, e
# o README dizia "provando idempotência" apontando para cá.
#
# A flag é a diferença entre "re-aplicar terminou" e "re-aplicar não errou".
echo "==> modo UPDATE: re-aplicando baseline.sql COM ON_ERROR_STOP=1 (idempotência de verdade)"
psql_install < "$BASELINE"
echo " ✓ update ok (zero erro na re-aplicação)"
echo "==> invariantes: vitest (tests/invariants)"
TEST_DB_CONTAINER="$CONTAINER" vitest run --config vitest.db.config.ts "$@"