# Fluxos seguros de migração de banco de dados para agentes de programação com IA

Mudanças em bancos de dados de produção exigem um fluxo diferente do usado para edições comuns de código. Um agente de programação com IA pode inspecionar um esquema, rastrear consultas da aplicação e criar arquivos de migração mais rápido do que a maioria das equipes consegue agendar uma revisão. Essa velocidade não pode se transformar em permissão para alterar a produção só porque um prompt incluiu a palavra «deploy».

Fluxos seguros de migração de banco de dados separam planejamento de autoridade. O agente pode preparar evidências e um conjunto preciso de mudanças. Uma pessoa pode revisar as consequências operacionais, provar que a recuperação funciona e aprovar uma única execução delimitada. Essa separação evita a falha conhecida em que um `ALTER TABLE` aparentemente inofensivo vira um fluxo de checkout bloqueado, uma reescrita descontrolada ou uma implantação incompleta que ninguém consegue explicar.

Este artigo usa comandos do PostgreSQL porque seu comportamento de bloqueios e transações é descrito de forma clara na documentação. O fluxo também se aplica a outros bancos de dados relacionais, mas não copie a sintaxe ou as suposições do PostgreSQL para outro mecanismo sem consultar o manual correspondente.

## Mudanças em produção precisam de autoridade separada da geração de código

Um arquivo de migração e uma migração em produção são atos diferentes. O primeiro descreve uma intenção. O segundo usa uma autoridade escassa contra um sistema ativo, com usuários, replicação, backups e versões da aplicação que podem não estar alinhadas.

As equipes costumam cometer o erro de dar ao agente uma única conexão ampla com o banco porque isso facilita o desenvolvimento local. Essa conexão acaba atravessando ambientes, e o agente não consegue distinguir um hostname de homologação de um cluster de produção se ambos aceitarem as mesmas credenciais. Pior: um agente que pode executar SQL arbitrário não tem um motivo natural para parar antes de uma reescrita de tabela. Ele vê uma tarefa e uma ferramenta.

Divida o trabalho em fases distintas, com entradas e permissões diferentes:

1. A descoberta lê metadados do esquema, histórico de migrações, código das consultas e restrições operacionais.
2. O planejamento produz SQL, comportamento esperado dos bloqueios, efeitos sobre os dados, verificações de preflight, consultas de verificação e uma decisão de recuperação.
3. A revisão confirma que o plano corresponde ao estado real da produção e que a organização aceita o risco.
4. A execução roda um artefato aprovado contra um único destino identificado.
5. A verificação prova que a aplicação e o banco chegaram ao estado esperado antes que alguém considere a implantação concluída.

A distinção útil é entre *reversibilidade* e *recuperabilidade*. Adicionar uma coluna que aceita valores nulos costuma ser reversível: uma instrução posterior pode removê-la se nenhum código depender dela. Atualizar milhões de linhas com base em uma expressão incorreta pode não ser reversível, mesmo que alguém escreva uma atualização aparentemente inversa. Talvez você não saiba quais eram os valores originais que foram sobrescritos. Recuperabilidade significa ter uma forma testada de restaurar os dados ou conter o dano. Trate esses conceitos como campos separados em todo plano de migração.

Um agente nunca deve deduzir autoridade sobre produção a partir de uma branch do repositório, do rótulo de um ticket ou de uma variável de ambiente fornecida em um prompt de chat. Esses sinais descrevem intenção, e a intenção frequentemente está errada. A execução deve exigir um destino selecionado fora do contexto textual do agente, um artefato preparado cujo digest esteja registrado e uma pessoa que veja exatamente o que será executado.

## Transforme o plano de migração em um artefato revisável

Um plano revisável dá à pessoa responsável informações suficientes para rejeitar uma migração antes que ela chegue ao banco. «Adicionar um índice» não é um plano. O tamanho da tabela, a finalidade da consulta, o formato do índice, a restrição de transação, a exposição a bloqueios e a consulta de verificação transformam isso em um plano.

Peça ao agente que produza um diretório com arquivos definidos, em vez de um parágrafo em um pull request. Por exemplo:

```text
migrations/2025-04-add-orders-status-index/
  up.sql
  verify.sql
  preflight.sql
  recovery.md
  manifest.json
```

O manifesto deve vincular os arquivos ao destino pretendido e registrar o que o agente descobriu. Este exemplo não expõe credenciais nem afirma que existe um backup apenas porque alguém pediu um.

```json
{
  "migration_id": "2025-04-add-orders-status-index",
  "engine": "postgresql",
  "target": "production-orders",
  "change": "create index for filtered order status query",
  "transaction_mode": "outside_transaction",
  "requires_backup_restore_check": true,
  "expected_write_blocking": "none during index build",
  "stop_conditions": [
    "target schema differs from preflight result",
    "backup restore check fails",
    "index is invalid after execution"
  ]
}
```

Uma pessoa revisora deve conseguir responder a cinco perguntas com base nesse artefato:

- Qual banco de dados e qual esquema receberão a mudança?
- Qual SQL exato será executado e o executor o envolverá em uma transação?
- Qual operação bloqueará, reescreverá, fará uma varredura ou consumirá espaço adicional?
- Que evidências provam que o banco pode ser recuperado se a mudança der errado?
- Quais consultas ou sinais da aplicação comprovam o sucesso?

Exija que o agente inclua suas incertezas. Se ele não conseguir determinar a versão do PostgreSQL, o tamanho da tabela, a versão atual da migração ou o intervalo de compatibilidade da aplicação que faz a chamada, isso deve aparecer como condição de parada. Inventar confiança a partir de um acesso parcial ao repositório é pior do que deixar um item em aberto.

Mantenha o SQL gerado imutável depois da revisão. Se uma pessoa revisora editar `up.sql`, gere novamente o checksum e envie o artefato para uma nova revisão. Uma falha comum começa com uma migração revisada, seguida de uma «pequena correção» apressada no shell de implantação. O comando real deixa de ser o comando revisado, e o registro de auditoria passa a contar uma história reconfortante, porém falsa.

## Classifique a operação antes de decidir como executá-la

O verbo SQL não informa o suficiente sobre o risco operacional. `ALTER TABLE` abrange mudanças que terminam rapidamente e mudanças que obtêm bloqueios ou reescrevem dados suficientes para ocupar todo o espaço em disco. Um fluxo seguro classifica a operação real, a versão do banco, o tamanho da tabela e a carga concorrente antes de definir o caminho de execução.

A documentação de `ALTER TABLE` do PostgreSQL deixa claro o ponto desconfortável: muitas formas adquirem um bloqueio `ACCESS EXCLUSIVE`, salvo quando o manual indica o contrário. Esse bloqueio entra em conflito com leituras e gravações. «Funcionou rapidamente em homologação» diz pouco se o ambiente não tem transações longas, tráfego de relatórios nem uma fração dos dados de produção.

Use quatro classes práticas:

| Classe | Exemplo típico | Expectativa de execução |
|---|---|---|
| Apenas metadados | Adicionar uma coluna que aceita nulos e não tem valor padrão | Operação curta, mas ainda é preciso verificar o comportamento dos bloqueios na sua versão |
| Criação concorrente | Criar um novo índice | Forma especial do comando e tratamento separado da transação |
| Alteração de dados em lotes | Preencher uma nova coluna | Pequenos commits, ritmo medido e progresso retomável |
| Reescrita ou mudança destrutiva | Alterar o tipo de uma coluna grande ou excluir dados | Decisão de manutenção com plano de recuperação explícito |

Não chame toda operação online de segura. O `CREATE INDEX CONCURRENTLY` do PostgreSQL evita bloquear gravações durante a criação, conforme documentado em `CREATE INDEX`. Ele também demora mais, não pode ser executado dentro de um bloco de transação e pode deixar um índice inválido após uma falha. O conselho popular de «sempre usar concurrently» ignora essas condições. Use-o quando a disponibilidade para gravações for importante e o executor de migrações conseguir respeitar suas regras.

Uma consulta de planejamento pode dar à pessoa revisora uma primeira estimativa do tamanho da tabela e dos índices:

```sql
SELECT
  pg_size_pretty(pg_total_relation_size('public.orders')) AS total_size,
  pg_size_pretty(pg_relation_size('public.orders')) AS table_size,
  pg_size_pretty(pg_indexes_size('public.orders')) AS indexes_size;
```

O resultado esperado tem uma linha com três valores de tamanho fáceis de ler. Trate-o como um sinal de dimensionamento, não como uma promessa de duração. Largura das linhas, estado do cache, gravações concorrentes, replicação, velocidade do disco e transações ativas alteram o resultado.

O agente também deve verificar os bloqueadores antes de uma operação com exposição significativa a bloqueios. O PostgreSQL expõe sessões ativas em `pg_stat_activity`, mas isso não dá permissão para encerrá-las. O plano deve identificar a pessoa responsável por qualquer carga de longa duração e declarar se a migração aguardará, será reagendada ou terá uma janela de manutenção.

## Um backup restaurado é o ingresso para a execução

Um comando de backup bem-sucedido prova que um programa gravou um arquivo. Não prova que o arquivo pode ser restaurado, que contém os objetos necessários ou que o procedimento de restauração funciona sob pressão. Faça uma verificação de restauração antes de um trabalho destrutivo ou difícil de reverter.

Em um banco PostgreSQL, um dump lógico em formato personalizado pode ser assim:

```sh
pg_dump -Fc -d "$SOURCE_DATABASE" -f "orders-preflight.dump"
pg_restore -l "orders-preflight.dump" | sed -n '1,12p'
createdb migration_restore_check
pg_restore -d migration_restore_check "orders-preflight.dump"
psql -d migration_restore_check -c "SELECT count(*) FROM public.orders;"
```

O comando de listagem deve mostrar entradas do arquivo, como a definição da tabela, a entrada com os dados da tabela e as definições dos índices. A consulta final deve mostrar um resultado de uma coluna com a contagem de linhas restauradas. Registre o resultado do comando, o identificador do dump, o destino da restauração e o resultado da verificação no registro da migração. Não coloque URLs de banco de dados nem senhas nesse registro.

Uma verificação de restauração lógica não substitui um teste de recuperação física, de recuperação point-in-time, de promoção de réplica ou de garantia de backup do provedor. Ela responde a uma pergunta mais específica: este dump pode ser restaurado em um banco e produzir os objetos e o formato de dados esperados? Essa resposta limitada ainda detecta arquivos danificados, extensões ausentes, problemas de funções e procedimentos que só funcionavam no laptop de quem os escreveu.

Escolha o método de recuperação antes da execução, não depois de um erro. O plano deve dizer qual destas opções se aplica:

- Uma migração inversa é segura porque nenhum dado foi descartado e a aplicação tolera a reversão.
- A restauração em um banco substituto é o caminho de recuperação, com a pessoa responsável pela decisão do cutover.
- Uma réplica ou um procedimento de recuperação do provedor é o caminho, com um objetivo de ponto de recuperação documentado.
- A mudança não pode ser recuperada de forma limpa, portanto exige uma janela de manutenção e uma aceitação explícita do risco.

Essa última categoria é legítima. Fingir que existe um rollback porque o template de implantação exige um campo não é. Já vi equipes escreverem `DROP COLUMN` como rollback para um preenchimento que alterou dados de clientes e depois descobrirem que excluir a nova coluna apenas apagou as evidências, enquanto os valores antigos continuavam errados.

## Expanda primeiro e remova depois

A maioria das mudanças de esquema voltadas para a aplicação deve preservar a compatibilidade entre versões que rodam ao mesmo tempo. Durante uma implantação, processos antigos e novos podem coexistir porque workers demoram a ser drenados, usuários mantêm requisições abertas ou um rollback reinicia uma versão anterior. Uma migração que pressupõe uma única versão instantânea da aplicação transforma o comportamento normal de uma implantação em uma indisponibilidade.

Suponha que você precise substituir `orders.status_text` por um `orders.status_code` com restrições. A versão insegura adiciona a nova coluna, reescreve todas as linhas, troca o código da aplicação e remove a coluna antiga em uma única versão. Ela tem vários pontos de falha e nenhum ponto tranquilo para parar.

Use uma sequência de expansão e contração:

1. Adicione `status_code` aceitando nulos e inclua estruturas de apoio que não quebrem a aplicação atual.
2. Implante um código que leia o novo campo quando ele estiver presente e grave os dois campos, ou derive um com segurança a partir do outro.
3. Preencha as linhas existentes em lotes limitados, registrando o progresso fora do contexto temporário do chat do agente.
4. Valide que todas as linhas atendem à nova regra e que os leitores usam o novo campo.
5. Em uma versão posterior, pare de gravar o campo antigo, aguarde o período de retenção acordado e só então remova-o.

O loop de lotes é importante. Um único `UPDATE` gigantesco mantém recursos ocupados por tempo demais, gera um pico de write-ahead log, sobrecarrega réplicas e torna a recuperação mais difícil de analisar. Uma consulta limitada dá à pessoa responsável um ponto entre commits para examinar o atraso da replicação, a taxa de erros e a carga do banco.

Este padrão do PostgreSQL atualiza um lote selecionado pela chave primária e retorna as linhas alteradas:

```sql
WITH batch AS (
  SELECT id
  FROM public.orders
  WHERE status_code IS NULL
  ORDER BY id
  FOR UPDATE SKIP LOCKED
  LIMIT 500
)
UPDATE public.orders AS o
SET status_code = CASE o.status_text
  WHEN 'new' THEN 10
  WHEN 'paid' THEN 20
  WHEN 'shipped' THEN 30
  ELSE NULL
END
FROM batch
WHERE o.id = batch.id
RETURNING o.id;
```

O executor repete isso somente enquanto houver linhas retornadas e enquanto o mapeamento produzir valores aceitáveis. `SKIP LOCKED` pode ser adequado para um worker controlado porque evita esperar por linhas mantidas por outra transação. Ele não prova que todas as linhas foram processadas. A consulta de verificação deve procurar nulos restantes e valores de origem inesperados:

```sql
SELECT status_text, count(*)
FROM public.orders
WHERE status_code IS NULL
GROUP BY status_text
ORDER BY count(*) DESC;
```

Se essa consulta retornar um valor desconhecido, pare. Não deixe um agente decidir que um estado inesperado em produção pode virar um valor padrão de enum só porque ele quer concluir a tarefa.

## A execução precisa de condições de parada rígidas e comandos delimitados

Uma migração aprovada ainda pode encontrar um estado de produção diferente daquele que o planejador inspecionou. O executor deve realizar verificações de preflight imediatamente antes da execução e parar quando houver divergências. É isso que torna um fluxo mais seguro do que um runbook seguido de memória.

Use um executor dedicado que aceite apenas um identificador de artefato e um seletor de destino. Ele deve recusar SQL inserido diretamente no terminal, rejeitar um artefato cujo checksum tenha mudado e exibir a identidade do destino antes de começar. O executor pode rodar primeiro um SQL de preflight somente leitura e então exigir uma aprovação separada para a parte que altera dados.

Defina explicitamente os limites de tempo da sessão. O PostgreSQL documenta `lock_timeout` e `statement_timeout` como controles separados. O primeiro interrompe a espera por um bloqueio; o segundo interrompe uma instrução que demora demais. Um preâmbulo razoável para uma operação curta de metadados pode ser:

```sql
SET lock_timeout = '5s';
SET statement_timeout = '60s';
SELECT current_database(), current_user, now();
```

Não use esses números cegamente em criações de índices ou preenchimentos de dados. Um timeout é um orçamento que o plano precisa justificar. Se o comando exige trinta minutos sob a carga esperada, um timeout de instrução de sessenta segundos apenas cria uma falha previsível. Para criar um índice concorrente, use um caminho do executor que não envolva o comando em uma transação:

```sql
CREATE INDEX CONCURRENTLY IF NOT EXISTS orders_status_code_idx
ON public.orders (status_code);
```

Depois desse comando, verifique o índice em vez de presumir o sucesso porque o processo terminou sem erro. O PostgreSQL registra a validade nos metadados do catálogo, e uma criação concorrente malsucedida pode deixar um índice inválido que precisa ser inspecionado e removido antes de uma nova tentativa. Inclua essa verificação em `verify.sql`, junto com um plano da consulta voltado para a aplicação quando fizer sentido.

Uma decisão real de abortar precisa de sinais nomeados. Pare a execução se o preflight encontrar uma versão inesperada da migração, espaço disponível insuficiente, um bloqueador ativo fora da janela permitida, uma restauração de backup malsucedida, um checksum inválido ou um resultado diferente do predicado de verificação. A pessoa responsável não deve precisar negociar com um agente enquanto a espera por um bloqueio aumenta.

## A aprovação deve vincular uma pessoa a uma única execução real

A fadiga de aprovação aparece quando os sistemas pedem aprovação para cada chamada SQL. As pessoas passam a aprovar por reflexo ou desativam os prompts. Uma única aprovação no início de um processo de agente definido é útil quando identifica o processo, expira quando ele termina e não cobre silenciosamente outra sessão mais tarde.

A aprovação deve mostrar contexto suficiente para que uma pessoa rejeite a execução: a identidade assinada do processo, o destino selecionado, o identificador do artefato, a classe de ação pretendida e se o canal de ação pode gravar. Ela não deve exibir uma senha em texto puro nem exigir que o agente a manipule. A pessoa autoriza a ação, não a divulgação do segredo.

O Sallyport se encaixa nesse limite ao manter as credenciais de API e SSH em seu cofre criptografado, permitir que um agente compatível com MCP solicite ações por meio de `sp mcp` e retornar resultados das ações em vez de segredos em texto puro. A aprovação separada de sessão e a aprovação opcional por chave podem fazer com que uma ação em um banco de produção exija uma decisão humana sem colocar uma credencial reutilizável no contexto do agente.

Mantenha a aprovação restrita à fase de execução. O agente de descoberta pode usar um caminho somente leitura. O agente de execução pode receber uma sessão que expira com seu processo e acessar apenas a rota de ação aprovada. A revogação imediata é importante porque a pessoa responsável precisa conseguir interromper uma execução depois de um resultado inesperado no preflight, não apenas depois que o script de implantação terminar.

Não substitua um desenho operacional claro por uma linguagem de políticas que sua equipe não consiga explicar sob pressão. O limite confiável é simples: o acesso bloqueado nega todas as ações, um novo processo de execução exige autorização e credenciais especialmente sensíveis podem exigir uma nova decisão a cada uso. Isso é mais fácil de auditar do que uma pilha de regras inferidas que ninguém lembra de ter escrito.

## Os registros de auditoria devem reconstruir a mudança sem segredos

Depois de uma migração malsucedida, as pessoas fazem perguntas simples: quem a executou, contra qual destino, qual artefato exato foi executado, quando ela parou e se alterou dados? Um transcript genérico do terminal raramente responde a tudo. Ele pode omitir o destino, perder o comando depois de uma limpeza do histórico do shell ou incluir segredos que jamais deveriam ter sido registrados.

Registre eventos estruturados de planejamento, aprovação, preflight, execução, verificação, revogação e falha. Cada evento deve incluir o identificador da migração e o digest do artefato, para que uma investigação consiga conectar a revisão à execução real. Inclua a identidade do banco retornada pelo preflight, e não apenas o nome solicitado por quem fez a chamada.

Um formato mínimo de evento pode ser assim:

```json
{
  "event": "migration.verify",
  "migration_id": "2025-04-add-orders-status-index",
  "artifact_sha256": "recorded-digest",
  "target_identity": "production-orders",
  "result": "passed",
  "observed": "index valid; query returned expected columns"
}
```

Evite registrar parâmetros SQL quando eles puderem conter dados pessoais, tokens ou conteúdo de clientes. Registre o digest do artefato e um identificador seguro da instrução. O sistema de execução pode conservar diagnósticos protegidos conforme uma política de retenção definida, mas um diário de auditoria deve continuar útil sem se transformar em outro cofre de segredos.

A evidência de adulteração muda a qualidade de uma investigação. Um registro que alguém pode editar depois de uma implantação malsucedida prova apenas que alguém teve acesso ao registro. Se você usar um log de auditoria encadeado por hashes, verifique-o de forma independente como parte da análise do incidente. O `sp audit verify` do Sallyport verifica offline sua cadeia de auditoria criptografada sem exigir acesso ao cofre, uma propriedade adequada para uma pessoa revisora que não deve receber credenciais de produção.

## A verificação deve testar a carga de trabalho, não apenas o DDL

Uma migração é bem-sucedida quando a carga de trabalho pretendida funciona corretamente, não quando o banco aceita uma instrução. Uma nova coluna pode existir enquanto as gravações da aplicação não a preenchem. Um índice pode ser válido enquanto a consulta-alvo não consegue usá-lo porque o predicado ou o tipo de dados é diferente. Uma restrição pode estar validada enquanto um worker antigo ainda envia dados que violam o novo contrato da aplicação.

Escreva as consultas de verificação antes da execução, enquanto as pessoas revisoras ainda podem questionar suas suposições. Para uma migração de índice, verifique o estado do catálogo e inspecione a consulta que motivou a mudança. Para um preenchimento de dados, conte as linhas restantes, agrupe valores de origem inesperados e confirme que o novo caminho de gravação da aplicação produz a representação esperada. Para uma restrição, teste gravações válidas e inválidas em um banco descartável antes de pedir que a produção a aplique.

O `EXPLAIN` do PostgreSQL é útil, mas fácil de usar de maneira inadequada. A decisão do planejador depende de estatísticas, valores de parâmetros, distribuição dos dados e configuração. `EXPLAIN (ANALYZE, BUFFERS)` executa a consulta e mede seu resultado, portanto não o aponte casualmente para uma consulta cara em produção. Use-o em uma consulta segura e representativa e defina um resultado aceitável antes de ver a saída. Caso contrário, todo plano vira algo que uma pessoa cansada consegue racionalizar.

Acompanhe os indicadores da aplicação durante e depois da mudança: taxa de erros das requisições, latência das consultas no caminho afetado, pressão sobre as conexões, saúde da replicação e falhas dos workers. O agente pode coletar e apresentar essas leituras, mas a pessoa responsável pela versão decide se elas atendem à condição de saída definida.

Não agende a contração destrutiva apenas porque a implantação da expansão passou. Espere até que a observabilidade e o histórico de versões mostrem que nenhum processo antigo da aplicação ainda depende do esquema antigo. Depois, execute a remoção como uma migração revisada separadamente. Essa mudança adicional custa menos do que descobrir durante um rollback que a versão antiga espera uma coluna removida uma hora antes.

## A primeira execução em produção deve ser deliberadamente sem graça

A primeira migração em produção assistida por um agente deve adicionar uma mudança de baixo risco e observável, não redesenhar uma tabela sob carga. Escolha uma coluna adicional que aceite nulos, um comentário ou outra operação cujo comportamento você já conheça. Use o exercício para testar os limites: geração do artefato, seleção do destino, aprovação, evidências do backup, eventos de auditoria, falha no preflight, execução e verificação.

Faça o exercício provar que o sistema consegue recusar um trabalho. Aponte o executor para um destino com a impressão digital do esquema incorreta e confirme que ele para. Altere o arquivo revisado e confirme que a verificação do digest o rejeita. Revogue a autorização durante uma execução inofensiva em ambiente preparado e confirme que as ações posteriores falham. Esses testes revelam se os controles funcionam quando alguém precisa deles, em vez de apenas parecerem sensatos em um documento de projeto.

Depois disso, amplie as classes de migração permitidas uma de cada vez. Uma equipe que consegue adicionar uma coluna com segurança ainda não provou que consegue preencher uma tabela grande, criar um índice concorrente ou se recuperar de uma transformação incorreta de dados. Cada classe precisa de sua própria execução observada e de seus próprios critérios de falha.

Trate a autoridade sobre produção como algo que o agente toma emprestado para uma única tarefa delimitada e perde em seguida. Esse hábito operacional evitará mais danos do que qualquer instrução engenhosa em um prompt.
