fix реклама Яндекса: сегмент Аудиторий указывается с приставкой 20 — без неё Директ отвечает «объект не найден»

Настоящая причина, по которой портал не мог завести кампанию. Прежний диагноз «сегменту
нужно не меньше 1000 опознанных людей» оказался ВЫДУМКОЙ: он был построен на статьях,
а не на замере, и увёл в ожидание, которое ничего не решало.

Что происходит. Сегмент Аудиторий указывается в условии ретаргетинга не своим номером,
а номером с приставкой 20: сегмент 58034825 уходит как ExternalId 2058034825. Без приставки
Директ ищет цель Метрики с таким номером и честно отвечает 8800 «Object not found» — тем же
отказом, что и на несуществующий сегмент. Поэтому поломка выглядела как «аудитория не готова».

Замер на боевом кабинете 30.07 на одном и том же ГОТОВОМ сегменте:
   ExternalId 58034825   -> отказ 8800 «Object not found»
   ExternalId 2058034825 -> условие создано, № 41678818
После починки то же самое кодом портала: условие № 41678854 создано и убрано за собой.

Приставку показал сам Яндекс: у уже существующего в кабинете условия на этот сегмент
retargetinglists.get возвращает ровно "ExternalId": 2058034825. Документация про приставки
говорит глухо и только «для интересов» — живой ответ кабинета оказался надёжнее документации.

Попутно снят ложный «дефект продукта». Порога в 1000 человек НЕ существует: сегмент годится
для Директа при 134 опознанных людях из 151 номера. Минимум портала менять не нужно.

Ещё один замер, важный на будущее: статус is_processed у сегмента — это НЕ «готов»,
у готового статус processed, заполнены matched_quantity и cookies_matched_quantity,
и стоит can_create_dependent. Судить о готовности только по can_create_dependent.

Тестов на приставку было ДВА, и оба закрепляли поломку: проверяли, что уходит номер БЕЗ
приставки. Оба были зелёные всё время. Тест, написанный по нашему представлению о правде,
а не по ответу живой системы, охраняет баг и делает его невидимым. Теперь оба проверяют
приставку и падают без неё; реклама портала 43 из 43.

Вместе с починкой ложатся три промта смен: 29.07 сведение телеграма и прокси, 29.07 робот
креативов в работу и 30.07 состояние после живой приёмки. В последнем ложный диагноз про
порог в 1000 человек помечен как неверный прямо в шапке — чтобы следующая смена не пошла
по нему второй раз.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Дмитрий
2026-07-30 10:57:38 +03:00
parent f2bfe3f1da
commit a1567ff545
6 changed files with 530 additions and 4 deletions
@@ -0,0 +1,154 @@
# Промт для следующей смены — телеграм сведён, дальше выкат и адрес для МТС
Дата: 29.07.2026, вечер. Свод закрыт и запушен. Открытым остался адрес для кабинета МТС.
---
Продолжаем Лидерру. Две линии: **выкат телеграм-модуля** и **адрес для кабинета МТС**.
## Линия 1 — телеграм сведён с main. ГОТОВО
**Коммит `77f61fb1`**, ветка **`feat/client-telegram-ads-on-main`**, **в gitea запушено**.
314 файлов, +42708 / −148, два родителя: телеграм `6f469fb1` + main `8bdd58e8`. main влит целиком.
Рабочий каталог `.claude/worktrees/telegram-merge` **полностью снаряжён** — свои `composer install`
и `npm ci`, свои бинари в `bin/`, своя тестовая база **`liderra_testing_tgmerge`**. Не выбрасывать:
следующий каталог придётся снаряжать заново с нуля.
🔴 Исходная ветка `feat/client-telegram-ads` и 292 черновика в главном каталоге **не тронуты**.
### Числа, померенные на своде
| Что | Результат |
|---|---|
| Телеграм | 193/193 |
| Реклама | 336/336 при 1029 проверках |
| Вместе | 532/532 |
| Экраны | 1704/1708, 3 пропущено |
| Сборка фронта | чисто, 3.4 сек |
| Полный Unit+Feature | **3923/3960** |
🔑 **16 падений полного прогона совпадают построчно с прогоном ветки БЕЗ телеграма** — свод не
добавил ни одного. Одиннадцать из них — тесты робота креативов Яндекса, они ходят в **настоящий**
Яндекс живым запросом: подмена ответа накрывает не все адреса. Это хвост соседней ветки.
### Что было починено по дороге
- **Помощник `rejectedCampaign` переименован в `tgRejectedCampaign`.** Одноимённый есть у рекламы,
помощники Pest глобальные — полный прогон падал фаталом. 🔑 Каждая ветка по отдельности этого
увидеть не могла: такие мины вылезают **только при сведении**.
- **Два теста уведомлений считали ВСЮ таблицу** вместо строк своего пользователя. В одиночку
зелёные, в полном прогоне красные.
- **Убраны две проверки отменённой сущности** «своё имя отправителя» — она дропнута в v9.27.
- **Журнал схемы:** телеграмные v8.86–v8.95 → **v9.18v9.27**, блок наверх, пометки
«предварительный» сняты. Версия `db/schema.sql` не тронута: там v8.85 и у main, и у телеграма.
- 🔴 **Авто-починка разметки испортила смысл в `ПИЛОТ.md`**: «**+** нумератор» переписала в
«**−** нумератор». Прод-документ. Откачено, переписано на «**и** нумератор».
🪤 Авто-фиксы разметки просматривать глазами — «+» в начале русской строки линтер считает списком.
### Сторож денег поставлен
`app/tests/Feature/ClientTg/TgMoneyUnderRealRoleTest.php` — три проверки под **боевой ролью**
`crm_app_user`, а не суперюзером. С контекстом клиента деньги двигаются, без контекста возврат
падает **громко**.
🔑 **И тут же вскрылось, на чём держится эта громкость:** база без контекста молча отдаёт ноль
строк, шумит только `firstOrFail()` в сервисе. Заменят на `first()` — возврат денег за отклонённую
модерацию станет тихим. Записано в самом тесте.
🪤 Тестовая база собирается миграциями, а права ролям на бою раздаёт **отдельный скрипт**
`db/02_grants.sql`. Без него сторож мерил бы отсутствующий доступ вместо защиты по клиентам.
### Что дальше по этой линии
1. Решить с владельцем: вливать ли `feat/client-telegram-ads-on-main` в `main`.
2. Выкат можно делать **не дожидаясь адреса** — модуль поедет с `TG_SANDBOX=true`: только
черновик, деньги не двигаются. Робот МТС без адреса не заработает, но это **другая задача**.
3. 🔴 При выкате на кластер ПЕРЕзапустить `db/03_service_bypass_policies.sql` — новым RLS-таблицам
мало `tenant_isolation`, иначе служебные роли получат тихий ноль.
4. Хвост: статанализ исключался при коммите. Из 628 замечаний **623 в тестах** обеих сторон
Pest-шум. Если браться — сначала пересобрать baseline под слитое состояние.
## Линия 2 — адрес для кабинета МТС. ЖДЁМ ПОСТАВЩИКА
**Куплено сегодня:** резидентный российский адрес, 1 месяц, `91.221.70.204:24009`, логин `lte4`,
выход в интернет **95.24.162.70**, срок до **28.08.2026**, смена адреса ручная по ссылке.
Списано **$12.28**, баланс **$131.06**.
🔴 **Не годится.** Замер с нашего сервера через этот прокси:
| Куда | Что вышло |
|---|---|
| ya.ru | HTTP 302 за 0.28 сек |
| **marketolog.mts.ru** | **обрыв за 0.02 сек** |
| mts.ru | обрыв за 0.02 сек |
| beeline.ru | обрыв за 0.02 сек |
Настоящий Chrome через него получает `ERR_TUNNEL_CONNECTION_FAILED`. Это **тот же запрет на сайты
операторов**, что на мобильном канале. Их слова «наш домен под ограничения не попадает» замером не
подтвердились. **Обходить не пытались и не надо** — это их сознательный запрет.
🔴 **Отдельно: пропали $53.33.** Баланс сам съехал 196.67 → 143.34, в истории операций строки
**нет**, мобильный канал 9493547 **не продлён** — срок как был 04.08.2026 19:50. Сумма копейка в
копейку равна цене продления.
**Обращение №31027** — открыто сегодня, в нём и замер, и вопрос про 53.33. Прежнее №30979 они
закрыли **молча, без единого ответа**.
Проверять появление адреса: `POST https://api.dashboard.proxy.market/dev-api/list/{ключ}` с телом
`{"type":"ipv4"}`; ключ в `%TMP%\pm_api_key.txt`. Резидентные в этом списке **не показываются**
их видно только в кабинете. Браузер под управлением: Edge на порту **9222**, профиль
`C:\liderra\pm-profile`, вход выполнен.
🪤 В форме нового обращения кнопка называется **`Create`**, и на странице есть ещё одна —
**`Create the ticket`**, которая в DOM стоит РАНЬШЕ. Поиск по началу строки цепляет её и молча
пересоздаёт пустую форму. Искать по точному совпадению.
🪤 Поле ответа в списке обращений однострочное и режет длинный текст; в форме НОВОГО обращения
поле нормальное, 1700 знаков проходят. Вставка не работает — печатать клавиатурой.
🪤 Своё фоновое слежение перезагружает вкладку и стирает набранное — останавливать перед работой.
**Если ответа не будет:** искать другого поставщика резидентных российских адресов без запрета на
сайты операторов. Похоже, у Proxy.Market это правило на весь дом, а не на конкретный продукт.
## Роботы и сервер
**`liderra-render`** — `51.250.1.97`, доступ `ssh -i ~/.ssh/liderra_deploy ubuntu@51.250.1.97`.
Стоят Chrome 150, Xvfb, privoxy:8118, playwright в `/opt/liderra-render`. Соседи — Asterisk, Caddy,
рендер 2ГИС; не мешать.
**Робот Яндекса** от прокси не зависит. Для доселения нужны **от владельца**: логин рекламного
кабинета, «прихожая» — остановленная кампания и группа, разрешение прописать `CREATIVE_ROBOT_TOKEN`
на боевом.
🔴 Голосовой сервер `185.182.110.115` **мёртв** — ни пинга, ни одного порта. Александра и Даша
лежат там.
## 🪤🪤 Главная ловушка дня — про инструменты
**В свежем git-worktree проверки не работают и при этом ВРУТ человеческим текстом.** Первый заход
коммита показал пять «замечаний»; три из пяти оказались упавшим инструментом, а не находкой:
| Проверка | Настоящая причина |
|---|---|
| «cspell нашёл слова» | пакета `cspell` нет — падение при запуске |
| «gitleaks нашёл ПДн» | нет бинаря `bin/gitleaks.exe` |
| «lychee нашёл битые ссылки» | нет бинаря `bin/lychee.exe`, потом — два файла, которых нет в git |
| «larastan нашёл ошибки» | нет `app/_ide_helper_models.php`, phpstan молча выходит с ошибкой |
🔑 **Датчик:** проверка жалуется содержательно, но ты не увидел ни одной конкретной строки —
значит смотришь на упавший инструмент, а не на находку. Ставить инструмент и перезапускать.
**Рецепт снаряжения свежего каталога:** `composer install` + `npm ci` в `app/`, `npm ci` в корне,
скопировать `bin/gitleaks.exe` и `bin/lychee.exe`, при нужде — `app/_ide_helper_models.php`.
🪤 На Windows `vendor` и `node_modules` **нельзя подсунуть ссылкой** — нужен свой установ.
🪤 `npm ci --ignore-scripts` оставляет пакеты **обрезанными** — половина файлов отсутствует, а
ошибки нет. Ставить полностью.
🪤 Настоящие находки тоже были: разрешение gitleaks по **номеру коммита** не переживает сведение
ветки — тот же текст приходит новым коммитом, нужен путь.
## Красные линии
Прод — живые клиенты и деньги, выкат только по явному «go», БД по умолчанию только чтение.
**Пуш только в gitea**, GitHub мёртв. Коммит по эскейпу, сообщение без круглых скобок, в файле,
явные пути. **В СМС-модуль не лезть.** Кампанию № 713051718 не трогать. Владелец не программист —
говорить простым русским.
@@ -0,0 +1,123 @@
# Промт для следующей смены — запустить рекламу в Яндексе в работу
Дата: 29.07.2026, вечер. Всё ниже **проверено живой командой сегодня**, а не по памяти.
---
Задача одна: **довести рекламу в Яндексе до работающего состояния на боевом liderra.ru.**
Портал умеет всё, кроме одного шага — картиночный креатив программно не создать (приём
`creatives.add` берёт только видео, конструктор Яндекс закрыл 01.06.2026). Этот шаг делает
робот-грузчик в браузере. Робота написали, живьём он ни разу не работал.
## Что уже живо на боевом
- **Загрузка аудиторий в Яндекс работает** — ключ `YANDEX_AUDIENCE_TOKEN` прописан,
в `sales_ad_audience_platforms` 3 записи.
- Экран «Яндекс Аудитория» открыт клиентам с 25.07.
- Кампаний в базе **2, обе черновики** (клиент 2 от 25.07, клиент 10 от 29.07 05:42).
## Что мертво (и почему)
| Что | Состояние на 29.07 | Что это значит |
|---|---|---|
| `YANDEX_DIRECT_ENABLED` | **`false`** | канал Директа закрыт флагом |
| `YANDEX_DIRECT_TOKEN` | прописан | ключ есть, ждёт включения |
| `CREATIVE_ROBOT_TOKEN` | **отсутствует** | дверь робота в портал закрыта наглухо |
| `ad_creative_jobs` | **0 строк** | робот не отрабатывал ни разу |
| Каталог `bots/` на боевом | **отсутствует** | 🔴 выкат копирует только `app/`, роботы лежат рядом в `bots/` — они на прод НЕ приезжают |
| Робот на рендер-виртуалке | **отсутствует** | там только служба снимков сайтов |
## Три вещи, без которых не начинать — они за владельцем
1. **Логин рекламного кабинета Яндекса**, под которым будет работать робот. Идёт в адресах
Директа параметром `ulogin`.
2. **«Прихожая»** — заранее заведённые в кабинете **остановленная** кампания и группа
(`YC_ANTEROOM_CAMPAIGN_ID`, `YC_ANTEROOM_GROUP_ID`). Робот открывает форму нового
объявления в них только чтобы дойти до окна «Загрузить креативы», ничего не сохраняет.
🔑 Прихожая обязана быть отдельной остановленной кампанией — тогда робот не приближается
к работающей рекламе вовсе.
3. **Разрешение** прописать `CREATIVE_ROBOT_TOKEN` на боевом и включить
`YANDEX_DIRECT_ENABLED=true`.
Плюс от владельца понадобятся **глаза и телефон один раз** — вход в кабинет заводится
руками (пароль + код из СМС) и дальше живёт в папке профиля браузера.
## Что такое `CREATIVE_ROBOT_TOKEN` (владелец спрашивал)
Это **пароль на служебную дверь «робот → портал»**, к Яндексу отношения не имеет, никто
его не выдаёт — придумываем сами длинной случайной строкой. Робот стучится в портал по
трём адресам (`/api/creative-robot/next`, `.../jobs/{id}/banners/{id}/file`,
`.../jobs/{id}/done` + `/inspection`) и показывает заголовок `X-Creative-Robot-Token`.
Проверка — `app/app/Http/Middleware/CreativeRobotToken.php`. Пароль пишется в двух местах:
боевой `.env` портала и `.env` робота. Пустой в настройках = дверь закрыта (401) — сейчас
именно так, и это нарочно.
## Где робот жить будет — рекомендация
**Рендер-виртуалка `liderra-render` 51.250.1.97**, а не боевой сервер: браузером боевой
нагружать не надо. Проверено сегодня, что там есть:
- `Xvfb` — есть; `node v20.20.2`; `google-chrome` системный; playwright-браузер
`chromium-1228` в `~/.cache/ms-playwright`;
- диск 15 ГБ свободно, память 7.9 ГБ (занято 0.4);
- служба-сосед `liderra-render.service`**не мешать**, там же Asterisk и Caddy.
🔴 **VNC/RDP на виртуалке НЕТ** (`x11vnc`/`xrdp` не установлены). Для разового входа глазами
их надо поставить и пробросить портом через SSH-туннель — наружу порт не открывать.
Доступ: `ssh -i ~/.ssh/liderra_deploy ubuntu@51.250.1.97`.
## Порядок работ
1. **Спросить у владельца три вещи выше.** Без прихожей и логина дальше идти нельзя.
2. Прочитать `bots/yandex-creatives/README.md` и `docs/cabinet-flow.md` (разметка экранов
кабинета, снята живьём) — там же список писем-алярмов и что делать по каждому.
3. Разложить робота на рендер-виртуалку: скопировать `bots/yandex-creatives`, свой
`npm ci` (playwright + nodemailer + dotenv), заполнить `.env` из `.env.example`.
4. Поставить `x11vnc`, поднять `Xvfb`, пробросить порт туннелем, позвать владельца —
`npm run login`. Профиль ложится в `YC_BROWSER_PROFILE_DIR`.
5. Прописать `CREATIVE_ROBOT_TOKEN` в боевой `.env` портала (тот же, что у робота) +
`php artisan config:cache` **от www-data** (🪤 квирк 107: под root ломает прод).
6. Проверить дверь: запрос к `/api/creative-robot/next` без заголовка → 401, с верным
заголовком → осмысленный ответ.
7. Включить `YANDEX_DIRECT_ENABLED=true`. **Это открывает деньги и показы** — только по
явному «да» владельца.
8. Расписание на виртуалке: `run:once` часто, `keepalive` раз в ~15 минут. Наложиться они
не могут — общий файл-замок `robot.lock`, занят → выход с кодом 0.
9. **Живая приёмка на одной кампании**: завести кампанию с баннерами, дождаться задания,
посмотреть, что робот залил креативы, портал сверил список до/после и кампания поехала.
Смотреть глазами в кабинете, а не только в журнал.
## Ловушки, известные заранее
🪤 **Выкат портала не везёт `bots/`.** Каталог лежит в корне репозитория, а rsync идёт из
`app/`. Робота на любую машину класть отдельно, руками.
🪤 **11 падений полного прогона тестов — это тесты робота креативов**, они ходят в
**настоящий** Яндекс живым запросом: подмена ответа накрывает не все адреса. Падают
одинаково и с телеграмом, и без него. Не пугаться и не «чинить» вслепую.
🪤 **Робот не нажимает «Сохранить изменения»** — единственную кнопку, которая меняет
кабинет. На это есть тест-сторож. Если появится соблазн «дожать» — сначала подумать, зачем
сторож ставили.
🪤 **Снимки экрана робота показывают логин и остаток счёта** в боковой панели кабинета.
Робот сам чистит старше двух недель; в переписку и в git их не тащить.
🪤 **Повторная заливка оставляет дубли креативов**, вычищать можно только руками. Если
письмо-алярм кончается словами «Креативы в кабинет уже загружены» — сначала посмотреть
кабинет, потом решать про перезапуск.
## Красные линии
Прод — живые клиенты и деньги, любое включение только по явному «да» владельца, БД по
умолчанию только чтение. **Пуш только в gitea**, GitHub мёртв. Коммит по эскейпу, сообщение
без круглых скобок, в файле, явные пути. **В СМС-модуль не лезть.** Телеграм-кампанию
№ 713051718 не трогать. Владелец не программист — говорить простым русским.
## Сегодняшний фон (не мешает, но знать)
- Телеграм-модуль выкачен на боевой 29.07 в песочнице (`TG_SANDBOX=true`), деньги не
двигаются. Подробности — `2026-07-29-HANDOFF-telegram-sveden-i-proksi.md`.
- Адрес для кабинета МТС не найден, обращение к Proxy.Market №31027 открыто. **К Яндексу
отношения не имеет** — робот Яндекса от прокси не зависит.
@@ -0,0 +1,210 @@
# Промт для следующей смены — реклама Яндекса после живой приёмки
Дата: 30.07.2026, утро. Всё ниже **проверено живой командой**, а не по памяти.
Предыдущий промт — `2026-07-29-HANDOFF-yandex-robot-v-rabotu.md` (он уже исполнен).
> 🔴 **Правка от 30.07 09:00 МСК.** Первая версия этого промта содержала **неверный диагноз**:
> будто Директ отказывает из-за порога «не меньше 1000 опознанных людей» в сегменте.
> Это оказалось неправдой, и вся ветка «нужен список побольше» была ложной. Настоящих причин
> было **две**, обе найдены и одна из них — наша ошибка в коде. Текст ниже исправлен.
---
Задача одна: **довести до конца последний шаг рекламы в Яндексе и убрать за приёмкой.**
Связка «портал → робот → кабинет Яндекса → опознание креативов → условие ретаргетинга
в Директе» **работает и проверена живьём по частям**. Не сделан последний стык: кампания
№ 6 ещё не заведена в Директе, и упирается это теперь **только в сторону Яндекса**.
## Что живо и проверено настоящим прогоном 29–30.07
| Что | Состояние |
|---|---|
| Робот на рендер-виртуалке `51.250.1.97`, `/opt/liderra-creative-robot` | стоит, **84 из 84** проверок зелёные |
| Вход в кабинет `sasha261185` | заведён глазами владельца, живёт в профиле, `keepalive` подтверждает |
| Расписание робота | `…-run.timer` каждые 2 мин, `…-keepalive.timer` каждые 15 мин, оба **включены** |
| Дверь «робот → портал» | без пароля 401, с верным 200 |
| Заливка картинок роботом | **15 из 15**, задание закрыто как `done` |
| Опознание креативов порталом | **15 из 15** номеров проставлены баннерам |
| **Условие ретаргетинга в Директе кодом портала** | **создано живьём** (№ 41678854 на готовом сегменте, сразу убрано) |
| Разведка причины отказа | читает обе причины целиком, 5–6 с на объявление |
Доступ к виртуалке робота: `ssh -i ~/.ssh/liderra_deploy ubuntu@51.250.1.97` (отдельной строки
в `~/.ssh/config` нет). Боевой — `ssh liderra-prod` (через бастион gitea).
## Кампания-подопытная
- Кампания **№ 6**, фирма **2** (`info@lkomega.ru`), имя «Приёмка робота — займ под залог».
- Статус **черновик**, режим ручной, свой список номеров.
- **1693 номера** — графа Д из `C:\Users\Administrator\Downloads\1785082209_report_606058_380216.xlsx`
(выгрузка лидов, личные мобильные). 🔴 Это ПДн: в git не тащить, в переписку не выводить.
- Сегмент Аудиторий **58283031**, файл переварен целиком, 1693 из 1693 годных.
- Смета 1693 показа, клиенту ≈ **203,16 ₽**. В рекламном кошельке фирмы **300 ₽**,
заморожено 0, не списано ничего.
- 15 баннеров нарисованы Claude: «ЗАЙМ ПОД ЗАЛОГ АВТО» + обязательное предупреждение про
условия займа на 22 % высоты. 🪤 На 320×50 надпись 4 пункта — площадь требованию отвечает,
но модерация может придраться к читаемости.
- Все выгрузки лидов владельца вместе дают всего **1873** личных мобильных (замер 30.07):
больше номеров из этого источника взять негде, и **этого больше не требуется** — см. ниже.
## Почему кампания не заводилась — две причины, не одна
Директ отвечал **8800 «Object not found»**. Долго считалось, что дело в размере аудитории.
**Это неверно.** Разбор 30.07 дал две независимые причины:
**Причина 1 — наша ошибка в коде (исправлена, проверена живьём).**
Сегмент Аудиторий указывается в условии ретаргетинга **не своим номером, а с приставкой
«20»**: сегмент `58034825``ExternalId 2058034825`. Мы посылали номер без приставки, Директ
искал цель Метрики с таким номером и честно отвечал «объект не найден» — тем же отказом, что
и на несуществующий сегмент. Отсюда и путаница.
Замер на одном и том же **готовом** сегменте:
| Что послали | Ответ живого Директа |
|---|---|
| `ExternalId 58034825` (как было) | отказ 8800 «Object not found» |
| `ExternalId 2058034825` (с приставкой) | **условие создано**, № 41678818 |
Приставку показал **сам Яндекс**: у уже существующего в кабинете условия `retargetinglists.get`
возвращает ровно `"ExternalId": 2058034825`. Документация Директа про приставки говорит
глухо (10/20/30 — «для интересов»), живой ответ кабинета оказался надёжнее.
**Причина 2 — Яндекс ещё не сопоставил наши номера (ждём, ускорить нельзя).**
У свежего сегмента поля `matched_quantity` / `matched_percentage` **пустые**, статус
`is_processed`, в кабинете «Обрабатывается». Пока сопоставления нет, Директ сегмента не видит
даже с правильной приставкой.
🪤 **Ловушка на будущее:** `is_processed` — это НЕ «готов». У готового сегмента статус
`processed` (без `is_`), заполнены `matched_quantity` и `cookies_matched_quantity`,
и стоит `can_create_dependent: true`. Судить о готовности **только** по `can_create_dependent`.
**Порога в 1000 человек не существует.** Живое доказательство: сегмент 58034825 годится
для Директа при **134 опознанных людях** из 151 номера. Прежнее утверждение про «минимум
1000» — выдумка, построенная на статьях, а не на замере. Сюда же: **никакого дефекта
«портал пишет 100, а надо 1000» нет** — минимум портала трогать не нужно.
## Что сейчас делает сторож — и что делать смене
Служба `liderra-priemka-storozh` (скрипт `/usr/local/bin/storozh.sh`) переписана 30.07:
раз в 10 минут пишет в журнал **живые цифры** (статус, сколько людей опознано, охват),
ждёт до **48 часов** и при `can_create_dependent: ДА` сам запускает кампанию через
`/tmp/zapusk.php 6`.
**Первое действие смены — `sudo tail -20 /var/log/liderra-priemka-storozh.log`.** Три ветки:
1. Строка «КАМПАНИЯ ПОЕХАЛА» — связка доказана целиком. Дальше: посмотреть кабинет глазами,
дождаться вердикта модерации, проверить окно передачи отказов на живом отказе.
2. В журнале появились цифры опознанных, но `годен нет` — Яндекс считает, ждать.
3. 48 часов прошло, цифры так и не появились — **это уже вопрос к Яндексу**, а не к коду.
Написать в поддержку Аудиторий (сегмент 58283031 висит непосопоставленным больше двух
суток при 100 % годных строк) и **спросить владельца**, ждать или брать другой список.
🪤 Сторож стоит **не в git** — только на боевом. Прежняя версия ждала 12 часов и писала
глухое «нет»; она бы дождалась готовности и всё равно упала на приставке. Это тот же класс:
**сторож, который «проверяет», может не проверять того, что нужно.**
## 🔴 Главное, что нельзя забыть
**Починки на боевом лежат руками, а не из хранилища.** Прямо на прод подложены:
- `app/Services/Advertising/YandexDirectClient.php` (в нём же приставка «20»)
- `app/Services/Advertising/CampaignLauncher.php`
- на виртуалке `src/cabinet.js`, `bin/run.js`, `bin/keepalive.js`
В хранилище: ветка **`fix/robot-yandex-zamok`** (коммиты `61ac314d`, `f2bfe3f1` отправлены
в gitea; правка приставки — **ещё не закоммичена на момент написания**, спросить эскейп).
В main **не влито**. 🪤 Любой обычный выкат из main вернёт все семь дыр обратно.
Первое дело — **предложить владельцу влить ветку в main**.
## Что чинили — чтобы не «починить» обратно
1. Слепок креативов просил кабинет пустым **массивом** — живой Яндекс отказывает 8000.
Нужен пустой **объект**.
2. Отказы Яндекса **по позиции внутри ответа** глотались молча, портал падал «нет ключа Id».
Теперь слова Яндекса выходят наружу — именно это позволило найти следующий пункт.
3. В условии ретаргетинга не было `MembershipLifeSpan`. Границы Яндекса 1..540; **ноль
нельзя** при создании, хотя в старых условиях кабинета нули стоят.
4. Робот не нажимал «Добавить выбранные». «Набор» в кабинете и «креатив» в `creatives.get`
разные вещи: без добавления в черновик формы Яндекс креативы не регистрирует. Замер
18 → 32 в ту же секунду. Объявление при этом НЕ сохраняется.
5. Гонка: «Создать» открывается на 4-й секунде при 11 принятых файлах из 15, остальные
доезжают к 7-й. Три прогона дали 14 из 15, каждый раз пропадала другая картинка.
6. Замок робота не снимался — `process.exit()` не даёт выполниться `finally`.
7. **Приставка «20» у номера сегмента** (30.07) — см. выше. Тестов на неё было два, и **оба
закрепляли поломку**: проверяли, что уходит номер БЕЗ приставки.
🪤 **Урок из пункта 7:** тест, написанный по нашему представлению о правде, а не по ответу
живой системы, **охраняет баг** и делает его невидимым. Оба теста были зелёные всё время.
## Незакрытый вопрос продукта — решать владельцу
Порога в 1000 нет, но **задержка сопоставления есть**, и клиент её увидит: он загружает
номера, жмёт «Запустить» и получает непонятную ошибку, потому что Яндекс ещё считает.
Правильно — не пускать в запуск, пока сегмент не стал годным, и писать по-человечески:
«Яндекс ищет ваших клиентов в сети, обычно это занимает несколько часов». Это **меняет
поведение продукта** (появляется ожидание вместо мгновенного запуска), поэтому Claude
сам не менял. Спросить.
## Уборка, которую Claude намеренно не делал
- 🔴 **В кабинете Яндекса ~57 лишних креативов** — четыре набора от прогонов. Все называются
«Новый креатив», отличить от настоящих нельзя, ошибка стоила бы боевых креативов владельца.
Удалять только владельцу, кнопкой «Удалить набор».
- 🔴 **Четыре лишних сегмента** в Аудиториях, все с именем «Лидерра кампания #6»:
58275925 (503), 58275978 (503), 58276009 (503), 58282999 (2025).
**НЕ удалять:** 58283031 (1693 — рабочий) и 58034825 («Лидерра — кандидаты», в работе).
- **300 ₽ на рекламном кошельке фирмы 2** — положены Claude для приёмки, не потрачены.
## Настройки боевого, изменённые 29–30.07 — и как откатить
| Строка в `.env` | Стало | Откат |
|---|---|---|
| `YANDEX_DIRECT_ENABLED` | `true` | `false` |
| `YANDEX_DIRECT_BASE_URL` | `https://api.direct.yandex.com` | убрать строку = вернётся песочница |
| `CREATIVE_ROBOT_TOKEN` | выпущен | убрать строку = дверь робота закрыта |
Копии прежнего файла рядом: `.env.bak-2026-07-29-robot`, `.env.bak-2026-07-29-direct-off`,
`.env.bak-2026-07-29-sandbox`. Прежняя версия кода с приставкой — рядом с файлом:
`YandexDirectClient.php.bak-2026-07-30-bez-pristavki` (восстановлена из коммита `f2bfe3f1`,
🪤 первый бэкап затёрся новой версией — проверять бэкап, а не верить ему).
После любой правки `.env``config:clear` + `config:cache` **от www-data**
(🪤 квирк 107: под root ломает прод) и `reload php8.3-fpm`.
## Ловушки, добытые кровью
🪤 **Выкат портала не везёт `bots/`** — робота на любую машину класть отдельно, руками.
🪤 **`php artisan tinker <файл>` открывает интерактив и вешает ssh.** Работает только
`--execute`, а для длинного — отдельный php-скрипт с ручным `bootstrap()` в `/tmp`.
🪤 **`app(YandexDirectClient::class)` не собирается** — у него в конструкторе адрес и токен
строками. Создавать через `new` с `config(...)`.
🪤 **`pkill -f имя-скрипта` через ssh убивает сам себя** — в командной строке удалённого
bash встречается то же имя. Гасить по номеру процесса.
🪤 **Список кампаний/лидов через `tinker` приходит ПУСТЫМ** — это защита по фирмам, а не
правда о базе. Читать через `DB::connection('pgsql_admin')`, писать — в транзакции
с `SET LOCAL app.current_tenant_id`.
🪤 **У клиентских пользователей нет токенов** (`createToken` не существует) — кабинет на
сессии. Ходить настоящими контроллерами с подстановкой пользователя, обернув как middleware.
🪤 **Браузер робота и расписание не делят профиль** — перед ручным заходом в кабинет
останавливать оба таймера, после — возвращать. Иначе робот пишет «уже работает» и уходит.
🪤 **Скрипт `.ps1` с кириллицей надо сохранять с меткой UTF-8**, иначе Windows PowerShell
читает его как абракадабру и падает на разборе.
🪤 **Сторож, который «проверяет», может не проверять ничего.** Дважды за две смены: сначала
проверка гонки считала размеры в окне и не заметила постоянного фильтра из тех же размеров;
потом сторож приёмки ждал готовности сегмента, но запустил бы кампанию в сломанный запрос.
🔑 **Всегда искать независимый признак, что сторож правда сработал** — и спрашивать
не «сработал ли признак», а «что именно он доказывает».
## Красные линии
Прод — живые клиенты и деньги, любое включение только по явному «да» владельца, БД по
умолчанию только чтение. **Пуш только в gitea**, GitHub мёртв. Коммит по эскейпу, сообщение
без круглых скобок, в файле, явные пути. **В СМС-модуль не лезть.** Телеграм-кампанию
№ 713051718 не трогать. Владелец **не программист** — говорить простым русским, без
технических слов; длинные разборы он не читает, нужны короткие абзацы по существу.