Когда переписывать нативно, а когда мириться с браузерным мостом: трёхуровневая схема для обратного кода

Последнее обновление: 2026-07-30 11:06:13

После механического разбиения кода, определения семейства алгоритма и дифференциальной проверки появляется важный, но ещё не готовый к продакшену результат: «я понимаю, что именно здесь вычисляется». Логика по-прежнему сидит в исходном рантайме. Теперь нужно решить, во что её превратить: в чистую функцию для CI или во внешний процесс, за которым придётся постоянно присматривать. Ошибка на этом этапе быстро съедает всю экономию от реверса — причём с процентами в эксплуатации.

В обзоре из четырёх этапов этому посвящена одна короткая строка: «Этап 4: деградация по транспортному слою». Здесь разберём её подробно. Главная мысль проста: на каждом уровне ниже на порядок растут поверхность зависимостей, число сценариев отказа и стоимость развёртывания — поэтому по умолчанию нужно тянуть решение вверх.

Три варианта приземления — и порядок не обсуждается

Вариантов ровно три, сверху вниз. Их стоит закрепить в инженерных правилах, а не выбирать ситуативно по принципу «лишь бы быстрее запустилось»:

  1. Нативная реализация. Перепишите логику на целевом языке, полностью отвязав её от исходного рантайма и оставив только стандартную библиотеку. Предпосылка одна: семейство алгоритма определено верно. Когда пройден этап идентификации по отпечаткам, в 90% случаев основа берётся из публичной реализации, отдельно обрабатываются точки расхождения — и на выходе получается чистая функция.

  2. Локальный JS-движок с минимальным фрагментом. Иногда быстро очистить логику от окружения слишком дорого. Тогда сохраняют небольшой кусок исходного JS и исполняют нужные несколько десятков строк в локальном Node/V8, а не тащат за собой всю страницу.

  3. Пассивный браузерный мост. Некоторые состояния существуют только в настоящей открытой странице с активной сессией: подпись, доставляемая рантаймом, или динамический идентификатор, привязанный к сессии. Если статически восстановить их нельзя, пока остаётся лишь считывать данные внутри браузера. Это временное решение: его необходимо явно отметить в интерфейсных заметках и никогда не делать вариантом по умолчанию.

Почему цена уровней растёт скачками

Именно поэтому порядок фиксирован: стоимость трёх уровней растёт не плавно. Каждый следующий обходится примерно на порядок дороже предыдущего.

Уровень

Поверхность зависимостей

Как ломается

Можно запускать в CI?

Нативная реализация

Стандартная библиотека, никаких внешних процессов

Результат не совпал; находится одним assert

Да — это чистая функция

Локальный JS-движок

Дополнительный рантайм Node; контекст V8 небезопасен для потоков, конкурентный доступ требует блокировки

Версия движка изменилась или отсутствует глобальная переменная, от которой зависит фрагмент

С трудом — движок нужно устанавливать

Пассивный браузерный мост

Настоящий Chrome, расширение, сессия, которую поддерживает человек, и локальный loopback-канал

Страница не открыта, сессия истекла, структура изменилась или вкладку закрыли

Нет — нужен живой человек

На первом уровне сбой ловится юнит-тестом; на третьем он выглядит так: «пользователь сегодня закрыл ту вкладку». Если то, что могло быть чистой функцией, превращается в решение третьего уровня, к каждому вызову привязывается человек. Ориентир: после определения семейства алгоритма обфусцированный SDK подписи можно приземлить как самостоятельную реализацию менее чем в 600 строк, зависящую только от встроенного crypto и работающую на первом уровне. То, что, казалось, можно провести лишь через браузерный мост, обычно оказывается алгоритмом, который вы ещё не до конца идентифицировали.

Граница пассивного моста: один снимок и никакой активности

Пассивный мост выходит из-под контроля ровно в один момент: когда начинает «помогать» — сам обновлять страницу, логиниться, ждать загрузку. Каждое добавленное «сам» сдвигает его от простого ретранслятора к краулеру. Поэтому граница должна быть предельно узкой. Эти ограничения появились не из теории:

Снимайте состояние один раз и никогда не меняйте страницу. Один раз считывайте cookies, состояние сессии и рантайм уже открытой страницы. Не создавайте вкладки, не обновляйте, не переходите по адресам, не переводите окно в фокус, не опрашивайте страницу в ожидании. Нет страницы — значит, её нет: не открывайте её за пользователя.

Как только чего-то не хватает — сразу возвращайте понятную ошибку. Нет подходящей вкладки — tab_unavailable; страница есть, но пользователь не авторизован — not_logged_in; авторизация есть, но рантайм ещё не готов — runtime_unavailable. Каждому из трёх кодов соответствует одно реальное состояние и одно следующее действие: дождаться страницы, войти в аккаунт или сменить цель. Не заставляйте вызывающий код гадать по общей ошибке «что-то пошло не так».

Чувствительное состояние не покидает браузер. Расширение не запрашивает разрешения cookies / webRequest. Оно только находит уже открытую подходящую вкладку, выполняет запрос в контексте этой страницы и очищает поля перед возвратом результата. Cookies и состояние подписи страницы ни на одном шаге не выходят из Chrome, а канал по умолчанию подключается к локальному loopback-адресу. Мост передаёт результаты, а не учётные данные.

Почему 15 scope нужны всего для нескольких платформ

Пассивный мост пересылает только запросы из whitelist: адаптер ограничивает путь, параметры и referer. Самое неочевидное здесь — гранулярность. Для нескольких платформ требуется 15 scope, потому что scope режутся по «контексту страницы», а не по «платформе». У одного TikTok есть Creative Center, Top Ads, Creator platform, библиотека инфлюенсеров и Ads Manager — это пять независимых scope, пять независимых состояний сессии и пять рантаймов страниц. Авторизация в рекламном бэкенде не даст доступ к рантайму Creator platform. Если выделить один срез на платформу, первая же ситуация «залогинен в подсайте A, но не могу обслужить подсайт B» вернёт вас к переделке. У Xiaohongshu основной сайт, пути, эквивалентные приложению, и маркетплейс для авторов тоже образуют три отдельных scope.

Отсюда следует и гранулярность планирования: блокировка ставится только на уровне семейства платформы. Запросы внутри одного семейства, например Douyin, выполняются последовательно: они используют одну реальную вкладку, а параллельные обращения к одному контексту страницы начинают мешать друг другу. Разные семейства, такие как Douyin и Xiaohongshu, работают параллельно — это две независимые вкладки. Внутри семейства поверх этого добавляется минимальный интервал между запросами. Слишком грубая блокировка сериализует работу, которую можно было вести параллельно; слишком мелкая даёт столкновения запросов в общей вкладке. Семейство платформы и есть естественная граница: «использует один рантайм страницы».

Передача логина человеку: единственное допустимое вмешательство

Пассивный мост не управляет страницей, но сессии истекают. Решение — свести участие человека к одному явному разовому действию: интерактивная команда запускает handoff, программа через операционную систему открывает нужную рабочую страницу, ждёт, пока вы вручную войдёте и страница станет готова, а затем повторяет исходный запрос. Расширение при этом не нажимает кнопки, не заполняет формы и не экспортирует cookies. Авторизация происходит в обычном браузере руками пользователя, а программа лишь продолжает запрос после её завершения.

Критическое правило: это не должно запускаться неявно. Неинтерактивная команда — в CI или по расписанию — никогда не открывает браузер. Она просто корректно возвращает ошибку сессии и оставляет решение верхнему уровню. Handoff также обязан защищаться от ложных ожиданий: после успешного входа рантайм получает лишь короткое дополнительное ожидание — в реализации это 20 секунд, — после чего должен быть вынесен вердикт. Если рабочая страница уже перенаправила на страницу аккаунта без нужного контекста интерфейса, текущий этап нужно завершить сразу. Нельзя путать детерминированное «сюда попасть невозможно» с «страница всё ещё загружается» и бессмысленно ждать. В потоке, допускающем деградацию, этот этап фиксируется как unavailable, а выполнение продолжается, вместо того чтобы валить весь процесс.

Статусы этапов: выполнено, пропущено, нужна сессия

Общее правило для всего описанного: этап не может возвращать только «успех» или «ошибку». В оркестрационном пайплайне результат этапа имеет шесть форм: completed — выполнен, empty — отработал, но данных нет, ready — подготовлен и ждёт отправки, skipped — намеренно пропущен по правилу, unavailable — временно недоступен, обычно нужна сессия, blocked — не выполнено предварительное условие.

«Намеренно пропущен», «нужна сессия» и «данных действительно нет» — три принципиально разных сигнала. Если вернуть один непрозрачный результат, невозможно понять: пустой ответ был ожидаемым или сессия умерла, а никто этого не заметил. Таким пайплайном нельзя управлять. Опишите статус этапа конечным enum, и оркестратор — скрипт или модель — сможет решить, нужно ли деградировать, переавторизоваться или остановиться. Это тот же принцип, что и три кода ошибок пассивного моста, только поднятый на уровень всего потока.

Как использовать модель для выбора уровня реализации

Именно здесь модель полезна, но в строго ограниченной роли: она не очищает логику от окружения за вас, а помогает понять, нужно ли это делать и насколько далеко идти. Это архитектурное решение, а не взлом подписи. Главный вопрос один: состояние, от которого зависит код, можно статически восстановить или оно доступно только во время выполнения? Затем усилия на очистку сопоставляются с частотой изменений. На разных шагах от модели требуется разное:

Шаг

Нужные возможности

Выбор

model id

Прочитать весь модуль и увидеть поверхность зависимостей

Длинный контекст, чтение графа вызовов за один проход

Kimi K3

kimi-k3

Аргументировать выбор уровня с обеих сторон и возражать против подхода «лишь бы заработало»

Сильное рассуждение; способна обосновать, почему стоит потратить лишний день на очистку

Claude Opus 5

claude-opus-5

Массовая первичная сортировка от десятков до сотен возможностей

Низкая стоимость, высокая параллельность

Claude Sonnet 5

claude-sonnet-5

Установить причину после деградации

Средний уровень рассуждений; объясняет проблему по логу сбоя

GPT-5.6 Sol

gpt-5.6-sol

Особенно важен второй вариант. Самая частая ошибка при выборе уровня — модель подхватывает ваш настрой «лишь бы запустить» и предлагает «через браузерный мост проще всего». Она не оценивает долгосрочную цену, а лишь отражает вашу интонацию. Модель с сильным рассуждением возразит: «этот сегмент — стандартный хеш с одним постоянным возмущением; стоит потратить день и сделать чистую функцию, а не отправлять его в мост». Не верьте на слово — проверьте сами. Возьмите 3 фрагмента логики, включая как минимум 1, для которого правильный уровень вам уже известен и который послужит контрольным. Передайте один и тот же промпт — «выбери уровень, обоснуй решение и возрази против преждевременного переноса в браузерный мост» — в claude-opus-5 и gpt-5.6-sol. Смотрите только на одно: модель пытается поднять решение на уровень выше или лениво выбирает третий по умолчанию.

Главная проблема — цена переключения

Четыре уровня у трёх поставщиков означают три SDK, три схемы авторизации и три формата ошибок. Переписывать клиент, чтобы менять модели между шагами, невыгодно. Поэтому большинство запускает одну модель на всё — и для решения о размещении, где особенно нужен сильный уровень рассуждений, получает модель, которая только поддакивает.

AIReiter выравнивает этот слой: один ключ, один OpenAI-совместимый интерфейс, за которым доступны все четыре уровня. Чтобы переключиться, достаточно поменять поле model в теле запроса.

# Аргументация выбора уровня: 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": "<промпт для выбора уровня + фрагмент реверсированной логики + список зависимостей>"}]
  }'

# Массовая первичная сортировка: измените одно поле
#   "model": "claude-sonnet-5"
# Установление причины деградации:
#   "model": "gpt-5.6-sol"

Уже используете OpenAI SDK? Укажите для base_url адрес https://aireiter.com/api/v1. Работаете с Anthropic SDK? Отправляйте POST /api/v1/messages с тем же ключом. По цене модели Claude идут со скидкой 30% от прайса, а модели GPT — за половину цены. У этого процесса две основные статьи расходов: массовая первичная сортировка десятков или сотен возможностей — Sonnet, большой объём вызовов — и один длинноконтекстный запрос для чтения всего модуля и анализа его зависимостей — Kimi, много токенов на вызов. Массовая сортировка выполняется на модели Claude, поэтому скидка приходится на самый плотный этап; модель для аргументации выбора уровня тоже Claude и также стоит на 30% дешевле. Длинноконтекстный уровень Kimi K3 доступен с тем же ключом.

  • Получить API-ключ

  • Попробовать без регистрации — сначала вручную передайте несколько фрагментов логики и посмотрите, будут ли две модели поддакивать вам или возражать против вопроса «стоит ли отправлять это в браузерный мост».

Итог

Приземление реверсированной логики — это не столько техническая, сколько экономическая задача. Лестницу из трёх уровней — нативная реализация > локальный JS-движок > пассивный браузерный мост — нельзя переворачивать: на каждом шаге вниз чистая функция меняется на процесс с внешними зависимостями, участием человека и живой вкладкой. Пассивный мост — не запретная зона, а временный компонент с жёсткими границами: один снимок, никакой активности, явная ошибка при отсутствии страницы, чувствительное состояние не выходит из браузера, логин только через явный handoff, статусы этапов всегда понятны. Соблюдайте эти условия — и мост останется надёжной временной мерой; отбросьте хотя бы одно — получите чёрный ящик, который никто не захочет сопровождать. Модель помогает решить, на каком уровне должна оказаться возможность, и удержать линию против инерции «лишь бы заработало». А правильность каждой реализации проверяет не модель, а дифференциальное тестирование.