# Atalhos de aprovação pelo teclado resistentes a acionamentos acidentais

Um controle de aprovação pelo teclado faz parte do limite de autorização. Não é um recurso de conveniência com um rótulo de segurança. Se uma tecla comum do terminal consegue atravessar uma mudança de foco, uma janela modal recém-aberta ou uma fila de entradas repetidas e autorizar uma chamada do agente, o controle não coletou uma decisão. Coletou um momento oportuno.

É fácil colocar essa falha em produção porque o caminho ideal parece excelente. O desenvolvedor inicia uma ação, surge uma janela de aprovação, Return a aceita e todos consideram a interação rápida. Depois, esse mesmo desenvolvedor está lendo a saída do terminal, mantém uma tecla modificadora pressionada enquanto troca de janela ou pressiona Return para fechar um aviso sem relação com a ação. A aprovação chega no momento errado e a ação recebe um consentimento que ninguém deu conscientemente.

## Um atalho de aprovação é um limite de segurança

Um atalho de aprovação precisa representar uma nova decisão atribuível sobre uma ação ativa específica. O fato de o usuário estar com o teclado ou de o processo solicitante ter permissão para fazer o pedido não transforma todo caractere em aprovação.

Há três fatos diferentes que as equipes costumam juntar em um só:

- Um processo pode solicitar uma ação.
- Uma pessoa pode estar diante da máquina.
- Essa pessoa pode aprovar exatamente essa ação agora.

O primeiro fato diz respeito à identidade do processo. O segundo trata da presença local. O terceiro é autorização. Um processo assinado pode estabelecer bem o primeiro fato. Uma janela visível pode sugerir o segundo. Nenhum dos dois prova o terceiro.

Trate a interface de aprovação como uma pequena máquina de estados, com uma entrada estreita para o estado aprovado. Ela deve saber qual solicitação está mostrando, se tem o controle atual da entrada, quando ficou apta a ser ativada pelo teclado e se a solicitação continua válida. Quando qualquer um desses fatos mudar, invalide o caminho antigo para a aprovação.

Um erro comum de implementação coloca o atalho em um manipulador amplo de comandos e verifica apenas se existe uma janela de aprovação. Isso parece inofensivo em uma demonstração. Na prática, significa que um evento entregue tarde pode aprovar uma solicitação diferente, uma solicitação antiga depois de ser substituída ou uma janela que acabou de voltar do segundo plano. O botão não precisava estar visível no instante em que a pessoa pressionou a tecla.

A solução não é remover todos os atalhos. A confirmação apenas com o mouse é ruim para quem usa bem o teclado e incentiva cliques apressados. O caminho é tornar a ativação pelo teclado mais difícil de validar do que o rótulo de um botão. O sistema só deve aceitá-la quando a janela atual, a solicitação atual, a geração atual do foco e a pressão física atual coincidirem.

Essa distinção muda a revisão de código. Pergunte «qual evento exato pode atravessar este limite?» em vez de «Return clica no botão?». Uma boa resposta nomeia a origem do evento, o estado da janela, o identificador da solicitação e os casos de rejeição. «A janela está aberta» não é uma resposta.

## O foco precisa ser comprovado, não presumido

A janela deve descartar a elegibilidade do teclado sempre que perder o foco, mesmo que volte a recebê-lo um instante depois. É durante as transições de foco que uma entrada destinada a uma superfície de um aplicativo acaba sendo interpretada por outra.

Considere uma sequência comum. Um agente executa um comando no terminal. O usuário digita um comando, muda para a documentação e então surge uma janela modal de autorização. O sistema operacional muda a janela ativa enquanto as mãos do usuário ainda estão se movendo. Dependendo da ordem dos eventos e do framework do aplicativo, uma pressão de Return pode chegar depois que a janela modal assumiu o foco, embora a intenção tenha sido formada antes de o usuário vê-la.

A regra insegura é simples: se o botão de autorização for o botão padrão e a janela estiver em primeiro plano, aceite Return. Essa regra trata o fato de estar em primeiro plano como prova de que a pessoa viu e avaliou a solicitação. Ela prova apenas que a janela ocupava a frente da pilha no momento da entrega do evento.

Use uma geração de foco. Quando a janela se tornar ativa, incremente um contador e desarme a ativação pelo teclado. Só comece a aceitar uma pressão válida depois que o aplicativo observar que o foco se estabilizou na geração atual e depois que detectar uma nova pressão do usuário. Ao desativar, limpe imediatamente o estado armado. Não o restaure automaticamente quando o foco voltar.

Isso é deliberadamente conservador. Quem mudar de janela e voltar precisará pressionar o atalho uma vez depois do retorno. Esse toque extra custa pouco. Enviar por acidente uma chamada HTTP com credenciais ou um comando SSH custa muito mais.

A mesma regra vale quando o sistema exibe outra folha modal, um aviso de senha, uma sobreposição de acessibilidade, uma interação com notificação ou uma troca de espaço de trabalho. Seu aplicativo não sabe por que o foco mudou e não deve adivinhar. Ele sabe que suas suposições anteriores já não valem.

Não dependa apenas dos indicadores visuais de foco. Eles mostram ao usuário para onde a entrada parece ir, mas uma decisão de aprovação precisa de uma transição de estado que verifique a janela realmente ativa do aplicativo e a geração atual da janela. O estado visual e o estado de autorização devem mudar juntos, a partir de uma única fonte de verdade.

Um teste manual útil é abrir uma janela de aprovação, manter uma tecla modificadora pressionada, mudar de janela, voltar e pressionar Return imediatamente. Repita com um clique fora da janela, um alerta do sistema e uma troca rápida de aplicativo. Em todas as tentativas, a solicitação deve continuar pendente até que ocorra uma nova ação explícita. Se uma única variação aprovar a ação, o caminho pelo teclado é amplo demais.

## Entradas mantidas pressionadas precisam de uma máquina de estados própria

Uma tecla que já estava pressionada antes de a janela se tornar elegível não pode autorizar a janela. Essa regra cobre a classe de erros que aparece quando alguém mantém Return, Space, Escape ou uma tecla modificadora pressionada enquanto a interface muda por baixo de seus dedos.

As APIs de eventos costumam fornecer as fases de pressionar, repetir e soltar, mas o código do aplicativo tende a reduzi-las a «recebi Return». Essa redução descarta a informação importante. Uma pressão iniciada no terminal tem um significado diferente de uma pressão iniciada depois que a solicitação de aprovação está visível e com foco.

Acompanhe o ciclo físico de cada atalho aceito. A janela só pode aceitar um atalho quando observar um evento de pressão que não seja repetição depois de sua própria época de ativação, enquanto estiver com foco, seguido pela regra de conclusão usada pela plataforma. Se o evento já estiver pressionado quando a janela abrir, crie um bloqueio para essa tecla e espere que ela seja solta. Não deduza uma nova pressão a partir de uma repetição.

Space merece o mesmo cuidado que Return. Muitos controles usam Space para ativação pelo teclado, e as pessoas mantêm Space pressionada para prévias, rolagem ou ações de acessibilidade. Escape também exige atenção. Ele deve cancelar apenas a solicitação ativa na tela e não pode cancelar uma solicitação substituta que tenha aparecido depois que o usuário o pressionou.

Combinações com teclas modificadoras precisam de uma política mais rigorosa. Evite tratar o estado isolado de uma tecla modificadora como permissão e evite atalhos que se sobreponham a hábitos do terminal, como Control-C, Control-D ou Control-R. Se escolher uma combinação, exija que todas as teclas comecem a ser pressionadas depois que a janela for armada e rejeite a combinação se o foco mudar enquanto qualquer uma delas estiver pressionada. Uma combinação iniciada em outra janela não é uma decisão sobre a sua janela.

Um pequeno registro interno basta:

```
requestId: 84f2
focusGeneration: 17
armedAfterEvent: 912
returnIsBlockedUntilUp: true
approvalState: pending
```

Os nomes não importam. A regra importa: um evento precisa ser mais novo que a geração de foco atual da janela e pertencer a um ciclo de entrada novo. As equipes costumam adicionar esse estado depois de um relato de aprovação acidental. Coloque-o antes de adicionar o atalho.

## O tempo das janelas modais transforma entradas normais em autorização

Uma janela modal que aparece no instante errado pode receber uma entrada que a pessoa pretendia enviar para a tarefa subjacente. O perigo vem do tempo dos eventos, não do fato de a janela ter uma mensagem de confirmação bem acabada.

Vale nomear algumas lacunas de tempo. Uma solicitação pode chegar entre a pressão e a soltura de uma tecla. A interface pode ser renderizada antes que o estado do aplicativo termine de associar o identificador da solicitação. Uma solicitação anterior pode ser fechada enquanto um callback enfileirado ainda mantém seu manipulador de conclusão. Uma nova janela pode reutilizar o mesmo objeto de botão, deixando uma ação de teclado antiga apontada para um conteúdo novo.

A falha conhecida se parece com isto:

1. O usuário pressiona Return no terminal para enviar um comando.
2. O processo do agente pede permissão enquanto a pressão ainda está em andamento.
3. O aplicativo apresenta uma janela de aprovação e ativa o botão padrão.
4. A soltura da tecla ou um evento repetido chega ao novo receptor.
5. O aplicativo trata esse evento como aprovação e envia a ação.

O usuário talvez nunca leia a janela. Mesmo assim, o aplicativo grava no histórico de auditoria que o usuário aprovou, o que torna a falha ainda pior. O registro é preciso sobre o caminho do código e falso sobre o ato humano.

Evite isso com uma barreira de ativação. Ao apresentar a janela, registre uma sequência de entrada monotônica ou um carimbo de tempo do subsistema de eventos e recuse eventos que tenham começado antes dessa barreira. Se a plataforma não puder fornecer uma sequência confiável, exija uma pressão completa e nova depois que a janela estiver visivelmente ativa. Prefira o desenho conservador quando a plataforma deixar margem para dúvida.

Use gerações de solicitações além das gerações de entrada. Vincule cada ação de botão e manipulador de teclado a um token imutável da solicitação. Quando a janela fechar, revogue esse token. Quando outra solicitação a substituir, crie um token novo em vez de alterar o objeto antigo. Um callback que carregue um token antigo deve retornar uma negativa, mesmo que uma nova janela esteja visível.

Não tente resolver o tempo apenas com um atraso, como «ignorar Return por 300 milissegundos». Atrasos são populares porque são fáceis de explicar e de programar. Eles falham em máquinas lentas, mudanças rápidas de foco, entradas de acessibilidade e com pessoas que legitimamente demoram mais. Um atraso mede tempo. Você precisa medir a relação entre o evento de entrada e o estado atual da janela.

## A memória muscular do terminal é uma entrada hostil

O trabalho no terminal produz exatamente as teclas nas quais uma janela de aprovação precisa desconfiar. Desenvolvedores pressionam Return para enviar comandos, Control-C para interromper o trabalho, Space para avançar pela saída e Escape para abandonar uma edição. Os agentes tornam a atividade no terminal mais frequente, então a janela de autorização aparece ao lado de um fluxo de entradas habituais.

Não presuma que o terminal está atrás de uma fronteira de aplicativo separada. As pessoas usam terminais incorporados, painéis divididos, shells remotos, janelas em tela cheia e painéis de ferramentas que parecem terminais. Um processo também pode disparar uma solicitação de autorização logo depois que uma saída orienta o desenvolvedor a pressionar uma tecla. A situação visual pode mudar mais rápido que o plano motor do usuário.

O padrão seguro é fazer com que uma janela de aprovação não consuma nenhuma entrada herdada. Ela pode receber uma nova pressão de Return depois que o foco e o armamento estiverem estabelecidos, mas deve rejeitar o Return que enviou um comando do shell, o Space mantido para avançar pela saída e qualquer repetição gerada por uma dessas teclas. Se a interface não puder estabelecer essa distinção, remova o atalho e exija uma confirmação pelo mouse ou por biometria.

Não associe uma aprovação perigosa a comandos com aparência de terminal em um mapa global de comandos. Um listener global vê eventos além da cadeia local de receptores da janela modal e dificulta provar onde a ativação começou. Mantenha o manipulador ligado à janela ativa específica e exija que ela verifique seu próprio token de solicitação antes de pedir que a camada de autorização aja.

É também aqui que copiar e colar merece atenção. Uma quebra de linha colada pode ser texto comum em um terminal e uma ativação em um controle de formulário. Os controles de aprovação devem ignorar a inserção de texto e o despacho de comandos que não tenham um evento físico de atalho válido. Um caractere colado é dado, não consentimento.

Teste com um fluxo real de terminal, não apenas com uma janela sintética. Inicie um comando que peça confirmações repetidas, dispare uma solicitação enquanto pressiona Return e teste depois de usar Control-C e Space. Teste quando o terminal estiver com foco, quando uma janela de documentação estiver com foco e quando a janela de autorização aparecer durante uma troca de janela. O objetivo é reproduzir uma intenção que começou em outro lugar.

## Teste o histórico de eventos, não o botão desenhado

Os testes mais úteis alimentam um reducer com um histórico de eventos hostil e verificam que ele não consegue emitir uma aprovação até que ocorra exatamente a sequência correta. Testes de snapshot podem confirmar que uma janela parece estar com foco. Eles não dizem se um evento antigo atravessou o limite.

Modele a janela com eventos explícitos, como `present`, `focusGained`, `focusLost`, `keyDown`, `keyRepeat`, `keyUp`, `requestRevoked` e `approveClick`. Dê a cada evento um token de solicitação e uma sequência de entrada monotonicamente crescente. O reducer deve produzir um de três resultados: continuar pendente, cancelar ou emitir uma aprovação para um token correspondente.

Este fixture é pequeno o bastante para ficar ao lado do código da interação:

```json
[
  {"seq": 41, "type": "keyDown", "key": "Return"},
  {"seq": 42, "type": "present", "request": "r-19"},
  {"seq": 43, "type": "focusGained", "request": "r-19"},
  {"seq": 44, "type": "keyUp", "key": "Return"},
  {"seq": 45, "type": "keyDown", "key": "Return"},
  {"seq": 46, "type": "keyUp", "key": "Return"}
]
```

O formato esperado da saída é igualmente importante:

```json
[
  {"seq": 44, "decision": "pending", "reason": "inherited-input"},
  {"seq": 46, "decision": "approved", "request": "r-19"}
]
```

Se a implementação aprovar na sequência 44, ela aceitou uma pressão que começou antes de a janela existir. Esse é o bug, expresso sem uma captura de tela ou uma condição de corrida que só aparece uma vez por semana.

Monte uma matriz de testes compacta em torno das mudanças de estado, não dos rótulos. Cubra pelo menos estes casos:

- Um atalho é pressionado antes da apresentação e solto depois que o foco chega.
- O foco sai depois que uma nova pressão começa e volta antes da soltura.
- Um evento de repetição chega enquanto a solicitação continua pendente.
- A solicitação A fecha, a solicitação B abre e um manipulador antigo é acionado.
- O cofre ou outro pré-requisito muda de estado depois que a janela aparece.

Em cada caso, verifique que nenhuma ação é enviada e que o histórico de auditoria registra uma negativa ou nenhuma decisão, de acordo com o desenho escolhido. Ignorar silenciosamente um evento pode ser aceitável. Um registro que afirma aprovação quando o reducer a negou não é.

Depois, teste a integração da interface, onde muitos reducers bons acabam sendo contornados. Verifique que todo caminho de ativação passa pelo mesmo bloqueio: clique do mouse, Return, Space, ação de acessibilidade, ação programática do botão padrão e qualquer comando de menu. Um caminho de atalho separado que chama a ação diretamente vai se afastar do caminho do botão com o tempo.

Use injeção de dependência para o executor da ação nesses testes. O teste só deve receber um objeto de chamada depois que o reducer emitir uma aprovação para o token atual. Conte as chamadas, capture o token da solicitação e falhe se um token antigo chegar ao executor. Não teste isso enviando uma chamada HTTP ou um comando SSH real. O limite de autorização deve poder ser testado sem uma credencial ou uma rede.

Os testes manuais continuam importantes porque os sistemas operacionais entregam eventos de foco e acessibilidade por caminhos que os mocks podem não reproduzir. Registre um pequeno roteiro de regressão em linguagem simples, execute-o no sistema mais antigo compatível e em um atual, usando um teclado físico. Inclua trocas rápidas de janela, teclas mantidas, teclas repetidas, alertas do sistema, bloqueio de tela, retorno do modo de suspensão e uma solicitação revogada enquanto está visível. O roteiro deve declarar a decisão esperada depois de cada ação, em vez de apenas dizer que a janela se comportou corretamente.

## Um evento negado deve continuar negado

Depois que um evento falha no predicado de aprovação, mudanças posteriores na interface não devem reabilitá-lo. Isso parece óbvio até que uma implementação armazene uma flag genérica de «aprovação pendente» e a verifique novamente quando o foco voltar ou uma animação terminar.

Torne a rejeição terminal para o evento. Se Return chegar enquanto a janela estiver desarmada, registre a entrada como herdada ou descarte-a e espere uma nova pressão posterior. Se o foco mudar durante uma combinação, invalide essa combinação. Se a solicitação mudar, invalide todos os eventos e callbacks associados à solicitação anterior. O código não deve manter uma aprovação em potencial caso as condições melhorem.

Esse princípio também protege contra ações duplicadas. Um clique duplo, uma ação pelo teclado seguida de uma ação pelo mouse ou uma repetição de evento não devem causar duas chamadas externas. Quando a camada de autorização aceitar um token de aprovação, marque a solicitação como consumida antes de enviar a ação. Ativações posteriores para esse token devem produzir uma rejeição inofensiva por duplicidade.

Separe o resultado da interface do resultado da ação externa. A interface só pode dizer «aprovada para envio» depois de fechar o estado de decisão. O executor pode então ter êxito ou falhar de forma independente. Se uma solicitação de rede falhar, não reative o token de aprovação antigo nem tente novamente em silêncio ao receber uma tecla posterior. Peça autorização novamente se o usuário precisar autorizar outra tentativa, especialmente quando o conteúdo ou o destino da ação puder ter mudado.

É aqui que o desenho da auditoria mostra seu valor. Registre contexto suficiente para reconstruir que uma entrada foi rejeitada por estar desatualizada, repetida, sem foco, revogada ou já consumida. Evite transformar o log em uma captura de todas as teclas pressionadas. Você precisa de evidências da decisão, não de um registro de vigilância.

## Identidade, autorização e escopo precisam de respostas separadas

Um fluxo de aprovação confiável responde quem fez o pedido, o que essa pessoa ou processo quer fazer e se o humano aprova essa ação específica. Não deve usar uma resposta como substituta de outra.

A identidade do processo é útil porque permite que o usuário decida se o solicitante é esperado. No macOS, a autoridade de assinatura de código pode oferecer um sinal mais significativo que um nome de processo mutável. Mas um processo esperado ainda pode fazer uma solicitação inesperada, e uma pessoa ainda pode aprovar a solicitação errada por causa de um atalho acidental.

O escopo é outra questão. Uma permissão de sessão pode ser razoável para trabalhos de baixo risco que um humano inspecionou, mas não transforma toda chamada futura na mesma decisão. A confirmação por ação tem outro objetivo: força um novo julgamento no momento em que uma ação usa uma credencial ou alcança um destino sensível. Desenhe a interação para que o usuário veja qual camada está alterando.

Evite rótulos vagos como «Permitir» quando a solicitação envolve um efeito externo. Informe o método, o destino e a operação relevante em uma linguagem que o operador consiga verificar. Também não transforme a janela em um pacote denso de dados. Mostre primeiro os fatos importantes para a decisão e disponibilize os detalhes exatos sem fazer a aprovação depender de uma sessão de rolagem.

O Sallyport mantém essas camadas deliberadamente estreitas: um cofre bloqueado nega todas as ações, um novo processo de agente solicita autorização de sessão por padrão e determinadas credenciais podem exigir uma nova aprovação a cada uso. Esse desenho, por si só, não torna a interação pelo teclado segura. O atalho ainda precisa provar que a pessoa atual aprovou a chamada atual.

## Publique somente quando o rastro da falha for entediante

Publique o atalho somente quando entradas hostis produzirem negativas comuns e explicáveis. Uma pessoa deve poder manter Return pressionada durante a apresentação da janela, trocar de janela no meio de uma combinação, provocar uma repetição de Space ou disputar espaço com uma solicitação substituta sem causar uma chamada externa.

Coloque o fixture de teste na mesma revisão da mudança de interface. Exija que o revisor leia a sequência de eventos que prova que uma tecla antiga não pode atravessar o limite. Exija que a implementação exponha uma única função de autorização que verifique a identidade da solicitação, a geração do foco, a atualidade da entrada e o estado de consumo. Os manipuladores de conveniência devem chamar essa função e nunca contorná-la.

Observe os logs de produção em busca de padrões que indiquem atrito, sem enfraquecer o predicado. Muitas negativas por entrada herdada depois que uma janela modal aparece podem significar que a solicitação chega em um momento ruim. Melhore quando e como a solicitação é apresentada ou ofereça um caminho pelo mouse e por biometria. Não «corrija» a métrica aceitando os eventos que o sistema rejeitou corretamente.

Um bom atalho de aprovação parece algo corriqueiro depois que a pessoa examinou a solicitação. Essa é a única velocidade que vale a pena otimizar. Qualquer outro caminho rápido é apenas uma forma de transformar a memória muscular do terminal em autorização.
