# Endpoints de rollback para implantações autônomas sem colisões

Agentes autônomos de implantação precisam de permissão para falhar com segurança, não de permissão para adivinhar. O rollback perigoso raramente é um comando que falha. É um comando que funciona depois que o mundo mudou e restaura um artefato antigo sobre uma release que o agente não criou.

Uma ação segura de rollback identifica uma tentativa específica de implantação, nomeia uma release anterior específica e se recusa a agir quando sua visão do estado atual ficou desatualizada. Trate o rollback como uma transição de estado protegida, não como um atalho para «a última versão boa». Essa distinção decide se o agente corrige o próprio trabalho ou apaga o trabalho de outra pessoa.

## Um rollback deve pertencer a uma única tentativa de implantação

Os endpoints de rollback precisam vincular a reversão à tentativa de implantação que causou a falha suspeita. Um número de versão, sozinho, não cumpre esse papel.

As equipes costumam armazenar uma sequência como `1.8.4`, `1.8.5` e `1.8.6` e depois expor uma operação que diz «implantar 1.8.4 em produção». Um agente implanta `1.8.5`, recebe um alerta e chama essa operação. Enquanto isso, uma pessoa engenheira implantou a release urgente `1.8.6`. O endpoint de rollback aceita a solicitação porque `1.8.4` existe. Agora a produção executa um artefato anterior às duas alterações. A API fez exatamente o que foi solicitado, e esse é o problema.

Mantenha estas identidades separadas:

- Uma **release** é um pacote imutável de código, referências de configuração e metadados. Dê a ela um ID durável e um digest do artefato.
- Uma **tentativa de implantação** é uma solicitação para colocar uma release em um destino nomeado. Ela tem um ID de implantação, um ator e um ciclo de vida.
- O **estado do destino** é o que o ambiente executa naquele momento. Ele inclui uma revisão ou geração que muda a cada transição aceita.
- Uma **intenção de rollback** diz que a tentativa de implantação `D` pode restaurar a release `R` do destino somente se `D` ainda for proprietária do estado atual.

O campo que costuma ser esquecido é o vínculo de propriedade. Quando a implantação `dep_842` promove a release `rel_105`, registre que `dep_842` criou a geração atual do destino, `gen_913`. Um rollback vinculado a `dep_842` só pode prosseguir enquanto `gen_913` continuar atual. Se uma implantação posterior criar `gen_914`, o serviço deve recusar a solicitação antiga.

Não infira a propriedade comparando registros de data e hora. A ordem dos relógios falha com novas tentativas, workers em fila, reparos manuais e qualquer sistema que permita ao usuário selecionar uma release mais antiga. Armazene a relação direta quando aceitar a promoção.

Isso também responde a uma questão operacional incômoda: um agente pode reverter a implantação de outro agente? Normalmente, não. Uma autoridade separada pode autorizar explicitamente essa intervenção, mas a capacidade comum de rollback deve abranger apenas as ações iniciadas pelo próprio solicitante. Uma autoridade ampla parece conveniente até que dois ciclos de implantação reajam ao mesmo incidente.

## A proveniência imutável torna a release anterior conhecida

O serviço precisa capturar a proveniência do rollback antes de alterar o destino, porque depois da promoção a palavra «anterior» se torna ambígua. Consultar o histórico de releases depois de um alerta é uma receita para selecionar o que quer que esteja próximo em uma lista.

Quando um serviço de implantação aceita uma promoção, deve criar um registro que inclua a release estável anterior escolhida naquele momento. Essa release pode ser diferente do evento imediatamente anterior. Por exemplo, um canário pode promover `rel_105` enquanto `rel_103` continua sendo a base estável e `rel_104` foi um experimento interrompido. O destino correto da recuperação pode ser `rel_103`, não a linha diretamente anterior a `rel_105`.

Um registro mínimo pode ser assim:

```json
{
  "deployment_id": "dep_842",
  "environment": "production",
  "release_id": "rel_105",
  "artifact_digest": "sha256:8b2c...",
  "source_revision": "4f1d9c7",
  "config_digest": "sha256:1a06...",
  "prior_release_id": "rel_103",
  "created_target_generation": "gen_913",
  "migration_set_id": "mig_77",
  "actor_id": "agent-run-27"
}
```

O `prior_release_id` é uma decisão, não um campo de conveniência. Seu controlador de promoção deve escolhê-lo usando regras que as pessoas responsáveis pela operação possam consultar: a última release estável verificada para aquele destino, talvez com configuração e requisitos de migração compatíveis. A operação de rollback consome essa decisão salva. Ela não a recalcula porque a tabela de histórico mudou.

A identidade do artefato precisa de mais do que um rótulo de release compreensível por pessoas. Tags podem mudar de lugar. Rótulos de build podem ser reutilizados por acidente. Um rollback deve implantar o endereço de conteúdo ou outra referência imutável do artefato registrada na tentativa original. Se o registro permitir que uma tag aponte para bytes diferentes depois, o ID da release do destino deve resolver para um digest capturado no momento da promoção.

A configuração exige a mesma disciplina. Reverter os bytes da aplicação e deixar alterados um recurso de funcionalidade, uma referência a segredo de execução, uma política de pull da imagem ou uma configuração de recursos pode criar um sistema que nunca existiu nos testes. Você não precisa duplicar todos os valores no registro da implantação, mas deve registrar uma revisão ou digest imutável da configuração e definir se o rollback a restaura.

O estado do banco de dados cria uma fronteira separada. Uma release que apenas adiciona colunas que aceitam valores nulos geralmente permite um rollback da aplicação. Uma release que remove uma coluna, reescreve valores ou muda semântica pode não permitir. Não prometa um «rollback completo» genérico se o planejador de releases não puder provar a compatibilidade. Marque a implantação como reversível na aplicação, reversível no tráfego ou exigindo reparo. Uma recusa é menos constrangedora do que executar código antigo contra um esquema que ele não entende.

## O endpoint precisa da release atual esperada

Uma solicitação de rollback deve transportar tanto a release desejada quanto o estado ativo que ela espera substituir. Sem essa pré-condição, o endpoint não consegue distinguir uma recuperação válida de uma instrução desatualizada.

Use um contrato de solicitação com este formato:

```http
POST /v1/environments/production/rollbacks
Idempotency-Key: 7e4cd1ee-62cb-4efa-985f-4ee0b77d577b
Content-Type: application/json

{
  "origin_deployment_id": "dep_842",
  "expected_current_release_id": "rel_105",
  "expected_target_generation": "gen_913",
  "restore_release_id": "rel_103",
  "reason": "error rate exceeded release threshold",
  "approval_id": "apr_551"
}
```

O serviço deve derivar `restore_release_id` de `origin_deployment_id` quando possível e depois comparar o valor enviado com o `prior_release_id` armazenado. Manter os dois campos na solicitação ajuda os auditores a ver a intenção declarada pelo agente, mas o registro do servidor prevalece. Nunca permita que um solicitante transforme sua própria implantação em autorização para escolher qualquer artefato histórico.

Em caso de sucesso, retorne a nova tentativa de implantação criada e a nova geração do destino. Não retorne uma resposta vaga com `accepted` se o sistema puder reservar a transição de forma síncrona.

```json
{
  "rollback_deployment_id": "dep_849",
  "reverted_deployment_id": "dep_842",
  "previous_release_id": "rel_105",
  "current_release_id": "rel_103",
  "target_generation": "gen_914",
  "status": "running"
}
```

Se o destino ativo não corresponder, retorne uma resposta de conflito. O corpo deve incluir informações suficientes para que o agente relate o ocorrido, mas não autoridade suficiente para improvisar uma nova ação.

```http
HTTP/1.1 409 Conflict
Content-Type: application/json

{
  "error": "stale_rollback",
  "origin_deployment_id": "dep_842",
  "expected_target_generation": "gen_913",
  "observed_target_generation": "gen_914",
  "observed_release_id": "rel_106"
}
```

Um 409 é um resultado de segurança bem-sucedido. Ensine o agente a parar nesse resultado, anexar a resposta ao registro do incidente e pedir uma nova decisão. Não ofereça uma instrução alternativa como «tente novamente sem a geração esperada». Essa alternativa transforma sua proteção em encenação.

Algumas equipes usam um cabeçalho HTTP `If-Match` com um ETag em vez de um campo JSON. Isso funciona se o ETag representar o estado do destino e mudar a cada transição. O mecanismo importa menos do que a regra: o comando precisa nomear o estado que pode substituir.

## A serialização impede a colisão de duas solicitações válidas

Uma verificação de pré-condição, sozinha, não protege um destino se dois workers puderem passar pela verificação antes que qualquer um confirme a alteração. O serviço de implantação deve serializar as mudanças no mesmo ambiente e usar uma comparação e troca atômica no armazenamento de estado.

Suponha que o destino atual seja `(rel_105, gen_913)`. Um agente envia um rollback e uma pessoa operadora envia `rel_106`. As duas solicitações leem `gen_913`. Se o serviço verificar na memória da aplicação e depois gravar sem condição, ambas poderão declarar sucesso. A última gravação vence, e seu histórico de auditoria registra um estado que talvez nunca tenha existido para os usuários.

Coloque a comparação e a alteração na mesma transação ou em uma gravação condicional. Uma implementação relacional poderia emitir este padrão:

```sql
UPDATE environment_targets
SET release_id = :restore_release_id,
    generation = generation + 1,
    active_deployment_id = :rollback_deployment_id,
    updated_at = CURRENT_TIMESTAMP
WHERE environment = :environment
  AND generation = :expected_generation
  AND release_id = :expected_release_id;
```

O serviço verifica o número de linhas afetadas. Uma linha alterada reserva a transição de estado. Zero linhas significa conflito. Em seguida, ele deve ler o destino atual e retornar os valores observados na resposta 409.

Uma fila não substitui essa condição. Filas reduzem a chance de trabalho concorrente, mas uma entrega duplicada, um caminho manual fora da fila ou uma nova tentativa de um worker ainda podem gerar comandos concorrentes. Mantenha a gravação condicional no local onde o estado vive.

A idempotência resolve uma falha diferente. Um agente pode perder a resposta depois que o serviço aceita o rollback. Se repetir a solicitação com a mesma chave de idempotência, o serviço deve retornar a implantação e o status do rollback original. Ele não deve reservar outra geração nem iniciar uma segunda execução.

Limite a idempotência ao solicitante e ao endpoint, registre um digest do corpo da solicitação e rejeite uma chave reutilizada com um corpo diferente. Caso contrário, um cliente com defeito poderá associar uma intenção nova a uma solicitação anterior ao reutilizar um identificador.

## Um rollback pode preservar um estado ruim de dependência

O rollback da aplicação e a recuperação do ambiente são operações separadas. Um endpoint que implanta código antigo não consegue tornar automaticamente todas as dependências compatíveis outra vez.

Já vi a versão previsível dessa falha: a release `rel_105` introduziu código que grava um novo valor de enumeração. Depois, uma migração restringiu uma limitação do banco de dados para permitir apenas os novos valores. A release falhou por um motivo não relacionado, e a pessoa operadora restaurou `rel_103`. O código antigo gravou o valor anterior, o banco o rejeitou e o incidente cresceu porque o rollback parecia completo no painel de implantação.

O endpoint não causou a alteração do esquema, mas sua resposta de sucesso fez uma afirmação falsa. Evite essa afirmação exigindo metadados da release que descrevam a compatibilidade em termos concretos. No mínimo, registre se a release restaurada pode ler os dados atuais, gravar dados atuais e operar com a revisão de configuração do destino.

O gerenciamento de tráfego tem sua própria armadilha. Um rollback de canário normalmente deve alterar apenas a alocação de tráfego pertencente àquele canário. Se uma release sem relação tiver ajustado o pool estável ou outro controlador tiver alterado uma regra de roteamento, um endpoint de rollback que grave um documento inteiro de roteamento poderá apagar essas mudanças. Use versões no nível do recurso ou altere apenas os campos de alocação reservados pela implantação.

O mesmo princípio vale para a infraestrutura. Se uma release criou uma fila, um bucket, uma função ou uma regra de firewall que um trabalho posterior adotou, excluí-los durante a reversão pode prejudicar outro serviço. A limpeza exige um registro de propriedade do recurso e uma verificação de que nenhuma implantação posterior o reivindicou. Se não puder estabelecer essa propriedade, deixe o recurso no lugar e crie uma tarefa de reparo.

Para operações irreversíveis, escolha uma correção posterior. O agente pode desativar um recurso de funcionalidade, desviar o tráfego ou implantar uma release corretiva. As pessoas operadoras não gostam dessa resposta porque «rollback» parece mais rápido, mas uma reversão visualmente limpa que destrói dados posteriores custa mais tempo do que um plano de reparo.

## Os agentes precisam de autoridade restrita e de um ponto de parada visível

Um agente autônomo deve receber a menor autoridade de ação capaz de concluir a implantação atribuída. Ele não precisa de credenciais brutas de nuvem, de um shell geral com acesso à produção nem de um endpoint que aceite IDs de release arbitrários.

Dê ao agente um identificador de implantação quando ele iniciar uma release. Esse identificador pode autorizar leituras de status, verificações de integridade, alterações de tráfego dentro da alocação da implantação e um rollback que nomeie a implantação original. Faça-o expirar quando a implantação chegar a um estado final ou quando uma pessoa revogar a execução. O serviço de implantação ainda precisa impor a propriedade no servidor, porque um identificador pode ser copiado ou um cliente pode apresentar falhas.

A aprovação humana deve ocorrer antes da fronteira irreversível, não depois que o agente já montou um comando irreversível. Uma política sensata pede aprovação quando um agente inicia uma implantação em produção e depois permite que ele reverta exatamente essa implantação enquanto a pré-condição de propriedade for válida. Se o agente encontrar uma release posterior, precisará de uma nova aprovação para qualquer intervenção. Esse é um bom momento para desacelerar, porque alguém mudou a situação.

O Sallyport pode manter as credenciais HTTP e SSH fora do processo do agente enquanto uma pessoa aprova a execução do agente ou marca uma credencial para aprovação a cada uso. Esse controle ajuda a proteger o caminho da ação, mas o serviço de rollback ainda precisa de suas próprias verificações de release e geração; a custódia das credenciais não define a propriedade da implantação.

Evite nomes de capacidade como `production:rollback:any`. Eles convidam o solicitante a escolher o escopo em tempo de execução. Prefira uma capacidade emitida pelo servidor e vinculada a `dep_842`, ao ambiente `production` e à rota específica de rollback. Se o agente solicitar um destino sem relação, a camada de autorização deve rejeitá-lo antes que o controlador de implantação avalie a solicitação.

Registre a identidade do processo ou da carga de trabalho do agente em todas as solicitações. Uma pessoa deve conseguir responder quem iniciou `dep_842`, qual código assinou ou autenticou o solicitante, qual aprovação o cobriu e se alguém revogou o acesso antes do fim da execução. Contas de automação anônimas transformam todo incidente em trabalho arqueológico.

## A verificação deve testar a release restaurada, não a solicitação

Um rollback só chega ao fim depois que o destino executa a release pretendida e o serviço verifica as condições que justificaram a recuperação. HTTP 202, uma saída de comando bem-sucedida ou um evento do controlador dizendo «aplicado» não prova que a release antiga atende ao tráfego corretamente.

Defina a verificação de acordo com o modo de falha real da implantação. Se latência ou erros dispararam o rollback, observe o serviço restaurado pelo mesmo caminho de medição depois que o tráfego chegar até ele. Se uma release de worker consumiu jobs malformados, verifique a versão do worker e uma carga controlada. Se um erro de configuração causou falhas na inicialização, inspecione as instâncias prontas e a revisão de configuração que carregaram.

Mantenha uma janela de observação limitada e registre o resultado. O endpoint pode informar `verifying` e depois `succeeded`, `failed` ou `needs_operator`. Não espere indefinidamente por uma métrica que pode estar indisponível. Um limite de tempo deve produzir um resultado inconclusivo explícito, seguido de uma decisão da pessoa operadora.

O evento de auditoria deve conectar todas as etapas: o alerta ou regra que solicitou a reversão, a implantação de origem, o estado esperado, a reserva condicional, os eventos de execução, as evidências de integridade, o estado final e qualquer revogação. Os eventos precisam de controles de ordenação e integridade, porque uma linha do tempo amigável de implantação não é suficiente durante uma disputa.

O Sallyport registra execuções de agentes e ações individuais em um registro de auditoria criptografado e encadeado por hash, e `sp audit verify` verifica essa cadeia offline sem uma chave do cofre. Use esse tipo de evidência para mostrar que um agente solicitou uma ação e mantenha os próprios registros de transição de estado e verificação do serviço de implantação como a fonte autorizada sobre o que mudou.

## Comandos conhecidos de rollback precisam de um wrapper mais seguro

`kubectl rollout undo` é útil para uma pessoa operadora trabalhando diretamente em um Kubernetes Deployment, mas não é um contrato completo para recuperação autônoma. A documentação do Kubernetes informa que `kubectl rollout undo` reverte para a revisão anterior da implantação, a menos que o solicitante forneça `--to-revision`. Esse padrão faz sentido durante um diagnóstico manual. Ele não prova que a revisão anterior pertence à execução do agente que falhou.

Um Kubernetes Deployment acompanha o histórico de revisões em ReplicaSets, enquanto `revisionHistoryLimit` controla quanto desse histórico o Kubernetes retém. Esse histórico é um artefato do controlador, não o registro empresarial da base aprovada de uma release, da compatibilidade da configuração ou da propriedade do agente. Quando o histórico é eliminado, o «undo» também pode não encontrar a revisão esperada por um processo externo de implantação.

Não entregue ao agente uma credencial geral do `kubectl` e chame o comando de seu endpoint de rollback. Coloque um controlador ou serviço de implantação entre o agente e o cluster. O serviço deve resolver o registro da implantação de origem, comparar a geração ativa do destino, reservar a alteração de estado e chamar a plataforma subjacente somente depois de passar por essas verificações.

A mesma crítica vale para comandos de provedores de nuvem que dizem «implantar a revisão X» ou controles de CI que dizem «executar novamente a release anterior». Eles operam sobre um recurso da plataforma. Não sabem se uma release está relacionada ao incidente atual do agente, a menos que seu plano de controle forneça esse contexto.

Mantenha o comando da plataforma restrito também. Se o serviço puder alterar a revisão de uma carga de trabalho nomeada, evite conceder a ele mutação em todo o cluster. Um wrapper que preserva credenciais amplas apenas escondeu o perigo atrás de outra API.

## Crie um registro de releases antes de automatizar a recuperação

Você pode introduzir um rollback protegido sem substituir todos os sistemas de implantação. Comece tornando o registro de releases a fonte autorizada para um destino de produção e faça os caminhos humano e do agente passarem pela mesma transição condicional.

Uma sequência prática de adoção tem cinco partes:

1. Atribua IDs imutáveis às releases e às tentativas de implantação, depois registre a release anterior aprovada e a geração do destino no momento da promoção.
2. Adicione um endpoint que exija `origin_deployment_id`, a release esperada, a geração esperada e uma chave de idempotência.
3. Torne condicional a atualização do destino no banco de dados ou no armazenamento de controle e retorne 409 para toda divergência.
4. Classifique cada release quanto à reversibilidade da aplicação, da configuração, dos dados e do tráfego antes da promoção.
5. Exija evidências de verificação antes que o controlador marque um rollback como concluído.

Execute esse contrato em modo de relatório antes de permitir que os agentes o executem. Deixe o serviço calcular o que restauraria e se rejeitaria a solicitação. Compare essas decisões com as ações reais dos incidentes. Isso revela registros de proveniência ausentes e caminhos manuais ocultos sem dar à automação a chance de sobrescrever a produção.

Depois, torne a rejeição algo rotineiro. Um rollback desatualizado deve criar um item de incidente compreensível com a release e a geração observadas, não uma falha misteriosa que incentive alguém a contornar o endpoint. O endpoint conquista confiança quando recusa uma solicitação insegura de forma consistente, inclusive quando a solicitação vem das pessoas que o criaram.

O primeiro campo a adicionar não é `force`. É `expected_target_generation`. Quando suas implantações carregarem esse fato e preservarem a base original, um agente poderá reverter sua própria release com um limite claro. Até lá, o rollback autônomo será um comando de implantação antigo apontado para um alvo em movimento.
