Quando reescrever e quando aceitar uma ponte com o navegador: a escada de três níveis para colocar código revertido em produção

Última Atualização: 2026-07-30 11:03:08

A primeira metade da engenharia reversa — separar mecanicamente o código, identificar a família do algoritmo e validar por testes diferenciais — leva a uma conclusão: "entendi o que ele calcula". Mas isso ainda não é algo pronto para enviar. A lógica continua presa ao runtime original, e é preciso decidir em que formato ela vai chegar: uma função pura que entra no CI ou um processo externo que alguém terá de vigiar. Escolha mal, e o tempo economizado antes volta como custo operacional, com juros.

A visão geral em quatro etapas resume este ponto em uma linha: "Etapa 4: degradar pela camada de transporte". Aqui, vamos abrir essa ideia. O princípio central é simples: cada descida de nível amplia por uma ordem de grandeza a superfície de dependências, os modos de falha e o custo de deploy — portanto, o padrão deve ser lutar para subir.

Os três níveis e a ordem que não deve mudar

Há apenas três formas de entregar essa lógica, nesta ordem. Isso deve constar nas convenções da equipe, não ser decidido caso a caso por "o que rodar primeiro":

  1. Reescrita nativa. Reescreva na linguagem-alvo, sem vínculo com o runtime original e usando apenas a biblioteca padrão. O pré-requisito é identificar corretamente a família do algoritmo. Quando a etapa de fingerprinting passa, 90% vem da implementação pública; os pontos de desvio restantes são tratados à parte, e o resultado vira uma função pura.

  2. Engine JS local com um fragmento mínimo. Há lógicas que, no curto prazo, custam caro demais para purificar. Nesses casos, mantenha uma pequena fatia do JS original e execute as poucas dezenas de linhas necessárias em um Node/V8 local — nunca a página inteira.

  3. Ponte passiva com o navegador. Certos estados só existem em uma página real, com sessão autenticada: uma assinatura fornecida pelo runtime ou um identificador dinâmico vinculado à sessão. Se a reconstrução estática não consegue reproduzi-los, por enquanto eles só podem ser lidos dentro do navegador. Trata-se de uma solução temporária, que precisa estar sinalizada nas notas da interface e jamais pode virar o padrão.

O custo real de cada nível

A ordem é fixa porque os custos dos três níveis não crescem de forma linear: cada um custa uma ordem de grandeza a mais que o anterior.

Nível

Superfície de dependências

Modo de falha

Funciona no CI?

Reescrita nativa

Biblioteca padrão, sem processos externos

Saída divergente, localizada por um assert

Sim — é uma função pura

Engine JS local

Um runtime Node adicional; o contexto V8 não é thread-safe, então a concorrência exige lock

Versão da engine ou ausência de uma global da qual o fragmento depende

Por pouco — é preciso instalar a engine

Ponte passiva com o navegador

Um Chrome real + extensão + sessão mantida por uma pessoa + canal local de loopback

Página fechada, sessão expirada, estrutura alterada ou aba encerrada

Não — precisa de uma pessoa ativa

Uma falha no primeiro nível é capturada por um teste unitário; no terceiro, a falha é "a pessoa fechou aquela aba hoje". Transformar algo que poderia ser uma função pura em algo de terceiro nível vincula uma pessoa a cada chamada. Como referência: depois de identificar a família do algoritmo, um SDK de assinatura ofuscado pode virar uma implementação independente com menos de 600 linhas, dependente apenas do crypto nativo e executada no primeiro nível. Aquilo que parece exigir uma ponte com o navegador geralmente é só um algoritmo que ainda não foi identificado por completo.

O limite da ponte passiva: um snapshot, nunca ação

Existe uma única forma de uma ponte passiva sair de controle: ela começa a "ajudar" — atualiza automaticamente, faz login automático, espera carregamentos por conta própria. Cada novo "auto" a desloca de encaminhadora para crawler. Por isso, o limite precisa ser extremamente estreito. Essas restrições foram aprendidas da pior maneira:

Faça apenas uma coleta pontual; jamais altere a página. Consulte cookies, estado da sessão e runtime de uma página já aberta uma vez por solicitação — e somente uma vez. Não crie abas, não atualize, não navegue, não dê foco, não faça polling nem espere. Página ausente é página ausente: não a abra pelo usuário.

Retorne um erro explícito assim que algo estiver faltando. Sem aba correspondente, retorne tab_unavailable; página aberta, mas sem login, retorna not_logged_in; login feito, mas runtime ainda indisponível, retorna runtime_unavailable. Cada um dos três códigos representa um estado real e uma próxima ação: esperar a página, fazer login ou trocar de alvo. Em vez de devolver um genérico "falhou" e obrigar quem chamou a adivinhar.

O estado sensível jamais sai do navegador. A extensão não pede permissões de cookies / webRequest; ela apenas consulta uma aba correspondente que já esteja aberta, conclui a requisição no contexto daquela página e limpa os campos antes de devolver o resultado. Cookies e estado de assinatura da página não saem do Chrome em nenhum momento, e o canal se conecta por padrão a um endereço local de loopback. A ponte transporta resultados, não credenciais.

Por que 15 escopos atendem apenas algumas plataformas

A ponte passiva encaminha requisições permitidas por whitelist, com caminho, parâmetros e referer restringidos por um adaptador. O ponto mais contraintuitivo é sua granularidade: são 15 escopos para apenas algumas plataformas porque os escopos são definidos pelo "contexto da página", não pela "plataforma". O próprio TikTok tem Creative Center, Top Ads, plataforma de criadores, biblioteca de influenciadores e Ads Manager como cinco escopos independentes — cinco estados de sessão e cinco runtimes de página distintos. Estar autenticado no backend de anúncios não fornece o runtime da plataforma de criadores. Se você criar uma única divisão por plataforma, o primeiro caso de "estou logado no subsite A, mas não consigo atender o subsite B" fará você voltar ao problema. O site principal do Xiaohongshu, seus caminhos equivalentes ao app e seu marketplace de criadores também formam três escopos distintos.

A granularidade do agendamento acompanha isso: o lock é aplicado apenas no nível da família de plataformas. Requisições da mesma família, como a família Douyin, rodam em série porque reutilizam a mesma aba real; dispará-las em paralelo no mesmo contexto de página faz com que interfiram entre si. Já famílias diferentes, como Douyin e Xiaohongshu, podem rodar em paralelo, pois usam abas não relacionadas. Dentro de uma família, inclua ainda um intervalo mínimo entre requisições. Se o recorte for amplo demais, você serializa trabalho que poderia ser paralelo; se for estreito demais, requisições que compartilham uma aba colidem. A família da plataforma é exatamente a fronteira natural de "compartilha o mesmo runtime de página".

Handoff explícito de login: a única intervenção humana permitida

A ponte passiva não opera a página, mas sessões expiram. A solução é concentrar a intervenção humana em uma única ação explícita: um comando interativo inicia um handoff, o programa abre pelo sistema operacional a página de negócio correspondente, espera você fazer login manualmente e a página ficar pronta, então repete a requisição original. Em nenhum momento a extensão clica em botões, preenche formulários ou exporta cookies. O login é feito por você, em um navegador real; o programa apenas retoma a requisição depois que você termina.

A restrição crítica é que isso não pode ser acionado implicitamente. Um comando não interativo, como CI ou uma tarefa agendada, nunca abre um navegador: ele apenas retorna o erro de sessão de forma clara e deixa a decisão para a camada acima. O handoff também precisa evitar falsos positivos. Após um login bem-sucedido, o runtime recebe no máximo uma espera adicional curta — 20 segundos na implementação — antes de exigir um veredito. Se a página de negócio já redirecionou para uma página de conta sem o contexto da interface-alvo, encerre a etapa imediatamente. Não confunda um "não dá para chegar lá" determinístico com "ainda está carregando" e espere sem necessidade. Em um fluxo que permite degradação, essa etapa é registrada como unavailable e o processamento continua, em vez de derrubar tudo.

Status de etapa: concluída, ignorada de propósito ou dependente de sessão

A base de tudo isso é: nenhuma etapa pode retornar apenas "sucesso" ou "falha". Em um pipeline de orquestração, o resultado de cada etapa tem seis formas: completed (concluída), empty (executou, mas não havia dados), ready (preparada, aguardando envio), skipped (ignorada deliberadamente por uma regra), unavailable (indisponível no momento, geralmente porque falta uma sessão) e blocked (um pré-requisito não foi atendido).

"Ignorada de propósito", "precisa de sessão" e "realmente vazia" são três sinais completamente diferentes. Se você devolve um único resultado opaco, não há como saber se aquele vazio é "deveria estar vazio" ou "a sessão morreu e ninguém percebeu" — e o pipeline se torna impossível de operar. Modele o status da etapa como um enum finito; assim, a camada de orquestração, seja um script ou um modelo, consegue decidir se deve degradar, reautenticar ou abortar. É o mesmo princípio dos três códigos de erro da ponte passiva, elevado ao nível do fluxo.

Como usar o modelo para decidir em que nível uma capacidade deve ficar

É só aqui que o modelo entra em cena, e em um papel contido: ele não faz a purificação por você; ajuda a avaliar se ela é possível e até onde vale levá-la. É uma decisão de arquitetura, não uma quebra de assinatura. A pergunta central é: o estado de que isso depende pode ser reconstruído estaticamente ou só está disponível em runtime? A partir daí, compare o esforço de purificação com a frequência de mudanças. Algumas etapas exigem capacidades diferentes do modelo:

Etapa

Capacidade necessária

Escolha

model id

Ler o módulo inteiro para entender a superfície de dependências

Contexto longo, leitura do grafo de chamadas de uma vez

Kimi K3

kimi-k3

Defender os dois lados da classificação e contestar o "só faz funcionar"

Raciocínio forte; capaz de justificar um dia extra de purificação

Claude Opus 5

claude-opus-5

Triagem inicial em lote de dezenas a centenas de capacidades

Baixo custo e alta concorrência

Claude Sonnet 5

claude-sonnet-5

Atribuição após uma degradação

Raciocínio intermediário; explicação baseada em um log de falha

GPT-5.6 Sol

gpt-5.6-sol

O segundo caso é o mais importante. O erro mais comum ao definir um nível é o modelo acompanhar seu tom de "só faz funcionar" e concluir que "a ponte com o navegador é mais fácil". Nesse momento, ele está apenas espelhando você, não calculando o custo de longo prazo. Um modelo de raciocínio forte reage: "este trecho é um hash padrão com uma perturbação constante; vale um dia de trabalho para transformá-lo em função pura e ele não deveria ir para a ponte". Não acredite apenas em mim: teste por conta própria. Escolha 3 blocos de lógica, incluindo pelo menos 1 cuja classificação você já saiba estar correta como controle. Envie o mesmo prompt — "dê uma classificação + justifique + conteste uma ida prematura para a ponte" — para claude-opus-5 e gpt-5.6-sol. Observe uma única coisa: ele luta para elevar o nível da solução ou cai preguiçosamente no terceiro?

O maior entrave é o custo de alternar

Quatro níveis de três fornecedores significam três SDKs, três esquemas de autenticação e três formatos de erro. Reescrever o cliente para alternar modelos entre etapas não compensa. Por isso, a maioria acaba usando um único modelo para tudo e, justamente na decisão de classificação que mais exige raciocínio, recorre a um modelo que apenas ecoa.

AIReiter achata essa camada: uma chave, uma interface compatível com OpenAI, os quatro níveis por trás dela, e a troca se resume a alterar o campo model no corpo da requisição.

# Placement argument: the reasoning tier
curl https://aireiter.com/api/v1/chat/completions \
  -H "Authorization: Bearer $AIREITER_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "claude-opus-5",
    "messages": [{"role": "user", "content": "<placement prompt + the reversed logic fragment + dependency list>"}]
  }'

# Bulk first-pass triage: change one field
#   "model": "claude-sonnet-5"
# Degradation attribution:
#   "model": "gpt-5.6-sol"

Já usa o SDK da OpenAI? Aponte base_url para https://aireiter.com/api/v1. Usa o SDK da Anthropic? Faça POST /api/v1/messages com a mesma chave. Em preço, os modelos Claude têm 30% de desconto sobre o valor de tabela, e os modelos GPT custam metade do preço. O custo desse fluxo se concentra em dois pontos: a triagem inicial em lote de dezenas a centenas de capacidades, com Sonnet e alto volume de chamadas; e a entrada única de contexto longo para ler um módulo inteiro e mapear sua superfície de dependências, com Kimi e muitos tokens por chamada. A triagem em lote roda em um modelo Claude, portanto o desconto incide justamente na etapa mais intensa; o argumento de classificação com raciocínio também usa um modelo Claude, com 30% de desconto. O nível de contexto longo do Kimi K3 está disponível com a mesma chave.

Conclusão

Entregar lógica obtida por engenharia reversa não é um problema técnico; é um problema de custo. A escada de três níveis — reescrita nativa > engine JS local > ponte passiva com o navegador — não pode ser invertida, porque cada descida troca uma função pura por um processo com dependências externas, intervenção humana e uma aba ativa. A ponte passiva não é uma zona proibida, mas um componente temporário com limites rígidos: um snapshot, nunca ação; erro explícito no instante em que a página falta; estado sensível sem sair do navegador; login apenas por handoff explícito; status de etapa sempre legível. Mantenha essas regras, e ela será uma solução provisória confiável. Abandone qualquer uma delas, e ela vira uma caixa-preta que ninguém quer manter. O modelo ajuda a decidir em que nível cada capacidade deve ficar e a resistir à inércia do "só faz funcionar"; já para verificar se cada reescrita está correta, quem decide é o teste diferencial, não o modelo.