გორა.
AI-translation · draft (awaiting native review)
Ферма — соцсети на живых телефонах

ტექნიკური სტეკი

Два парка на одном main, обмен через SQLite WAL, жизнь задачи с пятью гейтами, разведка селекторов прежде кода, флаги, деплой и самопроверка системы.

~5 წუთი წასაკითხი · 972 სიტყვა

TL;DR

Хаб на Node ставит задачи, пул воркеров на Python их выполняет, обмен идёт через общий SQLite в режиме WAL. Между «задача создана» и «палец коснулся экрана» стоят пять гейтов. Селекторы привязаны к версии приложения, и задача скорее упадёт, чем ткнёт наугад. Деплой идёт только через CI и умеет откатывать код, но не схему — и это сказано честно.

Технический стек

Два парка, один main

Репозиторий общий для двух ферм: локального USB-парка на семь телефонов и выделенной Windows-машины на девять. Постоянных веток под ферму нет — только временные под задачу. Фарм-специфичных значений в коде не бывает вовсе: хост, путь, таймзона, серийник устройства, почта — всё живёт в .env своей фермы.

Держат это две проверки в CI. Первая запрещает новые фарм-литералы в коде. Вторая требует, чтобы у каждого нового алиаса селектора стояла пометка, на какой ферме и на каком устройстве он разведан: алиас — это утверждение «я проверил этот билд на своём телефоне», а Pixel одной фермы не равен Samsung другой.

Почему обмен через базу, а не по HTTP

Хаб и воркер не ходят друг к другу по сети. Оба работают с одним файлом SQLite в режиме WAL, и у этого три следствия. Одна точка правды: состояние задачи не расходится между двумя процессами. Воркер переживает перезапуск хаба и наоборот — очередь лежит на диске, а не в памяти. И читатели не блокируют писателя, поэтому панель может опрашивать состояние сколько угодно часто, не мешая работе.

Цена решения — оба процесса обязаны жить на одной машине. Для фермы это не ограничение: телефоны всё равно висят на USB того же хоста.

Жизнь одной задачи

  1. Планировщик раскладывает задачи по неделе исходя из расписания персоны и весов активности — у каждой персоны свой профиль нагрузки.
  2. Задача ложится в очередь со временем запуска и приоритетом.
  3. Воркер клеймит её по замку на телефон: два процесса не могут одновременно трогать один экран.
  4. Перед выполнением проходят пять гейтов: окно активности персоны в её таймзоне; ночная пауза; парковка профиля; кулдаун телефона после тревоги; подтверждённая связь устройства с сетью.
  5. Задача выполняется под лизом с истечением. Если воркер умер, лиз протухает и задача возвращается в очередь — кроме последственных задач, где повторить вслепую нельзя.
  6. Исход пишется явно: выполнено, упало с причиной, пропущено или отложено в ручной разбор.

Последний пункт принципиальный. Если перезапуск оборвал задачу с необратимым действием, система не повторяет её никогда: прерванный тап мог уже дойти до площадки. Судьбу такой задачи выбирает человек.

Разведка прежде кода

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

Поэтому ни одна задача не пишется по угаданным селекторам. Порядок жёсткий:

  • Селекторы живут отдельно и привязаны к версии сборки; в теле задачи идентификаторов нет вообще.
  • Первая строка каждой задачи после подключения к устройству — сверка карты с экраном. Не совпало — исключение, и задача падает понятно.
  • Сырые дампы экранов хранятся по версиям: 10 приложений, 75 сборок.
  • Когда площадка выкатывает новый билд, сверка обязана сломаться. Это не баг, а замысел: хаб помечает версию устаревшей, и дальше идёт повторная разведка.

Поверх этого работает очередь самолечения: система сама снимает экран, предлагает правку карты и ждёт человека. Утверждает всегда человек.

Флаги

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

Деплой

Деплой идёт только через CI: пуш в main → self-hosted runner → скрипт развёртывания по обратному SSH-туннелю. Ручной запуск скрипта запрещён регламентом, а пуш в main блокирует git-хук.

Цепочка выглядит так:

  1. Drain — кооперативный. Ставится файл-флаг, воркер перестаёт брать новое и доигрывает начатое. Ожидание до 46 минут, и по таймауту прерывается деплой, а не живая задача. Файл-флаг снимается ловушкой на любом выходе — страховка, поставленная после конкретного инцидента в июле.
  2. Снапшот базы через VACUUM INTO с последующей проверкой читаемости нового файла. Провал проверки убивает деплой до того, как что-либо коснётся схемы.
  3. Схема — аддитивная и идемпотентная, применяется при старте хаба. Версионированного раннера миграций нет, обратных миграций нет тоже.
  4. Health-gate — проверка, что поднявшийся хаб отвечает.
  5. Откат кода на предыдущий релиз при провале сборки, распаковки или установки зависимостей.

Честно о границах: на провале health-gate откат не запускается — деплой падает, а новый хаб остаётся раскатанным. Схема назад не откатывается по замыслу, поэтому безопасен именно откат старого кода на новую схему, а снапшот базы лежит на случай катастрофы и накатывается руками.

Система не верит своим отчётам

Это самая необычная часть проекта. Ежедневная сводка не перечисляет успехи — она ищет места, где успех был засчитан зря:

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

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

Честные пределы

  • Масштаб упирается в физику: USB-порты, питание, место. Телефоны нельзя развернуть в облаке.
  • Хаб и воркер обязаны жить на одной машине — цена обмена через файл базы.
  • Ночная пауза и окна персон уменьшают дневную пропускную способность намеренно.
  • Новая версия приложения останавливает работу на ней до повторной разведки. Это выбор в пользу аккаунта, а не в пользу метрик.