8 min de leitura

Erros do broker de segredos exigem provas

Projete erros do broker de segredos em torno de evidências de execução para separar repetições seguras de resultados remotos desconhecidos.

Erros do broker de segredos exigem provas

Um broker de segredos deve informar o que seus registros comprovam, não o que uma exceção sugere. Se ele não consegue provar que a operação remota nem chegou a ser tentada, não deve descrever a chamada como uma falha de pré-execução. Assim que bytes da requisição ou uma solicitação exec de SSH puderem ter chegado ao outro lado, o resultado pode ser desconhecido, mesmo quando o erro local diz "timeout" ou "connection reset".

Essa diferença decide se um agente cria um segundo pagamento, faz a rotação da mesma credencial duas vezes, implanta novamente a mesma versão ou repete com segurança um trabalho que nunca saiu da máquina. Um contrato de erro útil carrega dois fatos independentes: onde o broker parou e o que ele sabe sobre a execução remota. Um único campo chamado "transient" não consegue representar os dois.

O desenho a seguir vale para chamadas HTTP e comandos SSH. Ele também pressupõe que o broker mantém um registro durável da ação. Um estado que existe apenas na memória pode melhorar a mensagem de erro, mas não justifica uma nova tentativa depois que o broker ou o chamador reinicia.

A causa não é o resultado

Toda falha precisa de uma fase, um resultado e uma orientação de nova tentativa. A fase indica ao operador onde investigar. O resultado informa ao chamador se o sistema remoto pode ter mudado de estado. A orientação diz à automação o que ela pode fazer agora, com base nas evidências registradas e na semântica da operação.

Use cinco fases públicas:

  • validation: a ação foi recusada antes de o broker escolher ou usar um segredo.
  • credential_injection: o broker não conseguiu obter, autorizar ou anexar a credencial.
  • connection_setup: resolução de nome, roteamento, TCP, TLS, transporte SSH, verificação do host ou autenticação remota falharam antes do envio.
  • remote_execution: o broker enviou a operação e está esperando o resultado remoto ou já o recebeu.
  • result_delivery: o broker registrou o resultado remoto, mas não conseguiu entregá-lo intacto ao chamador.

Essas fases servem para diagnóstico, não são uma política de repetição. Um erro connection_setup pode provar que nenhuma requisição da aplicação foi enviada, enquanto a quebra de uma conexão reutilizada pode deixar o broker sem saber se o outro lado recebeu a escrita. Um erro result_delivery pode acompanhar um sucesso remoto conhecido. Tratar ambos como erro genérico de rede elimina o fato de que o chamador precisa.

Use quatro estados de resultado:

  • not_attempted: evidências duráveis mostram que a operação remota não cruzou o limite de envio.
  • rejected: o sistema remoto devolveu uma recusa completa e oficial, sem declarar sucesso.
  • committed: o broker tem um resultado completo e oficial da operação.
  • unknown: o envio pode ter ocorrido, mas o broker não tem um resultado completo que resolva a operação.

committed não significa sucesso. Um comando que termina com status 23 ou uma requisição HTTP que recebe uma resposta 500 completa têm resultado conhecido. A aplicação remota chegou ao ponto de conseguir responder. Chamar isso de "falha ao executar" convida uma duplicação.

A orientação de nova tentativa deve ser igualmente explícita: never, after_correction, backoff, same_idempotency_key, reconcile ou fetch_result. O broker a calcula usando as evidências, a semântica do método, as garantias remotas e o estado do diário de resultados. O chamador nunca deve deduzi-la de uma string de erro.

Não junte rejected e committed em um único valor público known. Uma recusa pode permitir uma requisição corrigida, enquanto um resultado confirmado exige que o chamador o consuma ou confira. Os dois trazem certeza sobre a tentativa, mas levam a fluxos diferentes.

Uma aceitação assíncrona precisa de mais uma evidência, não de outro valor de resultado. Uma resposta HTTP 202 completa é um resultado confirmado para o envio, não prova que o trabalho na fila terminou. Registre o identificador do trabalho e o recurso de status fornecidos pelo serviço, e acompanhe o trabalho como outra operação. Repetir o envio porque o trabalho continua pendente pode colocá-lo duas vezes na fila.

Essa separação deixa nítida uma distinção que muitos SDKs confundem. A causa do erro responde "o que quebrou localmente?". O resultado remoto responde "o que já pode ter acontecido?". Um mecanismo de repetição que lê apenas a causa não é seguro.

Validação e injeção falham antes do envio

Erros de validação só são falhas reais de pré-execução enquanto o broker não tiver aberto um canal capaz de carregar a ação. Recuse destinos malformados, métodos sem suporte, campos ausentes, cargas grandes demais, referências desconhecidas de credenciais e substituições proibidas de cabeçalhos antes de resolver um host ou acessar um segredo. Registre phase=validation, outcome=not_attempted e, em geral, retry=after_correction.

Uma repetição automática não corrige uma falha de validação determinística. Repetir a mesma carga inválida desperdiça capacidade e pode esconder um ciclo do agente. Retorne um código estável como INVALID_TARGET, UNSUPPORTED_ACTION ou PAYLOAD_LIMIT, além do caminho do campo que o chamador pode corrigir. Não devolva a referência recusada se o nome dela contiver informação sensível.

A injeção de credenciais ainda é uma etapa prévia quando o broker falha antes de liberar qualquer byte da requisição remota. Cofre bloqueado, aprovação negada, credencial ausente, modo de injeção sem suporte e falha local ao descriptografar uma chave pertencem a essa fase. O resultado continua not_attempted, mas a orientação muda. Um cofre bloqueado pode permitir nova tentativa após uma ação do usuário; uma aprovação negada normalmente deve ser never para aquela invocação; um segredo ausente exige correção.

Mantenha o material da credencial fora do erro e do registro da ação. Registre o identificador da credencial ou uma etiqueta de versão não secreta, o modo de injeção e a decisão que interrompeu a chamada. Registrar um cabeçalho Authorization pronto para provar que a injeção ocorreu destrói o propósito do broker.

Existe um limite sutil. Se o broker monta uma requisição HTTP completa com a credencial em um buffer privado e falha antes de escrever, a ação remota ainda não foi tentada. Se ele entrega esse buffer a uma API de transporte e a API retorna uma escrita parcial ou ambígua, a injeção terminou e o envio pode ter começado. Classifique a falha pelo último limite comprovado, não pela função cuja pilha capturou a exceção.

A etapa prévia também precisa de uma cópia da configuração. Se a validação usa uma definição de rota e o envio depois lê uma definição modificada, as evidências deixam de descrever a ação executada. Antes da autorização, vincule à invocação o destino normalizado, a versão da credencial, o modo de injeção permitido e a impressão digital da requisição. Se qualquer valor vinculado mudar, crie uma nova invocação em vez de alterar o registro anterior.

Isso importa durante a aprovação humana. Um cartão de aprovação pode ficar aberto enquanto um agente ou uma recarga de configuração muda o corpo, o host ou o segredo selecionado. O broker deve aprovar a impressão digital que será enviada e verificá-la novamente logo antes da escrita. Uma diferença é validation/not_attempted, não uma justificativa para enviar a nova requisição com uma aprovação antiga.

A conexão precisa de um ponto final preciso

Uma nova conexão que falha antes da existência de um canal da aplicação geralmente comprova not_attempted. Falhas de DNS e rota, conexão TCP recusada, rejeição de certificado TLS ou chave de host SSH e falha de autenticação SSH acontecem antes que uma requisição HTTP ou um comando SSH possa executar. Registre o estágio exato concluído para que o chamador distinga um nome de host incorreto de credenciais recusadas sem ver o segredo.

A frase "a conexão falhou" é ampla demais para um cliente HTTP com conexões reaproveitadas. Quando o broker obtém uma conexão existente, a configuração já terminou. Uma escrita pode falhar porque o outro lado fechou um socket ocioso. O sistema operacional pode relatar um pipe quebrado depois que alguns bytes chegaram ao destino, ou depois que o destino recebeu a requisição inteira, mas antes que o cliente percebesse o fechamento. Isso pertence a remote_execution com outcome=unknown, a menos que o transporte forneça uma prova mais forte.

Não use "a chamada de escrita local confirmou zero bytes" como prova de que o sistema remoto não recebeu nada. Uma API com buffer pode aceitar bytes localmente antes de uma falha posterior, e uma escrita malsucedida diz pouco sobre o que o outro lado já leu. O limite útil de envio fica na primeira entrega a um transporte capaz de levar dados da aplicação. Depois desse ponto, o resultado padrão vira unknown.

A conexão também termina em pontos diferentes para HTTP e SSH. Em HTTPS, conclua DNS, TCP, TLS, a verificação do certificado e qualquer túnel de proxy antes do envio. Em SSH, conclua a negociação do transporte, a verificação do host, a autenticação do usuário, a criação do canal de sessão e toda preparação necessária do ambiente. Nada disso prova se um comando posterior começou, mas uma falha nessas etapas pode provar que ele nunca foi solicitado.

Um redirecionamento cria um segundo limite de envio. Uma resposta 307 ou 308 completa resolve a primeira troca HTTP, mas segui-la cria uma nova requisição para outro destino. Valide novamente o destino, o escopo da credencial e o método antes dessa requisição. Nunca encaminhe uma credencial de autorização entre origens só porque uma biblioteca segue redirecionamentos automaticamente.

Um proxy HTTP adiciona outro observador, mas não elimina a incerteza. Um túnel bem-sucedido só prova que o proxy abriu um caminho. Um proxy de encaminhamento pode retornar um erro completo sobre sua própria tentativa, e a RFC 9209 pode descrever onde o encaminhamento falhou, mas o resultado na origem ainda pode ser desconhecido. Registre qual salto produziu a evidência e não apresente a certeza do intermediário sobre sua resposta como certeza sobre os efeitos na origem.

Um broker pode repetir internamente a configuração da conexão quando cada tentativa tem seu próprio registro e nenhuma cruzou o limite de envio. Ele deve limitar as tentativas e mostrá-las em um único registro de ação:

{"attempts":[{"n":1,"stage":"tcp_connect","outcome":"not_attempted","code":"ECONNREFUSED"},{"n":2,"stage":"tls_handshake","outcome":"not_attempted","code":"CERT_EXPIRED"}]}

O erro final não deve apagar as evidências anteriores. Ele deve dizer que a operação permaneceu sem tentativa nos dois casos e precisa de correção.

O envio muda o ônus da prova

O momento do envio deve ser uma transição de estado explícita e durável. Antes da primeira entrega ao transporte, acrescente dispatch_started ao registro da ação e force sua persistência conforme a garantia do sistema. Se o processo cair depois da escrita, mas antes de registrar a transição, uma reinicialização pode chamar a operação incorretamente de não tentada.

O registro prévio rigoroso aumenta a latência, então equipes ficam tentadas a registrar depois de enviar. Essa recomendação é popular porque o caminho normal fica mais rápido e o código parece mais simples. Ela está errada para ações não idempotentes. A falha rara cai exatamente no intervalo em que o broker precisa escolher entre perder o trabalho e duplicá-lo.

O estado escrito com antecedência não prova que o sistema remoto recebeu a requisição. Ele desloca a incerteza de propósito para o lado seguro. Depois de dispatch_started, o resultado começa como unknown. Uma resposta oficial posterior pode mudá-lo para rejected ou committed. O broker nunca volta a not_attempted.

Em HTTP, o envio começa antes que o primeiro byte da requisição entre na conexão. Acompanhe se o broker enviou os cabeçalhos, enviou o corpo completo, recebeu os cabeçalhos da resposta e recebeu um corpo completo. Esses marcadores ajudam no diagnóstico, mas request_body_sent=true ainda não prova que a aplicação processou a requisição. Da mesma forma, request_body_sent=false não prova que ela não fez nada; um servidor pode recusar ou agir com base nos cabeçalhos antes de ler todo o corpo.

Em SSH, o envio começa antes que a solicitação de canal exec entre no transporte autenticado. Use want reply=true. A RFC 4254 diz que o servidor responde com sucesso ou falha do canal, mas o sucesso significa apenas que ele aceitou a solicitação para iniciar o comando. Não significa que o comando terminou nem que seus efeitos podem ser repetidos.

Um cancelamento depois do envio não é falha prévia. Se o chamador esgota o prazo e fecha o canal, o processo remoto pode continuar executando. Informe CALLER_CANCELLED como causa local e mantenha outcome=unknown até que um resultado remoto registrado resolva a questão. O cancelamento descreve o interesse do chamador, não o estado remoto.

Requisições em lote precisam de um resultado por item. Se o broker envia cinco mudanças em uma requisição HTTP e recebe uma resposta completa que só resolve quatro, não pode atribuir com segurança uma única orientação ao lote. Registre o resultado do transporte pai e cinco resultados filhos. Repita apenas o item cujos registros e contrato remoto permitem, ou confira todo o lote quando a API remota aplica as mudanças de forma atômica.

A mesma regra vale para um script de shell enviado por SSH. Um status de saída cobre o processo do script, não necessariamente cada efeito que ele tentou produzir. Se os chamadores precisam de decisões por ação, dê a cada operação seu próprio identificador remoto e registro de resultado, em vez de deduzir o progresso de stdout.

Um resultado remoto completo resolve a execução

Aprove o processo uma vez
A primeira chamada mostra a autoridade da assinatura, e a autorização dura até o processo terminar.

Uma resposta oficial e com enquadramento completo transforma a incerteza em um resultado conhecido. Para HTTP, registre o status final, os cabeçalhos não secretos selecionados, o corpo completo ou seu resumo e a conclusão do enquadramento. Para SSH, registre a aceitação do comando, o estado completo de stdout e stderr, o status de saída ou o sinal quando presentes e o fechamento do canal.

A RFC 9112 exige que o cliente registre uma resposta HTTP como incompleta quando a conexão fecha cedo ou a decodificação em blocos falha. Um broker de segredos deve aplicar uma regra mais rígida: nunca expor um corpo parcial como resultado remoto completo, mesmo quando os primeiros bytes contêm um JSON plausível. Devolva o conteúdo parcial somente em um campo de diagnóstico marcado com clareza, ou descarte-o se puder conter dados sensíveis.

O status HTTP sozinho não define se é seguro repetir a ação da aplicação. Um 401 completo prova que o servidor recusou aquelas credenciais para a requisição, então o broker pode marcar rejected; repetir sem alterar as credenciais é inútil. Um 429 ou 503 completo pode permitir backoff quando o método é seguro para repetir e a resposta fornece um prazo adequado. Um 500 completo é conhecido, mas a aplicação pode ter mudado o estado antes de gerá-lo. Não transforme todo 5xx em permissão para repetir um POST.

A RFC 9110 define idempotência pelo efeito pretendido de várias requisições idênticas e permite a repetição automática de métodos idempotentes após uma falha de comunicação. A condição importante é "reconhecidamente idempotente". Nomes de método são evidência, não magia. Um endpoint GET mal projetado que aciona uma implantação continua inseguro apesar do método; um PUT bem implementado pode ser repetível mesmo alterando o estado.

Respostas HTTP informativas não resolvem a ação. 100 Continue permite que o cliente envie o corpo, mas não diz nada sobre o resultado final da aplicação. Outras respostas 1xx também deixam a invocação em andamento. Apenas uma resposta final completa, ou um recibo de aplicação mais forte cujo contrato o broker entende, pode tirar o resultado de unknown.

Completude e autenticidade da resposta precisam andar juntas. Uma resposta perfeitamente enquadrada vinda da identidade TLS errada, de um host SSH não confiável ou de um proxy inesperado não é uma evidência oficial sobre o destino pretendido. A verificação de identidade normalmente termina durante a conexão, mas sessões retomadas e grupos de conexão ainda precisam vincular a identidade verificada do outro lado ao registro da ação.

A RFC 9209 define http_response_incomplete para um intermediário que recebeu uma resposta parcial do próximo salto. O status 502 recomendado ajuda na compatibilidade HTTP, mas um 502 isolado perde as evidências do resultado. Mantenha o outcome=unknown estruturado do broker junto de qualquer status mapeado.

O SSH tem uma armadilha parecida. A RFC 4254 recomenda que um servidor retorne exit-status, mas não obriga. Se o canal fecha depois de stdout sem status de saída ou sinal, o broker sabe que o fluxo terminou, mas não sabe se o comando teve sucesso. Retorne REMOTE_RESULT_INCOMPLETE e escolha unknown, a menos que o contrato da ação defina outro marcador oficial de conclusão.

A entrega do resultado não pode repetir a execução

A entrega só começa depois que o broker armazena de forma durável um resultado remoto resolvido. Se a serialização para o chamador falha, o pipe MCP fecha ou o processo chamador termina, a operação remota não volta a ser desconhecida. Informe phase=result_delivery, preserve outcome=committed ou rejected e defina retry=fetch_result.

Essa fase precisa de um identificador de invocação que permita ao chamador recuperar o resultado armazenado. Um novo envio da ação não é recuperação de resultado. Separe as duas operações para que uma biblioteca genérica não transforme sem querer um pipe de resposta quebrado em uma segunda chamada remota.

A ordem das escritas importa:

  1. Concluir e validar a resposta remota.
  2. Acrescentar o resultado resolvido e seu resumo ao registro durável.
  3. Confirmar o resultado recuperável sob o identificador da invocação.
  4. Entregar o resultado ao chamador.

Se a etapa 4 falhar, as etapas 2 e 3 provam o que aconteceu. Se o broker entrega primeiro e registra depois, uma queda pode deixar o chamador com sucesso enquanto a auditoria diz resultado desconhecido. Isso é um defeito de auditoria, mesmo sem causar repetição imediata.

Resultados grandes ou transmitidos em fluxo precisam da mesma regra. Armazene os blocos com números de sequência e um marcador final de completude. O chamador pode retomar a entrega a partir do último bloco verificado, mas o broker não deve declarar o resultado completo antes de obter o terminador, o tamanho declarado ou o fechamento próprio do protocolo que comprove o fim.

A confirmação do chamador é útil para retenção, não para o resultado remoto. Marque RESULT_DELIVERED apenas depois que o protocolo voltado ao chamador confirmar a entrega completa. Se não houver confirmação, mantenha o resultado disponível até a política de retenção expirar e trate leituras repetidas como leituras. Nunca execute a ação novamente para reconstruir um resultado que o broker escolheu não guardar.

O armazenamento do resultado pode falhar após a conclusão remota. Se o broker tem a resposta completa na memória, mas não consegue confirmá-la, sabe mais do que um simples unknown, porém as evidências não sobreviverão a uma queda. Retorne phase=result_delivery, inclua outcome=committed somente se o contrato de durabilidade permitir essa afirmação com o registro atual e exija atenção imediata do operador. A correção certa é reservar capacidade e testar falhas de armazenamento, não repetir a ação remota.

Idempotência é um contrato remoto

Verifique o rastro sem conexão
sp audit verify confere a cadeia criptografada de hashes sem precisar da chave do cofre.

Uma chave de idempotência torna um resultado desconhecido repetível somente quando o serviço remoto promete vinculá-la a uma operação lógica. Gerar um UUID no broker e registrá-lo não resolve nada por si só. O endpoint remoto precisa aceitar a chave, comparar a impressão digital da requisição, guardar o primeiro resultado resolvido por tempo suficiente e devolvê-lo em uma repetição.

O broker deve armazenar quatro fatos antes do envio: chave de idempotência, impressão digital da requisição, escopo remoto e informações de validade ou retenção quando publicadas pelo serviço. Na repetição, precisa reutilizar a mesma chave e uma impressão digital idêntica. Reutilizar a chave com um corpo alterado deve falhar localmente com IDEMPOTENCY_MISMATCH.

Não acrescente em silêncio um cabeçalho de idempotência a endpoints que não declaram sua semântica. Alguns serviços ignoram cabeçalhos desconhecidos. Outros limitam as chaves por conta ou rota. Uma política de repetição precisa de conhecimento configurado e revisado do contrato remoto, não de esperança baseada no nome de um cabeçalho.

Janelas de retenção fazem parte do contrato. Se um serviço esquece as chaves depois de um dia, uma repetição posterior pode criar um novo efeito mesmo parecendo idêntica ao broker. Armazene a primeira validade segura, interrompa as repetições automáticas antes dela e faça a conferência depois. Quando o serviço não publica garantia de retenção, trate a chave como útil apenas dentro de uma janela conservadora configurada.

A concorrência pode derrotar um desenho correto em execução sequencial. Dois workers podem ler o mesmo registro desconhecido e decidir repetir com a mesma chave. Um contrato remoto de desduplicação correto deveria reuni-los, mas o broker ainda deve obter um lease para a invocação, registrar a geração da repetição e permitir uma única tentativa ativa. Isso reduz a carga e mantém o diário compreensível.

Há apenas três caminhos seguros a partir de unknown:

  • Repetir uma operação cuja semântica é reconhecidamente idempotente.
  • Repetir com a mesma chave sob um contrato remoto de desduplicação verificado.
  • Conferir o estado remoto com um identificador estável e depois decidir se uma nova ação é necessária.

Todo o resto para para revisão. Isso pode parecer conservador enquanto um agente espera, mas efeitos duplicados custam mais que uma pausa visível.

Requisições HTTP condicionais podem reforçar o contrato. If-Match com uma etiqueta de entidade conhecida pode fazer uma atualização falhar se o recurso mudou, enquanto If-None-Match: * pode impedir a criação de um segundo recurso no mesmo destino. Elas não resolvem toda duplicação porque o modelo de recursos do endpoint continua relevante, mas fornecem evidência imposta pelo servidor no lugar de suposição do cliente.

Comandos SSH raramente oferecem uma chave de idempotência no protocolo. Coloque a repetibilidade no contrato de aplicação do comando: crie uma implantação com um identificador único de versão, escreva com uma comparação atômica ou execute uma consulta que confirme o estado pretendido. Nunca suponha que um comando shell é seguro porque não produziu saída.

O envelope de erro deve carregar evidências

Bloqueie o envio do cofre fechado
A porta do cofre recusa toda ação enquanto as credenciais criptografadas permanecem bloqueadas.

O chamador precisa de um contrato estável para máquinas e de uma mensagem curta para pessoas. Mantenha exceções da biblioteca de transporte em um campo interno de diagnóstico, pois os nomes mudam entre plataformas e expõem detalhes de implementação. O envelope público deve se parecer com isto:

{"invocation_id":"act_01J...","error":{"code":"REMOTE_OUTCOME_UNKNOWN","phase":"remote_execution","outcome":"unknown","retry":"same_idempotency_key","message":"Connection closed before a complete response was recorded."},"evidence":{"dispatch_started":true,"request_complete":true,"response_headers_received":false,"response_complete":false,"idempotency":{"key":"req_01J...","scope":"payments.create","fingerprint":"sha256:8b1...","remote_contract":"configured"}}}

Mantenha code, phase, outcome e retry como enumerações fechadas. Acrescente novos campos de evidência sem mudar o significado dos existentes. Os chamadores podem ramificar o tratamento pelas enumerações e mostrar message a uma pessoa. Não devem interpretar o texto da mensagem.

As evidências precisam dizer como o broker sabe, não apenas repetir a conclusão. Campos úteis incluem número da tentativa, identificador da conexão, sequência do diário de envio, impressão digital da requisição, marcador de conclusão do protocolo, identificador remoto, resumo da resposta, status de saída e identificador do registro de resultado. Exclua segredos, cabeçalhos completos de autorização, chaves privadas e corpos remotos sem filtragem.

Persista as transições como eventos apenas acrescentados e depois projete o estado atual:

ACTION_ACCEPTED
PREFLIGHT_VALIDATED
CREDENTIAL_AUTHORIZED
DISPATCH_STARTED
REQUEST_SENT
REMOTE_RESPONSE_STARTED
REMOTE_RESPONSE_COMPLETE
RESULT_COMMITTED
RESULT_DELIVERED

Uma ação que termina depois de PREFLIGHT_VALIDATED é comprovadamente não tentada. Uma ação que termina depois de DISPATCH_STARTED, mas antes de uma conclusão oficial, continua desconhecida. Uma ação com RESULT_COMMITTED sobrevive a uma entrega malsucedida sem outra execução remota.

A projeção deve recusar regressões impossíveis. unknown pode virar rejected ou committed quando chegam evidências tardias, mas committed não pode virar not_attempted. Um segundo observador pode anexar um resultado de conferência, porém não deve reescrever a tentativa original como se o envio nunca tivesse ocorrido.

Registre números de sequência monotônicos em vez de depender da ordem do relógio. Relógios podem mudar, e eventos de componentes concorrentes podem chegar atrasados. Horários ajudam operadores a correlacionar sistemas, mas a sequência do diário define qual transição durável ocorreu primeiro. Anexe uma origem e uma sequência local às evidências remotas importadas em vez de inseri-las no meio do histórico.

As evidências também precisam declarar sua fonte de confiança. transport_observed, remote_response, remote_query e operator_attested informam ao código posterior por que o resultado mudou. Um operador pode resolver legitimamente uma tentativa desconhecida depois de verificar o sistema remoto, mas esse fato não deve se passar por uma resposta recebida pelo broker na conexão original.

Integridade da auditoria e evidência de resultado resolvem problemas diferentes. Uma cadeia de hashes pode provar que eventos registrados não foram alterados depois, mas não prova que o broker registrou todos os eventos nem que a aplicação remota respeitou a requisição. O log de auditoria criptografado, encadeado por hashes e cego para escrita do Sallyport, junto das visualizações separadas de sessões e atividades, fornece um lugar durável para manter essas transições; o resultado da ação ainda precisa do contrato de fase e resultado descrito aqui.

O código de repetição deve ser simples e testável

O mecanismo de repetição deve consumir a orientação já derivada das evidências. Ele pode adicionar limites de frequência e de tentativas, mas não deve transformar uma orientação insegura porque uma exceção parece temporária.

decide(record, operation):
  if record.retry == "fetch_result":
    return FETCH(record.invocation_id)

  if record.outcome == "not_attempted":
    if record.retry == "backoff":
      return RETRY_NEW_ATTEMPT
    return STOP_FOR_CORRECTION

  if record.outcome == "unknown":
    if operation.idempotent:
      return RETRY_NEW_ATTEMPT
    if record.retry == "same_idempotency_key" and
       operation.fingerprint == record.fingerprint:
      return RETRY_SAME_KEY
    return RECONCILE

  if record.outcome == "rejected" and record.retry == "backoff":
    return RETRY_WHEN_ALLOWED

  return RETURN_RECORDED_RESULT

Teste transições, não classes de exceção. Injete uma falha antes do acesso à credencial, durante TLS, antes da primeira escrita, depois de escrever a requisição completa, no meio dos cabeçalhos da resposta, no meio de um corpo enquadrado, depois da confirmação do resultado e durante a entrega. Encerre o broker entre cada par de transições duráveis e verifique que a recuperação nunca declara not_attempted após DISPATCH_STARTED.

Adicione comportamentos remotos hostis. Faça o servidor aplicar o efeito e fechar sem resposta. Faça-o retornar 500 depois de confirmar. Faça-o respeitar uma chave de idempotência, ignorá-la e recusar a mesma chave com uma carga alterada. Para SSH, feche depois de aceitar exec, omita exit-status e envie um status de saída antes de interromper o fluxo. O resultado esperado deve seguir as evidências em todos os casos.

As métricas devem contar a fase e o resultado separadamente. Um aumento de connection_setup/not_attempted aponta para roteamento, certificados ou autenticação. Um aumento de remote_execution/unknown exige conferência e pode revelar um problema de confiabilidade remota. Juntar tudo como "falhas do broker" esconde a causa operacional e o risco de duplicação.

Não permita que um SDK amigável apague o contrato. Se ele precisar lançar exceções, anexe o envelope completo e permita repetição automática apenas para backoff ou same_idempotency_key. O chamador deveria precisar escrever código claramente inseguro para repetir uma ação unknown/reconcile.

Testes de recuperação devem incluir workers concorrentes e leases vencidos. Pause um worker depois que ele obtiver o lease de repetição, deixe o lease vencer e inicie outro. Quando o primeiro continuar, sua verificação de geração deve impedi-lo antes do envio. Sem essa verificação, até uma classificação perfeita pode criar duplicações concorrentes.

Trate limites de repetição como parte do registro da ação, não como contadores locais do processo. Reinicializações não devem zerar o número de tentativas nem a validade da chave remota. Quando o limite acabar, retorne as últimas evidências e exija conferência; trocar o código por um genérico "número máximo de tentativas excedido" descartaria o diagnóstico mais seguro.

O estado mais difícil deve continuar desconfortável. Quando o registro diz que o envio começou e nenhuma conclusão oficial chegou, o broker não conhece o resultado remoto. Preserve esse fato, confira o estado e recuse transformar a ausência de evidência em permissão.

FAQ

Um timeout pode provar que uma ação remota não executou?

Um timeout só prova que um prazo expirou. Ele é uma falha prévia apenas quando registros duráveis mostram que o envio nunca começou; depois do envio, trate o resultado como desconhecido até outra evidência resolvê-lo.

O broker deve repetir toda requisição GET malsucedida?

Não. A RFC 9110 define GET como idempotente pela semântica pretendida, mas um endpoint mal projetado ainda pode causar efeitos. Repita somente quando o contrato do endpoint e as evidências do broker permitirem.

É seguro repetir uma resposta HTTP 500?

Não apenas pelo status. Um 500 completo é um resultado remoto conhecido, mas a aplicação pode ter confirmado uma mudança antes de gerá-lo; a semântica do método ou a desduplicação remota precisa justificar a repetição.

Uma chave de idempotência sempre impede ações duplicadas?

Não. O serviço remoto precisa reconhecer a chave, vinculá-la à impressão digital da requisição e devolver o resultado armazenado nas repetições. Uma chave existente apenas no log do broker fornece correlação, não desduplicação.

Qual é a resposta mais segura a um resultado remoto desconhecido?

Primeiro consulte o estado remoto com um identificador estável. Repita apenas se a conferência não mostrar efeitos, a operação for idempotente ou um contrato remoto verificado aceitar a mesma chave e impressão digital.

Por que separar a conexão da execução remota?

A separação mostra se uma requisição pode ter chegado ao outro lado. Uma falha TLS em uma nova conexão pode provar que não houve tentativa, enquanto uma queda após a escrita em uma conexão reutilizada pode deixar o resultado desconhecido.

Como informar uma falha durante a entrega do resultado?

Registre phase=result_delivery e preserve o resultado remoto já resolvido. O chamador deve recuperar o resultado armazenado pelo identificador da invocação, em vez de enviar a ação novamente.

O fechamento de um canal SSH comprova a conclusão do comando?

Não sozinho. A RFC 4254 recomenda um status de saída, mas não o exige; um fechamento sem status, sinal ou marcador da aplicação pode deixar o resultado desconhecido.

Quais evidências pertencem a um erro do broker de segredos?

Inclua o identificador da invocação, fase, resultado, orientação de repetição, estado de envio, impressão digital, marcadores de conclusão e identificador do resultado quando existir. Exclua credenciais e dados remotos sensíveis sem filtragem.

Como uma equipe pode testar se as repetições são seguras?

Encerre o broker em cada transição durável e injete falhas antes do envio, nas escritas e no enquadramento, após confirmar o resultado e durante a entrega. Verifique que nenhum registro enviado volte ao estado não tentado.

Sallyport

O Sallyport executa chamadas de API e comandos SSH pelo seu agente de IA. As chaves ficam em um cofre local no seu Mac; você aprova cada execução e toda ação vai para um registro selado.

© 2026 Sallyport · Código aberto sob Apache-2.0 · Oleg Sotnikov