# Configuração regional remota em testes de agentes

Um agente pode executar o mesmo comando, com os mesmos argumentos e arquivos, e ainda receber outro resultado em um host remoto. A entrada ausente costuma ser a configuração regional do processo. Ela muda como ferramentas comuns classificam texto, ordenam nomes, formatam números, imprimem datas, escolhem uma codificação e redigem seus erros.

Trate a configuração regional como dado de teste, não como enfeite da máquina. Fixe-a quando um teste espera um protocolo estável, varie-a quando o código promete lidar com convenções humanas e registre-a sempre que um agente cruzar o limite de um processo ou de SSH. Caso contrário, um teste local aprovado pode esconder uma falha de análise remota até o agente agir sobre a linha, o valor ou a data errada.

Isso não significa impor inglês em todo lugar. Uma interface de máquina e uma interface humana têm tarefas diferentes. Uma saída estável para máquinas precisa de formato e ambiente explícitos. Uma saída para usuários merece uma localização deliberada. Misturar as duas faz um diagnóstico traduzido ser analisado como estado, ou uma vírgula decimal virar silenciosamente o valor incorreto.

## O processo remoto determina o resultado

A configuração regional efetiva pertence ao processo que executa o comando. A configuração do seu laptop não controla um programa executado por SSH, a menos que algo encaminhe ou defina essas variáveis de modo intencional. Um shell remoto interativo também pode não corresponder a uma sessão de comando não interativa.

O POSIX.1-2024 define claramente a precedência. Um `LC_ALL` não vazio substitui todas as categorias. Se ele estiver ausente, uma variável de categoria como `LC_TIME` ou `LC_COLLATE` prevalece nessa categoria. `LANG` fornece o padrão para as categorias ainda não definidas. Isso significa que `LANG=C` não tem efeito se um `LC_ALL=de_DE.UTF-8` herdado continuar presente.

O OpenSSH acrescenta outra fronteira. Seu manual de cliente diz que `SendEnv` seleciona variáveis locais para envio, mas o servidor precisa aceitá-las. Por padrão, o cliente não envia nenhuma. `SetEnv` pode solicitar valores explícitos, sujeitos à mesma aceitação do servidor. Um desenvolvedor pode ter `SendEnv LANG LC_*` na configuração SSH pessoal, enquanto o auxiliar SSH sem estado do agente não usa essa configuração, ou o contrário. Nenhum resultado deve ser presumido.

Arquivos de inicialização de login deixam o quadro mais confuso. Uma distribuição pode definir `LANG` pelo PAM ou pela configuração do sistema. O perfil do usuário pode alterá-lo para um login interativo. Um comando remoto costuma rodar sem seguir o mesmo caminho de inicialização. Um contêiner iniciado por esse comando pode introduzir seu próprio conjunto, e uma imagem mínima talvez nem tenha o locale solicitado instalado.

A correção é definir o ambiente pretendido no ponto final de execução. Não dependa de encaminhamento quando o comando precisa de configuração estável. Coloque a atribuição ao lado da ferramenta:

```sh
ssh buildbox 'env LC_ALL=C.UTF-8 TZ=UTC command-to-test --format=plain'
```

Isso deixa visível o contrato do teste. Também produz uma falha informativa se `C.UTF-8` não estiver disponível, em vez de herdar sem aviso a escolha do host. Quando não for possível contar com esse nome, detecte os locales aceitos durante o provisionamento e use uma alternativa documentada.

## Registre o ambiente antes de interpretar a saída

Um relatório de falha remota precisa das categorias efetivas, codificação, fuso horário, identidade da ferramenta e bytes originais. Registrar apenas `LANG` é insuficiente, pois `LC_ALL` ou uma variável de categoria pode substituí-lo. Guardar somente texto já decodificado pode apagar a prova de uma falha de codificação.

Execute uma pequena sonda antes do comando investigado:

```sh
env | LC_ALL=C sort | sed -n '/^LANG=/p;/^LC_/p;/^TZ=/p'
printf 'charmap='; locale charmap
printf 'decimal='; locale -k decimal_point 2>/dev/null || true
printf 'date='; date +'%Y-%m-%dT%H:%M:%S%z'
printf 'tool='; command -v sort
sort --version 2>/dev/null | sed -n '1p'
```

Um resultado comum do Linux pode ter este formato:

```text
LANG=de_DE.UTF-8
LC_NUMERIC=de_DE.UTF-8
TZ=Europe/Berlin
charmap=UTF-8
decimal=decimal_point="," 
date=2026-07-24T143105+0200
tool=/usr/bin/sort
sort (GNU coreutils) 9.5
```

Não transforme essa amostra em valor esperado. O que importa é o conjunto de campos. Algumas implementações de `locale` formatam a saída de palavras-chave de outra maneira, e outras ferramentas podem não aceitar `--version`. Capture o status de saída e o erro padrão de cada sonda para que um recurso ausente não pareça um valor vazio.

Em falhas de codificação, preserve os bytes antes de decodificar. Um executor de testes pode escrever saída e erro padrão em arquivos separados, calcular checksums e depois decodificar uma cópia com a codificação declarada. Uma exibição hexadecimal ao redor do primeiro byte inválido é muito mais útil que um caractere de substituição inserido pela camada de logs.

Registre também o transporte exato do comando. `ssh host command`, `ssh host sh -lc command`, um terminal interativo e um processo iniciado por uma ferramenta de agente são caminhos de execução distintos. Eles podem escolher shells, arquivos de inicialização, pseudoterminais e filtros de ambiente diferentes. Se o caminho que falha usa uma ação do agente, reproduza esse caminho em vez de provar que um login digitado à mão funciona.

Esse conjunto de diagnóstico deve acompanhar o artefato de teste sempre que os resultados divergirem. Ele transforma “o sort remoto é instável” em uma comparação de entradas concretas.

## Fixe um locale para protocolos, não para pessoas

Use uma configuração fixa quando a saída do comando alimentar um analisador, snapshot, diff, chave de cache, decisão de implantação ou outro programa. Use o locale humano solicitado quando a saída for destinada a uma pessoa. São interfaces distintas, mesmo que um comando produza ambas hoje.

O conselho comum de definir `LC_ALL=C` em todos os lugares é popular porque torna muitas ferramentas Unix previsíveis e está disponível em sistemas POSIX. Como regra geral, está errado. Dependendo do sistema e do runtime, o locale `C` pode implicar um modelo de caracteres voltado a ASCII. Um programa que leia nomes como `Málaga` pode rejeitar ou tratar mal os bytes, mesmo depois de estabilizar a ordem.

`C.UTF-8` combina ordenação simples e UTF-8 em muitos sistemas Unix atuais, o que o torna uma opção prática de teste. O POSIX, porém, não exige esse nome exato. macOS, distribuições Linux, contêineres e runtimes de linguagens não expõem um catálogo idêntico. `locale -a` mostra o que o host oferece, e imagens de teste provisionadas devem declarar qual nome garantem.

Outra distinção precisa ficar clara: estabilidade de locale não é estabilidade de formato de saída. Fixar `LC_ALL` não promete que duas versões de uma ferramenta imprimam colunas, espaçamento, avisos ou campos JSON idênticos. Se ela oferecer JSON, delimitadores NUL, segundos de época ou uma string de formato explícita, escolha também essa interface. Controlar o locale remove uma variável, mas não congela o programa.

Um bom wrapper limpa possíveis substituições e acrescenta apenas o necessário:

```sh
run_stable() {
  env -u LANGUAGE -u LC_COLLATE -u LC_CTYPE -u LC_MESSAGES \
      -u LC_MONETARY -u LC_NUMERIC -u LC_TIME \
      LC_ALL=C.UTF-8 TZ=UTC "$@"
}
run_stable sort input.txt
```

Se a portabilidade incluir sistemas cujo `env` não oferece `-u`, construa um ambiente mínimo. Defina `PATH` explicitamente e preserve apenas as variáveis necessárias à aplicação. Não copie todo o ambiente pai para depois corrigir somente `LANG`, pois as substituições de categoria continuarão presentes.

Testes de comportamento voltado ao usuário devem fazer o oposto. Eles selecionam de propósito um locale aceito e verificam a convenção relevante. Um teste de relatório em alemão pode esperar vírgula decimal e nomes de meses em alemão. O analisador por trás do relatório ainda deve trocar internamente números e datas normalizados.

## Datas precisam de formato e fuso horário

Locale e fuso horário causam falhas diferentes em datas. `LC_TIME` controla nomes e representações convencionais. `TZ` controla qual horário civil corresponde a um instante. Fixar um não fixa o outro.

O GNU Coreutils alerta que a saída de `date` nem sempre pode ser analisada depois. Seu manual recomenda formato independente de idioma, representação gregoriana e uma zona inequívoca como UTC ou `Z` para dados gerados. Esse conselho vale mais que um snapshot aprovado por acaso em uma configuração inglesa.

Para um protocolo de teste, escolha uma representação explícita:

```sh
env LC_ALL=C.UTF-8 TZ=UTC date +'%Y-%m-%dT%H:%M:%SZ'
```

A saída tem o formato `2026-07-24T12:31:05Z`. Se o teste precisar de um instante fixo em vez do relógio atual, passe esse instante por uma opção aceita pela ferramenta ou injete um relógio na aplicação. Controlar o locale não faz o tempo parar.

Evite `%c`, `%x`, `%X`, `%a` e `%b` em dados que outro programa vai analisar. Essas diretivas pedem intencionalmente convenções regionais ou nomes traduzidos. Diretivas numéricas ainda podem incluir detalhes de calendário em alguns sistemas. O manual GNU documenta locales que usam calendários alternativos em certas diretivas, então um ano aparentemente numérico não é uma promessa universal sem definição pelo formato e pelo locale escolhido.

Números de semana são outra armadilha. Ano civil, ano ISO baseado em semanas e convenções locais respondem a perguntas diferentes perto do Ano Novo. Declare qual deles a regra de negócio usa e teste datas de fronteira. Fixar o locale não corrige uma combinação errada de `%Y-%V`.

Relatórios humanos devem formatar datas na borda do sistema. Mantenha o instante armazenado ou transmitido em forma estável e depois aplique o locale e o fuso do leitor para exibição. Se um agente precisar comparar timestamps de vários hosts, peça valores de época ou strings no estilo RFC 3339 com deslocamentos. Não faça o agente adivinhar se `03/04/26` significa 3 de abril ou 4 de março.

Uma matriz útil de datas inclui um locale com nomes de meses em inglês, outro com nomes diferentes, UTC, um fuso com transições de horário de verão e datas perto da mudança de relógio e da virada do ano. O objetivo não é enumerar o planeta. É expor código que presumiu as convenções do desenvolvedor.

## A ordenação deve servir ao consumidor

Texto não tem uma única ordem natural. Ordem de bytes, de pontos de código Unicode e collation linguístico produzem sequências diferentes. Testes falham quando esperam uma e invocam outra.

O manual do GNU `sort` afirma que as comparações normalmente usam a sequência selecionada por `LC_COLLATE`. Ele recomenda especificamente `LC_ALL=C` quando um script exige a ordem tradicional. O mesmo manual observa que definir apenas `LC_COLLATE` é arriscado se `LC_ALL` o substituir ou se as categorias de caracteres usarem codificações incompatíveis.

Considere este fixture:

```text
Zebra
apple
zebra
Ångström
ábaco
```

Um locale no estilo `C` costuma ordenar pelos bytes codificados, com letras ASCII maiúsculas antes das minúsculas e sequências UTF-8 fora de ASCII depois. Um locale linguístico pode comparar caixa ou acentos em níveis diferentes. Não cole uma ordem presumida em um artigo ou teste multiplataforma. Execute o locale realmente aceito e verifique a propriedade semântica de que precisa.

Para um manifesto reproduzível ou arquivo de referência, a ordem por bytes costuma ser correta. Defina `LC_ALL=C` se todos os caminhos estiverem restritos ao conjunto portátil, ou use um locale UTF-8 verificado e defina a função de ordem no programa. Para uma lista exibida a leitores espanhóis, suecos ou alemães, ordem binária oferece uma experiência ruim. Use uma biblioteca de collation regional com versão de dados fixada, pois os dados do sistema operacional podem mudar sem alteração no seu código.

Ordenação e junção devem compartilhar regras. O GNU Coreutils orienta a executar `sort` e `join` com locales e opções consistentes. Um arquivo ordenado sob um collation pode parecer desordenado ao `join` sob outro, causando correspondências perdidas ou diagnósticos. O mesmo vale para `comm`, remoção de duplicatas, mesclagens e qualquer pipeline que suponha valores iguais adjacentes.

Prefira asserções que expressem a intenção. Se a ordem não importa, compare conjuntos ou mapas em vez de registrar um snapshot acidental. Se a ordem por bytes faz parte do protocolo, calcule-a no teste e identifique-a. Se a função é uma ordem localizada, inclua fixtures com acentos, variações de caixa e pontuação que a diferenciem da ordenação binária.

Um agente pode piorar isso usando um `sort` exploratório e tratando a primeira linha como o item “menor” ou “seguinte”. Coloque a regra de ordenação no contrato da ação. “Selecione a primeira versão em ordem semântica” é diferente de “selecione o primeiro nome sob o locale remoto”.

## Vírgulas decimais quebram pipelines sem aviso

`LC_NUMERIC` define o sinal decimal e as convenções de agrupamento usadas por funções sensíveis ao locale e algumas opções de comando. Um valor exibido como `1,25` pode estar certo para uma pessoa e ser inválido para um analisador que espera `1.25`. Pior, um analisador permissivo pode aceitar só o prefixo e retornar `1` sem um erro claro.

O GNU `sort -n` usa o separador de milhares e o sinal decimal do locale ao reconhecer prefixos numéricos. `locale.format_string`, `locale.atof` e funções relacionadas do Python também seguem `LC_NUMERIC`. O `float()` comum do Python e muitos formatos de dados não seguem. Passar texto entre essas duas famílias sem uma fronteira definida cria um bug que aparece apenas em certas configurações.

Mantenha números de protocolo normalizados. Números JSON usam ponto, opções de linha de comando normalmente documentam uma gramática fixa e formatos de banco de dados definem sua própria representação. Formate vírgulas ou agrupamentos apenas para exibição, depois de concluir cálculo e serialização.

Teste analisadores com valores que deixem o truncamento silencioso evidente:

```text
0.5
1.25
1234.75
-0.125
```

Depois execute a mesma operação sob um locale com vírgula decimal. Se a ferramenta aceitar entrada localizada de propósito, forneça fixtures equivalentes com vírgula e rejeite agrupamentos ambíguos. Se ela prometer gramática fixa, defina o locale do comando e verifique que uma entrada com vírgula falha claramente.

Não “conserte” uma saída arbitrária substituindo toda vírgula por ponto. A vírgula pode separar campos, agrupar milhares ou fazer parte do texto. Use um modo de saída estruturado ou um analisador que conheça o locale declarado. Se o produtor não publicar uma gramática, considere a saída para humanos inadequada à automação.

A aritmética do shell também gera confiança falsa. O shell pode usar sintaxe fixa, enquanto um `awk`, `printf`, conversor de planilhas ou runtime aplica locale em operações específicas. Teste o pipeline inteiro sob um ambiente, em vez de testar cada comando separadamente no shell de login.

Dinheiro exige tratamento ainda mais estrito. Armazene unidades menores ou um tipo decimal com moeda explícita e localize apenas o valor exibido. Um agente que decide se um valor ultrapassa um limite deve receber o número normalizado, não extrair dados de um relatório feito para leitores.

## Erros de codificação começam antes da decodificação

O locale pode dizer ao processo como interpretar sequências de bytes como caracteres. `LC_CTYPE` afeta a classificação de caracteres e costuma estar associado a um conjunto de codificação. Isso importa para ferramentas que dividem texto, reconhecem classes, mudam caixa, calculam largura ou convertem bytes e strings.

Ter UTF-8 nas duas máquinas não prova que todo processo o usa. Um serviço remoto pode iniciar no locale `C`, um contêiner mínimo pode não ter dados regionais gerados, ou um runtime pode ativar seu próprio modo UTF-8. A documentação do Python é direta: em alguns sistemas, a codificação preferida é apenas uma estimativa, e o Python UTF-8 Mode pode ignorar a codificação regional nessa consulta.

Decodifique explicitamente nas fronteiras. Quando o contrato do comando disser UTF-8, leia bytes e decodifique como UTF-8 com política estrita de erros. Não chame o decodificador padrão da plataforma esperando sorte. Se nomes de arquivo arbitrários forem possíveis no Unix, lembre que eles são sequências de bytes na fronteira do sistema operacional. Forçá-los em texto comum pode perder informações. Use a codificação de sistema de arquivos e a estratégia reversível do runtime quando existirem.

Substituir erros é útil para exibição, mas perigoso para decisões. Duas sequências inválidas diferentes podem virar o mesmo caractere visível. Um teste deve falhar mostrando o deslocamento do byte, preservar a saída original e exibir uma pequena janela hexadecimal. Essa evidência mostra se o produtor emitiu codificação antiga, truncou uma sequência ou devolveu dados binários por um canal de texto.

Classes de caracteres merecem fixtures diretos. Sob locales diferentes, `[[:alpha:]]`, conversão de caixa e reconhecimento de espaço podem incluir caracteres distintos. O POSIX explica que `LC_CTYPE` determina como sequências de bytes viram caracteres e quais pertencem às classes. Um script que limpa nomes com um intervalo dependente do locale pode aceitar ou remover outro texto remotamente.

Use código que entenda Unicode para texto humano e regras ASCII explícitas para identificadores de protocolo. Não deixe o locale do ambiente decidir o que pode ser um nome de variável, token ou campo. Do outro lado, não aplique um filtro limitado a ASCII ao nome de uma pessoa e chame o resultado de validação.

Um conjunto compacto de testes de codificação deve ter ASCII simples, texto acentuado pré-composto, o mesmo texto visível com marcas combinantes, outro sistema de escrita e uma sequência de bytes deliberadamente inválida quando a interface aceitar bytes brutos. Verifique bytes nas fronteiras e caracteres após a decodificação. Essa separação torna as falhas compreensíveis.

## Uma matriz pequena encontra as suposições

Execute a maioria dos testes determinísticos sob um locale fixado e depois uma suíte menor de variações escolhidas para quebrar suposições. Testar todos os locales instalados gasta tempo e ainda oferece cobertura fraca, pois muitos compartilham as mesmas convenções relevantes.

Escolha variantes pelo comportamento:

1. Use `C` para comportamento portátil de bytes e diagnósticos traduzidos que não devem ser analisados.
2. Use um locale UTF-8 disponível com ponto decimal e um collation não trivial.
3. Use outro com vírgula decimal e nomes de datas diferentes.
4. Adicione um locale ou modo do runtime que exponha suposições de codificação, se o produto aceitar.
5. Combine casos de data com UTC e um fuso que tenha horário de verão.

Provisione esses locales na imagem de teste. Um teste ignorado porque o runner não tem os dados não é aprovação. Imprima `locale -a` quando a preparação falhar e torne o catálogo necessário parte da definição da imagem.

Mantenha a matriz perto da inicialização do processo. Em Python, passe uma cópia do ambiente a `subprocess.run` em vez de mudar o locale global em um runner com threads:

```python
import os
import subprocess

def run_case(locale_name):
    child_env = os.environ.copy()
    child_env.update({"LC_ALL": locale_name, "TZ": "UTC"})
    return subprocess.run(
        ["./agent-command", "inspect", "fixtures/names.txt"],
        env=child_env,
        check=False,
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
    )
```

O manual do Python diz que `setlocale()` não é seguro entre threads na maioria dos sistemas e altera uma propriedade de todo o programa. Mudá-lo durante testes pode fazer casos paralelos se afetarem. Um ambiente filho isola o comando testado e reflete melhor a execução remota.

As asserções devem separar status de saída, bytes da saída padrão, bytes do erro e significado analisado. `LC_MESSAGES` pode mudar o idioma do diagnóstico sem mudar a falha. Um teste que procura a frase inglesa “No such file” testa um catálogo de tradução, não a condição de erro. Prefira códigos de saída, campos estruturados ou identificadores estáveis.

Quando uma variação falhar, reduza por categoria. Comece com `LC_ALL` limpo e depois defina `LANG` e categorias individuais para descobrir se tempo, collation, números, mensagens ou caracteres causaram a mudança. Variáveis de categoria são ótimas ferramentas de diagnóstico mesmo que a produção use um único `LC_ALL`.

Execute a matriz menor em alterações que toquem análise, inicialização de processos, SSH, relatórios ou fixtures. Uma execução programada pode cobrir mais sistemas operacionais e versões. Salve a sonda de ambiente com cada falha para que a repetição não dependa da memória.

## Ações do agente precisam de um contrato

Um agente autônomo amplia a ambiguidade do locale porque pode encadear uma saída plausível com uma ação de consequência real. Se uma lista mudar de ordem, pode selecionar outro arquivo. Se um analisador truncar um decimal, pode comparar o limite errado. Se uma data cruzar um fuso, pode agir sobre o registro do dia incorreto.

Dê às ferramentas remotas um contrato explícito com quatro partes: o ambiente definido, os bytes ou dados estruturados retornados, as informações de saída preservadas e a semântica de shell usada. Inclua versões ou sondas de capacidade quando a saída variar entre implementações. Um prompt não corrige uma fronteira de processo não especificada.

Defina o significado de sucesso antes de o agente ver a saída. Status zero pode significar que o comando terminou, não que encontrou um registro. Algumas ferramentas relatam resultados parciais com aviso, enquanto outras escrevem progresso no erro padrão mesmo em caso de sucesso. Preserve os três canais e deixe um analisador com gramática declarada decidir se o resultado serve. Não peça a um modelo para deduzir sucesso pelo tom de uma mensagem localizada.

Faça um locale incompatível causar erro de preparação, não surpresa durante a ação. Se um comando iniciar com `LC_ALL=fr_FR.UTF-8` em um host sem esse locale, o shell ou runtime pode avisar e usar um fallback, ou o programa pode falhar. O executor deve primeiro confirmar o locale com `locale -a` ou uma verificação de capacidade, registrar o nome escolhido e parar quando não puder testar o comportamento necessário. Um fallback escolhido no provisionamento é controlado; outro escolhido no meio de uma ação é estado oculto.

Revise também as camadas de interpretação separadamente. O processo local monta um argumento SSH, o serviço remoto inicia o shell do usuário e esse shell analisa a string antes que o programa leia os argumentos. Atribuições de ambiente e aspas podem mudar em cada camada. Prefira uma API remota baseada em argumentos quando existir. Se houver apenas uma string de shell, teste o comando serializado exato e inclua espaços, aspas simples, nova linha e texto fora de ASCII. Fixar o locale não corrige aspas, mas um erro nelas pode aplicar a configuração ao comando errado.

Trate saídas analisadas com frequência como pequenos protocolos versionados. Guarde um fixture dos bytes, documente o locale e a família da ferramenta esperados e rejeite formatos desconhecidos. Quando uma atualização mudar o formato, atualize juntos o analisador e o fixture. É menos chamativo que fazer o agente “entender” outra saída humana, e muito mais seguro para comandos que levam a escritas.

Para SSH, prefira um comando que defina o ambiente remotamente a esperar que o encaminhamento do cliente coincida. Use aspas na camada correta e teste valores com espaços, aspas e texto fora de ASCII. Não adicione um shell de login apenas para herdar o locale, pois ele também importa aliases, scripts e outros estados.

Mantenha os resultados brutos disponíveis para revisão. O Sallyport pode executar ações SSH pelo auxiliar incluído `sp-ssh` enquanto guarda as chaves SSH no cofre criptografado, e o diário Activity registra cada chamada. Isso não torna a saída independente do locale, mas preserva uma fronteira útil: o agente recebe resultados sem receber a credencial usada para obtê-los.

Quando uma ação puder alterar estado externo, valide o valor analisado antes da escrita. Exija formato explícito do comando de leitura, rejeite bytes que não possam ser decodificados e anexe o locale registrado à ação proposta. Aprovação humana só faz sentido se o cartão mostrar o valor que o sistema realmente analisou.

O primeiro reparo em uma suíte existente é concreto. Encontre cada comando remoto cuja saída é analisada ou registrada. Adicione a sonda às falhas, fixe locale e fuso no processo remoto e introduza um locale de vírgula decimal e outro com collation diferente como casos adversos. As falhas surpreendentes são as suposições que seu shell local vinha escondendo.
