# O que os testes de encerramento à força comprovam sobre um gateway de agente?

Um gateway que mantém credenciais para um agente de IA precisa continuar seguro quando o processo desaparece no pior momento possível. Um teste do caminho feliz pode mostrar que uma aprovação aparece, uma solicitação funciona e um registro de auditoria existe depois. Ele não mostra se uma credencial escapou durante uma solicitação interrompida nem se a trilha de auditoria afirma que um trabalho foi concluído quando isso nunca aconteceu.

Os testes de encerramento à força revelam as lacunas entre afirmações como «a solicitação começou», «a credencial foi anexada», «o sistema remoto a recebeu» e «o registro é durável». Essas afirmações são fatos separados. Tratá-las como um único evento é o que leva equipes a publicar um gateway de ações que parece controlado até que uma falha transforme uma nova tentativa comum em uma implantação duplicada ou em uma chamada de saída impossível de rastrear.

Em um gateway para macOS, diferencie uma saída normal da morte do processo. A Apple documenta a terminação normal de aplicativos como um caminho do ciclo de vida em que o aplicativo pode salvar o estado e executar o tratamento de encerramento. Uma terminação forçada pode não conceder esse tempo. A Apple também identifica SIGKILL como um tipo de terminação que pode ocorrer quando uma pessoa encerra um aplicativo à força. Seu plano de testes deve presumir que handlers de limpeza, gravações adiadas e telemetria de melhor esforço não serão executados.

A pergunta útil não é «o aplicativo reinicia?». A pergunta útil é: em cada ponto de interrupção, você consegue dizer exatamente quais efeitos colaterais são possíveis, quais são impossíveis e quais evidências sobrevivem?

## Um encerramento à força precisa atravessar a limpeza

Um teste de encerramento à força só é válido quando remove a oportunidade de o gateway fazer a limpeza. Se o teste chama uma função de desligamento educada, espera as filas esvaziarem, libera os logs e só então sai, você testou uma terminação ordenada. Isso importa, mas não representa o caso de falha que preocupa as pessoas de segurança.

Use dois modos de terminação e nomeie-os corretamente:

- Uma solicitação de terminação normal testa o tratamento de cancelamento, o fechamento ordenado de arquivos e a forma como a interface informa uma sessão interrompida.
- Um encerramento abrupto testa o estado da memória, dos buffers de arquivo, dos sockets abertos e do trabalho em andamento quando nenhuma limpeza do aplicativo é executada.

No macOS, um harness de teste pode usar `kill -TERM` no primeiro caso e `kill -KILL` no segundo. `SIGTERM` dá ao processo a chance de tratar a terminação. `SIGKILL` não dá. Não chame os dois casos de «encerramento à força» na tabela de resultados, porque eles respondem a perguntas diferentes.

```sh
# Find the gateway process you started for the test.
pgrep -fl Sallyport

# Graceful termination case.
kill -TERM 48192

# Abrupt termination case.
kill -KILL 48192

# Confirm the process is gone.
ps -p 48192
# PID TTY           TIME CMD
# 48192 ttys003    0:00.42 gateway-test
# After SIGKILL, ps should print no process row.
```

O ID do processo não é o identificador do teste. Dê a cada execução um ID de execução, um ID de ação e um ID de solicitação remota. Coloque esses IDs nos registros do fixture e nos registros do gateway. Sem identificadores compartilhados, você acabará comparando timestamps e tentando adivinhar se um POST remoto pertencia à execução que foi encerrada.

Não teste em um endpoint de produção, mesmo que acredite que a chamada seja inofensiva. O objetivo deste trabalho é criar resultados ambíguos de propósito. Monte um receptor que possa pausar antes de aceitar um corpo, depois de ler os cabeçalhos, depois de registrar o corpo ou antes de liberar a resposta. Uma API real de terceiros não oferecerá essas fronteiras de forma confiável e pode impor novas tentativas ou cache que você não solicitou.

## A solicitação precisa de pontos de interrupção explícitos

Você não pode encerrar um processo «durante a injeção da credencial» se a implementação não define o que isso significa. Adicione pontos de espera observáveis ao redor do fluxo da ação. Eles são controles de teste, não uma linguagem de políticas nem um mecanismo de autorização de produção.

Para uma ação HTTP, use uma sequência como esta:

1. O gateway aceita uma descrição de ação autorizada e atribui um ID de ação.
2. Ele grava um registro de intenção, se o projeto exigir isso antes do trabalho de rede.
3. Ele resolve a credencial dentro do processo protegido.
4. Ele monta a solicitação de saída e injeta a credencial.
5. Ele abre a conexão e grava a solicitação.
6. Ele recebe uma parte suficiente da resposta para classificar o resultado remoto.
7. Ele grava um registro de conclusão ou de resultado desconhecido e então libera um resultado para o agente.

Instale uma pausa na fronteira depois de cada operação numerada. A pausa precisa ser observável por um processo separado. Um socket local exclusivo para testes, um pipe nomeado ou um descritor de arquivo controlado pelo harness funcionam. Uma chamada de sleep não funciona bem, porque a variação de tempo transforma um teste de fronteira em uma disputa.

Um protocolo compacto para o fixture pode ter esta aparência:

```json
{
  "action_id": "fq-2026-07-22-017",
  "hold_after": "request_headers_built",
  "release": false
}
```

O gateway pausa depois de criar os cabeçalhos, mas antes de qualquer tentativa de conexão. O harness confirma esse estado, encerra o processo e pergunta ao receptor se ele viu uma conexão. O resultado esperado é zero solicitações de entrada e zero cabeçalhos contendo credenciais. Se o receptor viu uma solicitação nesse ponto, seu checkpoint está tarde demais ou uma tarefa em segundo plano atravessou a fronteira sem ser contabilizada.

Mantenha os checkpoints estreitos. «Antes da comunicação de rede» é amplo demais se a resolução de DNS, a criação da conexão, a negociação TLS e a transmissão do corpo ocorrem em tarefas diferentes. Você não precisa de um checkpoint dentro de cada chamada de biblioteca, mas precisa de estrutura suficiente para dizer se o servidor remoto poderia ter recebido credenciais ou um corpo que altera o estado.

Para SSH, a mesma ideia se aplica, mas com evidências diferentes. Faça uma pausa antes que `sp-ssh` receba uma solicitação de conexão baseada em credencial, depois que o auxiliar começar, mas antes da autenticação, depois da autenticação, mas antes do início do comando, e depois que o comando remoto retornar, mas antes de a conclusão local ser confirmada. Registre o início e a saída do comando remoto em um host de teste descartável. Não deduza que o comando foi executado apenas porque existe uma conexão TCP aberta.

## Encerrar antes da injeção não pode deixar rastros remotos

O caso de falha mais inicial tem o resultado esperado mais limpo: se o gateway morrer antes de anexar uma credencial ou iniciar o transporte, nenhum serviço externo deve ver qualquer coisa dessa ação.

Isso parece óbvio, mas as implementações costumam misturar preparação e envio. Uma biblioteca cliente pode começar uma conexão enquanto outra tarefa busca ou formata um cabeçalho. Um wrapper de novas tentativas pode alocar uma solicitação de saída antes que o código grave o registro de diário pretendido. Um callback de métricas pode gravar uma descrição de ação contendo um parâmetro de consulta com uma URL que o caminho normal de mascaramento nunca vê.

Teste quatro interrupções antes do envio:

- Depois da autorização, mas antes de qualquer busca de credencial.
- Depois da busca da credencial, mas antes da montagem da solicitação.
- Depois da montagem da solicitação, mas antes do início da conexão do socket.
- Depois do início da configuração da conexão, mas antes que qualquer byte contendo credencial deixe o processo.

Cada interrupção tem uma afirmação diferente. Antes da busca da credencial, inspecione diagnósticos e logs seguros em relação à memória para confirmar a ausência de valores ou placeholders que poderiam ser substituídos mais tarde. Depois da busca, inspecione as mesmas saídas e verifique que o segredo nunca ultrapassou o limite do processo. Antes da conexão, o receptor não deve ter nenhum registro de conexão. Durante a configuração da conexão, o receptor pode ver um handshake ou tentativa de conexão malsucedida, mas não pode receber um cabeçalho de autorização, um campo de basic auth, um cabeçalho personalizado de credencial nem uma tentativa de autenticação SSH.

Essa distinção importa porque uma tentativa de conexão não é uma ação autenticada. Não registre «nenhuma ação ocorreu» se a borda remota registrou uma tentativa de conexão. Registre a verdade restrita: nenhum segredo foi enviado e nenhuma solicitação da aplicação foi recebida. Os registros de segurança perdem utilidade quando apagam evidências de que os operadores poderão precisar durante a análise de um incidente.

O projeto do Sallyport torna essa fronteira especialmente importante: o agente nunca deve manter uma credencial em texto simples, enquanto o aplicativo executa a ação HTTP ou SSH baseada em credencial e devolve um resultado. O teste não termina porque o agente não possui o segredo. Ele termina quando um gateway encerrado também não consegue transformar uma ação parcialmente preparada em uma solicitação de saída contendo um segredo.

## A injeção é um evento local, não uma prova de envio

A injeção de credenciais é onde as equipes cometem o erro de contabilidade mais prejudicial. Elas registram «credencial usada» quando o código monta um cabeçalho ou entrega uma identidade a uma biblioteca SSH. Esse evento informa que o gateway se preparou para autenticar. Não prova que algum par recebeu a credencial.

Mantenha pelo menos quatro estados locais separados:

| Estado | O que você pode afirmar honestamente | O que não pode afirmar |
|---|---|---|
| Credencial resolvida | O processo protegido leu uma credencial para esta ação | Que um par a recebeu |
| Solicitação montada | O processo criou uma solicitação contendo credencial na memória | Que uma conexão foi aberta |
| Gravação do transporte iniciada | O processo tentou enviar bytes | Que a aplicação remota os processou |
| Resultado remoto observado | O processo recebeu evidências do lado remoto | Que o registro de conclusão é durável |

Encerre o processo depois que a credencial for resolvida e novamente depois que o objeto da solicitação existir, mas antes do início da gravação no transporte. Nos dois casos, o agente não deve receber nenhum segredo e o fixture remoto não deve registrar nenhuma solicitação contendo credencial. O diário da ação deve mostrar um estado de preparação interrompida ou nenhum registro persistido, dependendo de onde está sua fronteira de durabilidade. Não invente uma ação concluída só para deixar o diário organizado.

Depois, encerre no primeiro ponto em que a biblioteca de transporte pode gravar. Este é o teste incômodo. O processo local pode ter chamado uma função de gravação, mas o kernel, o proxy, a camada TLS ou o serviço remoto pode não ter recebido a solicitação completa. O resultado esperado deve ser `outcome=unknown`, a menos que o receptor tenha evidência positiva de que recebeu ou não recebeu a solicitação.

Evite registrar cabeçalhos completos para facilitar este teste. Isso é um atalho que cria um segundo caminho para o segredo. Em vez disso, faça o fixture calcular um marcador não reversível a partir do valor recebido da credencial e armazene apenas esse marcador. O gateway pode armazenar uma referência da credencial ou um identificador interno. Durante o teste, compare identificadores e marcadores sem imprimir a credencial.

Um registro útil do fixture tem este formato:

```json
{
  "action_id": "fq-2026-07-22-017",
  "connection_seen": true,
  "headers_complete": true,
  "credential_marker": "sha256:7b8c...",
  "body_complete": false,
  "response_sent": false
}
```

Use uma credencial exclusiva para testes, sem valor fora do fixture. Mesmo assim, mantenha-a fora de capturas de tela, do histórico do shell e dos logs comuns. Um segredo descartável ainda é um objeto com formato de segredo, e hábitos de teste costumam chegar ao código de produção.

## A comunicação de rede cria um estado desconhecido honesto

Quando uma solicitação que altera o estado pode sair da máquina, a certeza local termina antes que o sistema remoto necessariamente responda. Esse é o caso que precisa orientar o comportamento de novas tentativas, o texto da interface e a semântica da auditoria.

Considere um POST que pede a um serviço de implantação para iniciar uma versão. O gateway grava a solicitação completa. O serviço persiste o trabalho de implantação. Antes que a resposta chegue ao gateway, você encerra o processo. Depois da reinicialização, existem três realidades possíveis:

1. O serviço nunca recebeu a solicitação.
2. O serviço recebeu a solicitação, mas a rejeitou.
3. O serviço aceitou a solicitação e criou uma implantação.

O gateway local pode não saber qual realidade ocorreu. Um registro marcado como `failed` é falso no caso três. Um registro marcado como `succeeded` é falso nos casos um e dois. Marque-o como `unknown` e preserve o ID da ação, o ID da solicitação remota, o destino, o método e o ponto em que a observação local parou.

É também aqui que novas tentativas cegas causam danos. As equipes gostam da repetição automática porque falhas transitórias de rede são comuns e demonstrações bem-sucedidas parecem tranquilas. Para um GET, repetir costuma ser aceitável se isso não provocar contabilidade no servidor nem problemas de limite de requisições. Para POST, PATCH, comandos remotos ou chamadas de API que gravam dados, repetir exige um mecanismo de idempotência ou uma reconciliação posterior.

Use o recurso de idempotência do sistema remoto quando ele existir. Forneça um token de idempotência específico da ação, não um token reutilizado durante toda a sessão do agente. Se a API remota não oferecer esse recurso, crie um caminho de consulta remoto que responda se o ID da ação foi aceito antes de uma nova tentativa. Se nenhuma das duas opções existir, peça que uma pessoa decida. É menos elegante que a recuperação automática, mas muito mais seguro que executar duas vezes.

Teste esta sequência exata com o receptor:

```text
Gateway writes intent record
Gateway sends POST with action ID fq-2026-07-22-017
Receiver stores action ID and body
Receiver delays its HTTP response
Harness kills gateway with SIGKILL
Gateway restarts
Agent requests retry
Gateway checks receiver for action ID before another POST
```

O resultado deve mostrar uma ação remota armazenada e um registro local com resultado interrompido ou desconhecido, que depois é reconciliado com o estado remoto. Se o segundo POST aparecer antes da verificação de reconciliação, o teste encontrou um erro de repetição. Se o diário apagar a incerteza e deixar apenas um registro de sucesso limpo, encontrou um erro de auditoria.

Não dependa de eventos de fechamento TCP para provar que uma aplicação remota não agiu. Um socket pode ser fechado depois que o par já entregou a solicitação ao código da aplicação. O registro durável da ação no receptor é a evidência que importa.

## A confirmação da auditoria precisa ter uma promessa de durabilidade definida

Um log de auditoria não se torna confiável só porque contém uma cadeia de hashes. Uma cadeia de hashes pode revelar alteração ou remoção dentro da sequência sobrevivente. Ela não recupera um evento que nunca chegou ao armazenamento durável antes da morte do processo.

Escreva a promessa em uma frase. Por exemplo: «Antes de um gateway iniciar uma ação externa que altera o estado, ele registra de forma durável a intenção da ação; depois de observar um resultado, registra esse resultado de forma durável antes de devolvê-lo ao agente». Em seguida, teste os verbos «iniciar» e «duravelmente», em vez de tratar um objeto em memória com timestamp como um registro.

O POSIX define `fsync()` como uma solicitação para transferir os dados de um arquivo para o armazenamento associado e afirma que ela não retorna até que essa ação seja concluída ou relate um erro. O padrão também alerta que as garantias de armazenamento dependem da implementação e da configuração. Esse aviso não é motivo para ignorar a chamada. Ele significa que você precisa ser preciso sobre o que o software pode prometer e testar o comportamento de recuperação que ele controla.

Para um log criptografado e encadeado por hash, teste pelo menos estas interrupções:

- Encerre depois de gravar uma entrada proposta, mas antes da barreira de durabilidade.
- Encerre depois da barreira de durabilidade, mas antes da atualização do índice em memória.
- Encerre depois que uma entrada de conclusão for duravelmente gravada, mas antes que o agente receba a resposta.
- Encerre durante compactação, rotação ou qualquer processo de projeção do diário.

Depois de cada reinicialização, execute o comando de verificação offline e inspecione tanto a sequência bruta da auditoria quanto as projeções visíveis ao usuário. O Sallyport oferece `sp audit verify` para verificar o log de auditoria criptografado e encadeado por hash sem exigir a chave do cofre. Isso o torna útil neste conjunto de testes, mas a verificação deve ser uma entre várias afirmações, não o teste inteiro.

Seus resultados esperados precisam de nuance. A ausência de um registro depois de um encerramento anterior à sua fronteira de durabilidade pode ser aceitável se nenhuma ação externa começou. A ausência de um registro depois que o gateway iniciou uma solicitação que altera o estado não é aceitável se o projeto prometeu uma intenção gravada antecipadamente. Um registro presente com resultado desconhecido costuma estar correto. Um registro que afirma conclusão antes da observação da resposta remota está errado, mesmo que o teste geralmente passe por acaso.

Mantenha o histórico da execução do agente separado do histórico das chamadas de ação ao inspecionar a recuperação. Uma sessão pode terminar abruptamente enquanto várias ações têm resultados diferentes. Uma única entrada de execução dizendo «encerrada» não substitui os registros individuais de uma solicitação que chegou ao sistema remoto e de outra que nunca saiu da memória.

## O estado de aprovação não pode sobreviver ao seu objeto

Uma reinicialização testa o estado de autorização tanto quanto a durabilidade. Se um gateway autoriza determinado processo de agente para uma sessão, encerrá-lo não pode transformar essa autorização em uma permissão reutilizável por um novo processo que faça uma solicitação parecida.

A regra segura mais simples é fazer com que a aprovação de uma sessão pertença a uma identidade de processo observada e morra com essa execução. Depois da reinicialização, um novo processo de agente precisa de uma nova autorização de sessão. Um processo que permaneceu ativo enquanto o gateway reiniciava também precisa de uma nova decisão, a menos que o gateway consiga restabelecer exatamente o vínculo de identidade e o produto documente esse comportamento de propósito. Não restaure aprovação a partir de um registro de cache solto, como nome do comando, diretório de trabalho ou rótulo amigável do processo.

Teste a fronteira de reinicialização com dois agentes que parecem semelhantes na linha de comando, mas diferem na autoridade de assinatura ou na origem do executável. Aprove o primeiro. Interrompa o gateway durante uma ação. Reinicie-o. Em seguida, faça o segundo agente emitir a mesma ação. O gateway precisa mostrar uma nova decisão de aprovação, não herdar a sessão do primeiro agente.

A aprovação por chamada exige outro teste. Encerre o gateway enquanto o cartão de aprovação está visível, reinicie-o e repita a ação. O evento antigo da interface não pode autorizar a nova chamada. Encerre depois que a pessoa aprovar, mas antes da resolução da credencial, e confirme que o novo processo não pode reutilizar a aprovação antiga. Esses testes detectam um erro comum: persistir o booleano «aprovado» sem vinculá-lo a um ID de ação, a uma sessão específica e a uma condição de expiração.

Os controles fixos do Sallyport dão uma forma clara a esse teste. O bloqueio do cofre nega ações enquanto ele está fechado, a autorização da sessão está vinculada a um novo processo de agente e as credenciais selecionadas podem exigir aprovação a cada uso. Os testes de encerramento à força devem provar que essas fronteiras continuam verdadeiras quando a interface, o estado do cofre e o fluxo da ação reiniciam em momentos diferentes.

## Monte uma matriz de resultados antes de executar a suíte

Um teste que apenas diz «encerrar na etapa X» deixa espaço demais para interpretação. Crie uma matriz com linhas para os pontos de interrupção e colunas para os resultados observáveis. Revise o resultado esperado antes de escrever o código do teste. Se a equipe não consegue concordar sobre o que deve acontecer, a implementação ainda não tem um contrato de falha definido.

Use estas colunas:

| Ponto de interrupção | O sistema remoto pode ver uma conexão? | O sistema remoto pode ver credenciais? | Status local permitido | Resultado do agente após a recuperação | Evidência necessária |
|---|---:|---:|---|---|---|
| Antes da busca da credencial | Não | Não | Não iniciado ou interrompido | Nova aprovação ou caminho de repetição | Apenas rastreamento do gateway |
| Depois da resolução da credencial | Não | Não | Preparação interrompida | Nova aprovação ou caminho de repetição | Apenas rastreamento do gateway |
| Durante a gravação da solicitação | Sim | Possivelmente | Desconhecido | Reconciliar antes de repetir | Registro do receptor e registro local |
| Depois da aceitação remota | Sim | Sim | Desconhecido até observar ou reconciliar | Reconciliar antes de repetir | Registro remoto durável |
| Depois da conclusão durável | Sim | Sim | Concluído | Devolver resultado em cache ou reconciliado | Registro de auditoria verificado |

As palavras «possivelmente» e «desconhecido» não são fraquezas. São uma descrição honesta do trabalho distribuído. A palavra perigosa é «falhou» quando o gateway não tem evidência de que o serviço remoto deixou de agir.

Execute cada linha repetidamente, mas não transforme repetição em substituto para controle. Cem encerramentos aleatórios podem não atingir a linha entre um acréscimo no diário e sua barreira de durabilidade. Um único encerramento controlado em um ponto de espera pode estabelecer o comportamento dessa linha. Repita depois para detectar disputas, erros de agendamento e movimentações acidentais do checkpoint.

Colete artefatos dos dois lados em cada execução: a linha do tempo de eventos do harness, a lista durável de ações do fixture remoto, as informações de saída do processo, os diagnósticos de recuperação do gateway, a saída da verificação da auditoria e a saída visível ao agente. Coloque o ID da execução no nome do arquivo. Se um teste falhar, preserve as evidências antes de executá-lo novamente. A segunda execução costuma destruir a única pista útil.

## Trate divergências como trabalho de projeto, não como ruído de teste

Quando um teste de encerramento à força produz uma divergência entre o diário do gateway e o fixture remoto, resista à vontade de alterar a afirmação até que ela passe. A divergência costuma ser justamente a descoberta do teste.

Se o fixture informa que uma solicitação foi aceita e o gateway não registra intenção, mova a fronteira da intenção durável para mais cedo ou impeça a ação antes dessa fronteira. Se o gateway informa conclusão e o fixture não tem nenhuma ação, descubra se o gateway confundiu uma gravação local com um resultado remoto. Se um agente reiniciado consegue agir sem uma nova aprovação, corrija o vínculo de identidade em vez de adicionar um timeout maior.

O melhor resultado não é um painel cheio de testes verdes. É um contrato de falha que um operador consegue usar sob pressão: esta ação definitivamente não saiu da máquina; esta ação pode ter chegado ao sistema remoto e precisa de reconciliação; esta ação foi concluída e seu registro sobreviveu à verificação. Essas são as únicas categorias que tornam defensável uma decisão depois de uma falha.

Execute esta suíte sempre que alterar o fluxo de credenciais, as bibliotecas de transporte, o diário, o comportamento de novas tentativas, a supervisão de processos ou o tratamento de aprovações. Um gateway conquista confiança ao se comportar de forma previsível quando não tem chance de se explicar.
