Владелец решил катить. Промт написан под это, а не под хвосты.
Главное, что вскрылось при подготовке: работа разошлась по ДВУМ веткам, и ни
одна не содержит другую. У соседней ветки tg-zagolovok нет наших трёх правок
тестов от 01.08, у нас нет их заголовка объявления. Обе от основной не
отстают. Поэтому первый шаг смены - свести ветки, иначе выкатим половину.
Выкат намеренно разрезан надвое. Портал можно катить сразу: переключатель
остаётся в старом положении, поведение не меняется, новые таблицы и канал
уезжают спящими. Робота включать некуда, пока не решено, на какой машине он
живёт - МТС не пускает адреса дата-центров, это упирается в обращение 31074.
В промте прописаны обязательные пред-полётные: журнал схемы для их миграции
записью v9.31 - её нет вообще, а это то самое правило, из-за нарушения
которого ночью столкнулись номера; пересборка списка исключений статанализа
после сведения; проверка их нового теста на добавленный запрет выхода
в интернет; ПЕРЕзапуск service_bypass_policies, иначе канал робота увидит
ноль заданий молча.
Приёмка - глазами в кабинете МТС, а не по журналу портала. Добавлены две
находки для владельца, ничего не тронуто, и одиннадцать ловушек.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Владелец решил катить. Промт написан под это, а не под хвосты.
Главное, что вскрылось при подготовке: работа разошлась по ДВУМ веткам, и ни
одна не содержит другую. У соседней ветки tg-zagolovok нет наших трёх правок
тестов от 01.08, у нас нет их заголовка объявления. Обе от основной не
отстают. Поэтому первый шаг смены - свести ветки, иначе выкатим половину.
Выкат намеренно разрезан надвое. Портал можно катить сразу: переключатель
остаётся в старом положении, поведение не меняется, новые таблицы и канал
уезжают спящими. Робота включать некуда, пока не решено, на какой машине он
живёт - МТС не пускает адреса дата-центров, это упирается в обращение 31074.
В промте прописаны обязательные пред-полётные: журнал схемы для их миграции
записью v9.31 - её нет вообще, а это то самое правило, из-за нарушения
которого ночью столкнулись номера; пересборка списка исключений статанализа
после сведения; проверка их нового теста на добавленный запрет выхода
в интернет; ПЕРЕзапуск service_bypass_policies, иначе канал робота увидит
ноль заданий молча.
Приёмка - глазами в кабинете МТС, а не по журналу портала. Добавлены две
находки для владельца, ничего не тронуто, и одиннадцать ловушек.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Промт следующей смене врал в самом громком месте: хвост Н1 был записан как
срочный - "похоже, протух боевой ключ Яндекс Директа". Это оказалось ложной
тревогой, и написал её я сам, не проверив трёх фактов, опровергающих её
за пять минут. Ключ в тесте подставной, локально боевого ключа нет вовсе,
а в Яндекс тест ходил потому, что подбирал чужое задание из базы.
Раздел Н1 переписан: пометка "закрыт, тревога ложная", разбор настоящей
причины и урок про слово "срочно" в промте. Раздел Х4 помечен закрытым -
тест внешних сервисов больше не зависит от сети.
В Х2а вписан готовый датчик поиска файла, который гадит в базу, и ловушка
про то, что искать уборку словом нельзя. В Х2б у "чужого писателя" появилось
имя - параллельная смена в папке tg-zagolovok, она ходит в общую базу.
Добавлены две находки для владельца, ничего не тронуто: ночная сборка слепка
падает целиком от одного проекта без источника лидов, и начальник отдела
при запросе всего отдела видит всех клиентов системы.
В ловушки дописан запрет на выход в интернет из тестов с пояснением, почему
его нельзя снимать.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Промт следующей смене врал в самом громком месте: хвост Н1 был записан как
срочный - "похоже, протух боевой ключ Яндекс Директа". Это оказалось ложной
тревогой, и написал её я сам, не проверив трёх фактов, опровергающих её
за пять минут. Ключ в тесте подставной, локально боевого ключа нет вовсе,
а в Яндекс тест ходил потому, что подбирал чужое задание из базы.
Раздел Н1 переписан: пометка "закрыт, тревога ложная", разбор настоящей
причины и урок про слово "срочно" в промте. Раздел Х4 помечен закрытым -
тест внешних сервисов больше не зависит от сети.
В Х2а вписан готовый датчик поиска файла, который гадит в базу, и ловушка
про то, что искать уборку словом нельзя. В Х2б у "чужого писателя" появилось
имя - параллельная смена в папке tg-zagolovok, она ходит в общую базу.
Добавлены две находки для владельца, ничего не тронуто: ночная сборка слепка
падает целиком от одного проекта без источника лидов, и начальник отдела
при запросе всего отдела видит всех клиентов системы.
В ловушки дописан запрет на выход в интернет из тестов с пояснением, почему
его нельзя снимать.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Полный набор стал зелёным целиком: 4063 теста, 0 падений и 0 ошибок против
36 непроходящих до правок. Рабочий код не тронут - изменены только тесты.
Корень у пяти правок один: тест зелёный, а после себя оставляет мусор в базе,
и падают соседи. Три файла рекламы писали набело, без отката. Их записи -
задание роботу и проекты без источника лидов - переживали тест, и дальше
в том же прогоне их подбирали чужие проверки. Отсюда 16 падений в файле
про робота креативов и 13 в файлах про ночной слепок.
Тестам запрещён выход в интернет - Http::preventStrayRequests в TestCase.
До этого прогон физически ходил в живой кабинет Яндекс Директа, а поломка
маскировалась под "Invalid OAuth token", хотя ключ в тесте подставной.
Сторож принят вырезанием: со снятой правкой та же поломка называет себя
честно, с адресом запроса.
Он же вскрыл, что ProjectRuleNotificationTest слал живой запрос на удаление
проекта в кабинет поставщика crm.bp-gr.ru, оставаясь зелёным - ответ кабинета
тест не проверяет. Поставлена заглушка.
ExternalServiceDownAlertTest избавлен от зависимости от сети - закрыт хвост,
тянувшийся с 30.07: падал в общем прогоне, проходил в одиночку.
InAppNotificationTest брал первую попавшуюся запись во всей таблице вместо
своей. SalesOverviewTest попадал в топ-4 клиентов по удаче: при запросе
всего отдела отбор не ограничен ничем, все тенанты базы с нулём лидов равны.
Клиенту даны настоящие лиды - место в четвёрке заслуженное.
Проверено: полный прогон 4063/4059 зелёный, статанализ 0, код-стиль моих
файлов чисто. На бой ничего не выкачено.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Прежний промт помечен устаревшим - в нём закрыт хвост Х1, изменилось состояние
ветки и у хвоста про тестовую базу нашёлся более грубый корень. Оставлен ради
разбора, как разруливать столкновение номеров журнала схемы.
В новом промте пять хвостов, и один из них новый и срочный: в полном прогоне
одиннадцать падений подряд с ответом живого Яндекс Директа "Invalid OAuth token".
Похоже, протух ключ доступа, а на бою в Директе запущена живая кампания с
замороженными деньгами. Порядок разбора - сначала посмотреть, а не чинить, и
доложить владельцу до любых действий с токеном.
По тестовой базе записан готовый прибор: сравнить число записей о применённых
миграциях с числом файлов миграций. Больше файлов - в базу лезет чужая рабочая
папка, меньше - сборка оборвалась. Замер в промте: на общей базе телеграм давал
42 из 244, на своей 244 из 244 два раза подряд.
Хвост про хрупкий тест внешних сервисов, возможно, отпал сам - в последнем
прогоне на своей базе он не упал. В промте написано, как это проверить и как
закрыть запись честно.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Прежний промт помечен устаревшим - в нём закрыт хвост Х1, изменилось состояние
ветки и у хвоста про тестовую базу нашёлся более грубый корень. Оставлен ради
разбора, как разруливать столкновение номеров журнала схемы.
В новом промте пять хвостов, и один из них новый и срочный: в полном прогоне
одиннадцать падений подряд с ответом живого Яндекс Директа "Invalid OAuth token".
Похоже, протух ключ доступа, а на бою в Директе запущена живая кампания с
замороженными деньгами. Порядок разбора - сначала посмотреть, а не чинить, и
доложить владельцу до любых действий с токеном.
По тестовой базе записан готовый прибор: сравнить число записей о применённых
миграциях с числом файлов миграций. Больше файлов - в базу лезет чужая рабочая
папка, меньше - сборка оборвалась. Замер в промте: на общей базе телеграм давал
42 из 244, на своей 244 из 244 два раза подряд.
Хвост про хрупкий тест внешних сервисов, возможно, отпал сам - в последнем
прогоне на своей базе он не упал. В промте написано, как это проверить и как
закрыть запись честно.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Основная ветка ушла вперёд на 23 коммита - приехала чужая работа по воронке
продаж, вебхуку поставщика и сверке CSV. Конфликтов было два.
1. Журнал схемы db/CHANGELOG_schema.md. В обеих ветках лежала запись v9.28
от 31.07 про разное: у нас очередь заданий роботу, у них стадии воронки
"Тестирование ручное" и "Выслано КП". Обе записи настоящие, поэтому ни одна
не выброшена: боевая v9.28 осталась на своём номере, наши две подвинуты на
v9.29 очередь заданий и v9.30 грант админ-роли. Поправлены ссылки на номер
в шапках двух миграций, перекрёстные ссылки внутри самих записей и врезка
"Перенумерация" вверху журнала - там теперь описан и этот случай.
2. docs/observer/STATUS.md - файл авто-генерируемый, взята версия основной
ветки, хук перепишет его сам.
Заголовок db/schema.sql не трогали: там своя нумерация v8.85, ни одна из
веток её не двигала.
Плюс одна настоящая находка статанализа, приехавшая с чужой работой: у метода
SupplierPortalClient::fetchDeliveredLeads в описании возвращаемого набора не
было поля tag, хотя код его уже возвращает и чужой же тест его ждёт. Дописал
одно поле в описание, логику не трогал. Список исключений статанализа пересобран
- разъехались счётчики ложного класса Pest от новых строк в чужих тестах.
Проверено на ОТДЕЛЬНОЙ тестовой базе liderra_testing_tgmerge:
телеграм на портале 244 из 244
робот 124 из 124
статанализ 0 замечаний
код-стиль чисто
полный прогон 4063 теста, 4018 прошло, 17 упало
До сведения было 4039 тестов и 20 падений - тестов стало больше, падений
меньше. Ни одно падение не касается телеграма или журнала схемы: 13 из 17 в
рекламном модуле Яндекса, из них 11 - живой отказ авторизации Яндекс Директа,
остальные счётные, от накопленных за прогон данных.
Отдельно вскрылось при проверке: общая тестовая база liderra_testing испорчена -
в ней 180 записей о применённых миграциях при 146 файлах, то есть в неё пишет
не только эта рабочая папка. Из-за этого сборка базы срывалась на первом же
шаге. Отдельная база всё вылечила. Это хвост Х2б, лечение в репозиторий не
вносил - решение владельца.
На бой ничего не выкачено, переключатель TG_ROBOT_TRANSPORT остаётся в process.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Работа по роботу закончена и запушена, тестовая база вылечена наполовину,
на бой ничего не выкачено. В промте пять хвостов по убыванию важности.
Главный и срочный: в обеих ветках лежит запись v9.28 журнала схемы от 31.07
про разное — у нас таблица заданий роботу, в основной стадии воронки продаж.
При сведении веток это конфликт, и обе записи должны остаться разными
номерами, а не выбором одной стороны. Ветка отстала на 23 коммита.
Остальные хвосты: изоляция между тестами и одна тестовая база на все рабочие
папки, покалеченные сторожа в главной папке репозитория, хрупкий тест
внешних сервисов, русский токен в старом тесте.
Выкат вынесен отдельно с пометкой, что он только по явному слову владельца:
упирается не в код, а в решение, где поселить робота.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Корень оказался в Laravel: в конце каждого теста он проверяет, осталось ли
соединение в своей черновой транзакции, и если тест её закрыл сам - сбрасывает
признак "база собрана". Следующий тест пересобирает базу целиком прямо посреди
прогона, снося её под всеми остальными. В проекте полно кода со своими
транзакциями, поэтому срабатывало примерно раз на восемь прогонов.
Что сделано:
- пересборка базы теперь один раз и в самом начале прогона, а не лениво в
середине по первому файлу, который её закажет;
- признак "собрано" держится взведённым - пересборка посреди прогона стала
невозможна;
- отказ заливки схемы стал громким: раньше PDO мог вернуть отказ без ошибки,
и прогон ехал дальше по неполной схеме;
- добавлена сверка полноты сборки: сколько шагов записано против того,
сколько их лежит.
Приёмка вырезанием: новый тест-сторож зелёный с защитой и падает без неё,
прогон без защиты дольше на 12 секунд - это и есть лишняя пересборка.
Замер до и после, полный набор портала:
без правки 3175 прошло, 837 упало
с правкой, прогон 1 3971 прошло, 19 упало
с правкой, прогон 2 3971 прошло, 19 упало, списки совпали дословно
Оставшиеся 19 - давние и не от базы: 11 падают на неверном токене Яндекса,
одна на типе исключения, семь счётных от накопления данных между тестами.
Плюс план перевода чтения вердикта и пересдачи на опрос - отдельным файлом,
код по нему ещё не писался.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Конфликты только в документах: журнал «мозга» взят из main, запись v9.28
переставлена поверх боевого журнала схемы. Код не пересекался.
Сторож слоёв проверен отдельно в рабочей папке: 0 нарушений.
Сторож секретов gitleaks отработал по-настоящему.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
В спеке §3 уточнено по факту кода: нормализатор отдаёт номер с плюсом,
а хранится он без плюса — плюс срезаем.
Сторожа орфографии и разметки пропущены: они не находят проблемы, а падают —
их библиотеки в корне обрезаны той же поломкой, что статанализ и сторож
журналов. Сторож секретов gitleaks — живой, работает.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
До правки decide на незнакомой стадии возвращал unknown_stage — реклама на
такие фирмы встала бы без единого сообщения. Теперь обе новые стадии идут
по правилу переговоров: крутим до созвона, потом ещё срок просрочки.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Сторож проверен вырезанием: с убранной защитой тест краснеет, с возвращённой
зеленеет. Зелёный тест, которого не видели красным, ничего не охраняет.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Канал из пяти: почта, ватсап, телеграм, макс, другое. Адрес/ник сохраняем
как ввёл менеджер — телеграм-ник и почта к телефонному виду не приводятся.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Телефон приводится к виду 79… без плюса — как контакты карточки и как
принимают Яндекс Аудитории. Неразобранный номер отказывает, карточку не двигает.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Список стадий правится в двух местах сразу: PROSPECT_STAGES на фронте и
STAGES в контроллере. Расхождение дало бы пустую колонку.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Стадии manual_testing и kp_sent в CHECK + 4 nullable-колонки под их поля.
Откат и повторный накат проверены на своей тестовой базе измерением колонок.
Статанализ пропущен с разрешения владельца: 287 замечаний было и до правки,
все — известный ложный класс Pest, моих файлов среди них нет.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Документ ПРОТОКОЛ-создание-вебмастера.md (коммит e0223de4) ссылался на
схему ПРОТОКОЛ-вебмастер-КАРТА.html, а сама схема в репозиторий не попала —
лежала на диске незакоммиченной с 22.06.2026. Из-за этого сторож ссылок
краснел на чистом main и блокировал любой пуш: 2 битые ссылки из 2242.
Содержимое схемы не менялось, кроме косметики по правилу проекта:
белый цвет записан полностью (#ffffff вместо #fff), 8 мест, вид тот же.
У объявления два независимых признака: вердикт модерации (Status) и идёт ли
показ (State). Созданное программой объявление рождается выключенным, и
включение кампании его не поднимает — команды поднять сами объявления в коде
не было вообще.
Живая приёмка 31.07.2026, кампания 713175197: принята и включена, Яндекс на
уровне кампании пишет «Идут показы», а внутри все 15 объявлений выключены.
Показов ноль при замороженных у клиента 3333.36 руб.
Опросчик теперь чинит по СОСТОЯНИЮ из ответа Яндекса, а не по переходу
статуса: собирает объявления со статусом «принято» и выключенным показом и
зовёт ads.resume. Так самовосстанавливается и уже запущенная кампания — по
переходу она осталась бы выключенной навсегда, потому что «принято» у нас
записано давно. Состояние Яндекс присылал в каждом ответе и раньше, опросчик
его просто выбрасывал.
Проверено вырезанием: без починки новый тест краснеет. Реклама 349 из 349.
Живой запуск кампании на бою 30.07.2026 вскрыл две поломки, которых вчерашняя
починка не видела. Обе — из класса молчаливых: портал рапортует успех, а в жизни
ничего не происходит.
ПЕРВАЯ. Минимум Директа НЕ постоянный.
Вчера мы приняли живой отказ «Budget for this period cannot be less than 600 rub.»
за постоянный порог и зашили 600 ₽ в настройку. Сегодня та же кампания на неделю
получила отказ «cannot be less than 2400 rub.» — проверка её пропустила, и клиент
снова увидел английский текст.
Две точки дали правило: минимум = 300 ₽ за каждый календарный день периода,
считая оба края.
период 30.07-31.07, 2 дня → 600 ₽ = 2 × 300
период 30.07-06.08, 8 дней → 2400 ₽ = 8 × 300
Теперь порог считается от периода показа, а ставка за день вынесена в настройку
YANDEX_DIRECT_MIN_SPEND_RUB_PER_DAY. Период вычисляется ДО денег — иначе считать
минимум не от чего.
ВТОРАЯ. Создать объявления — не значит запустить рекламу.
Запуск проходил успешно, портал ставил статус «на модерации», а в кабинете лежали
кампания, группа и 15 объявлений в состоянии DRAFT и OFF. Яндекс кладёт всё
созданное черновиком и проверку сам не начинает. Реклама не показалась бы никогда,
и узнать об этом можно было только глазами в кабинете: наш журнал говорил, что всё
хорошо.
Добавлены два вызова, которых не было вовсе:
ads.moderate — отдать созданные объявления на проверку
campaigns.resume — включить показ
Порядок обязателен. Пока кампания черновик, включить её нельзя — Директ отвечает
«кампания является черновиком и не может быть остановлена». Сначала модерация,
она переводит кампанию в MODERATION, и только потом включение.
Отбор объявлений строго по Ids. На CampaignIds Директ отвечает «отсутствует
обязательный параметр Ids» — именно так отправка молча не сработала бы.
На модерацию уходят объявления, созданные ИМЕННО этим заходом: повторная отправка
уже проверяемого — отказ по позиции, он порвал бы возобновляемый запуск. Включение
показа безобидно при любом повторе: на включённой кампании Яндекс отвечает
предупреждением, а не отказом.
Заодно клиент Директа перестал молчать про отказы внутри ответа: раньше он смотрел
только AddResults, UpdateResults и DeleteResults, теперь ещё ModerateResults и
ResumeResults. Отказ по позиции в этих двух проходил бы насквозь незамеченным.
ПРОВЕРЕНО ЖИВЬЁМ. Кампания № 6 на бою: в кабинете 713175197, группа 5778555449,
15 объявлений, аудитория подключена, статус MODERATION, показ включён. На счету
Яндекса 8775 ₽. У клиента заморожено 3333.36 ₽ за 27778 показов до 06.08.
Тесты: 346 зелёных по рекламе, 43 по клиенту Директа, статанализ ноль замечаний.
Четыре новых теста — минимум по длине периода на боевом случае, отправка на
модерацию, порядок модерация-до-включения, отсутствие повторной отправки.
Два старых теста закрепляли частный случай в два дня — им проставлен явный срок
показа, иначе они молча описывали бы неправду.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Переделка сделана и запушена, на бой не выкачено. Развилка описана: поселить
робота и выкатить, перевести на опрос чтение вердикта и пересдачу, вылечить
тестовую базу. Ловушки смены выписаны, включая молчаливую установку
зависимостей и правило замерять сторожа до починки своего текста.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Переделка сделана и запушена, на бой не выкачено. Развилка описана: поселить
робота и выкатить, перевести на опрос чтение вердикта и пересдачу, вылечить
тестовую базу. Ловушки смены выписаны, включая молчаливую установку
зависимостей и правило замерять сторожа до починки своего текста.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Продолжение разбора 31.07.2026. У пяти сегодняшних добранных сделок (223, 225-228)
пусты регион и город. Проверил три источника: ДаData по этим номерам молчит
(qc=0, виртуальные операторы МиАТел/МТТ/Скартел/ВымпелКом), Россвязь не совпала,
тег поставщика у них — «РФ», то есть вся страна. Восстановить их регион НЕЧЕМ,
задним числом не чиню и не выдумываю.
Но причина на будущее устранима: в журнале отданного ЕСТЬ колонка «Тег», и там
бывает настоящий регион (у одного из семи сегодняшних — «Свердловская область»).
Мы её просто не читали, поэтому у добранного лида в карточке было только
vid+phone+project, и когда ДаData молчит, резолверу нечем подстраховаться —
RegionTagResolver работает именно по тегу.
- parseDeliveredRows: тег берётся из ячейки сразу после td.crm-domain-column.
Разметка списана с ЖИВОГО кабинета, не придумана. Прочерк/пусто → tag=null,
поведение как раньше (парсер этого журнала уже дважды ломал прод — 09.07 и 16.07,
поэтому только добавление поля, ни одна существующая ветка не тронута).
- CsvReconcileJob: тег кладётся в raw_payload добранного лида.
Тесты (20/20 в файле, 233/233 поставщик+вебхук): разбор живой разметки, «РФ» и
прочерк, тег доезжает в карточку и — главное — с тегом резолвер даёт регион
(source=tag), а стоит тег вырезать, тот же лид снова «регион неизвестен».
Larastan 0, Pint чисто.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Сама установка НЕ выполнена — она трогает живую машину и боевой портал,
нужно явное разрешение владельца. Куда селить робота, зависит от исхода
разговора с поставщиком прокси.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Своей петли нет: запускается таймером, зависший проход не мешает следующему.
Номера кладутся во временный файл и убираются при любом исходе.
Проверено живьём против поднятого портала: верный токен — «Работы нет.» и код 0,
чужой токен — понятная ошибка 401.
Заодно закрыта мина: токен уходит в заголовок HTTP, куда пускают только латиницу.
С русскими буквами робот падал нечитаемым сбоем Node ещё до обращения к порталу —
теперь говорит по-человечески. Наступил на неё сам, проверяя план дословно.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
При transport=poll портал кладёт задание и выходит, робот заберёт его сам.
Процессный путь оставлен рабочим и остаётся умолчанием.
Полный набор тестов телеграма: 219 из 219 зелёные.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Применение итога вынуто из джоба в сервис без изменения логики — теперь его зовут
оба пути, процессный и опросный. Отчёт принимается только по заданию в работе.
Сторож принят вырезанием: без проверки статуса повторный отчёт проходит с 200.
Отдельно закрыта мина, найденная ревью защиты и отсутствовавшая в плане: роль
crm_admin_user имела на client_tg_campaigns только чтение, а канал робота пишет
туда итог — на бою приём отчёта упал бы по правам. Миграция v9.29 даёт UPDATE.
Полный набор тестов телеграма: 217 из 217 зелёные, старые в том числе.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Номера не кладём в задание и не пишем в журнал — отдельный запрос, без кеша.
Сторож принят вырезанием: без проверки статуса тест отдаёт 200 вместо 404.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Выдача строго по одному: у робота один профиль браузера и одна сессия кабинета.
Задание, по которому робот не отчитался за срок аренды, возвращается в очередь.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Где работать, что уже стоит, красные линии, ловушки и приёмка вырезанием.
Отдельно записано, что проверка обращения к поставщику прокси умирает
вместе с сессией и её надо ставить заново.
Co-Authored-By: Claude Opus 5 (1M context) <noreply\@anthropic.com>
Нужна при ЛЮБОМ исходе с адресом: и на сервере, и на машине владельца робот стоит
не там, где очередь портала, а сегодня портал запускает его как процесс у себя.
Списано с работающего образца - робота креативов Яндекса.
12 задач по шагам с готовым кодом и тестами, переключатель process/poll оставляет
старый путь рабочим. Отдельно вынесены красные линии: перезапуск srv_bypass после
миграции, номера телефонов не в задании, приёмка сторожей вырезанием.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Было зашито channel msedge. На сервере Edge не ставится, робот там не запускался вообще.
Теперь выбор в config: на Windows msedge, иначе chrome, перебивается MTS_BROWSER_CHANNEL.
Три новых теста на выбор по умолчанию и на ручную перебивку.
Заодно в промт смены записаны живые замеры 30-31.07:
- рендер-сервер 51.250.1.97 настоящим Chrome получает от МТС отказ по адресу, 4 прогона;
та же проба с рабочей машины 77.74.123.226 даёт форму входа. Прежний вывод «тупик снят»
был сделан по curl, а curl получает от защиты МТС заглушку и врёт про доступ.
- прокси Proxy.Market сам отвечает 403 на стадии CONNECT для mts.ru, beeline.ru,
megafon.ru, tele2.ru, при этом yandex.ru, vk.com, avito.ru, rt.ru пропускает.
Значит режет поставщик прокси, а не сайт. Замер отправлен им, обращение 31074.
- связка портал-робот запускает робота как процесс на своей же машине, поэтому при любом
решении по адресу её придётся переделать на опрос портала роботом.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Живой запуск кампании на бою 30.07.2026 вскрыл две поломки, которых вчерашняя
починка не видела. Обе — из класса молчаливых: портал рапортует успех, а в жизни
ничего не происходит.
ПЕРВАЯ. Минимум Директа НЕ постоянный.
Вчера мы приняли живой отказ «Budget for this period cannot be less than 600 rub.»
за постоянный порог и зашили 600 ₽ в настройку. Сегодня та же кампания на неделю
получила отказ «cannot be less than 2400 rub.» — проверка её пропустила, и клиент
снова увидел английский текст.
Две точки дали правило: минимум = 300 ₽ за каждый календарный день периода,
считая оба края.
период 30.07-31.07, 2 дня → 600 ₽ = 2 × 300
период 30.07-06.08, 8 дней → 2400 ₽ = 8 × 300
Теперь порог считается от периода показа, а ставка за день вынесена в настройку
YANDEX_DIRECT_MIN_SPEND_RUB_PER_DAY. Период вычисляется ДО денег — иначе считать
минимум не от чего.
ВТОРАЯ. Создать объявления — не значит запустить рекламу.
Запуск проходил успешно, портал ставил статус «на модерации», а в кабинете лежали
кампания, группа и 15 объявлений в состоянии DRAFT и OFF. Яндекс кладёт всё
созданное черновиком и проверку сам не начинает. Реклама не показалась бы никогда,
и узнать об этом можно было только глазами в кабинете: наш журнал говорил, что всё
хорошо.
Добавлены два вызова, которых не было вовсе:
ads.moderate — отдать созданные объявления на проверку
campaigns.resume — включить показ
Порядок обязателен. Пока кампания черновик, включить её нельзя — Директ отвечает
«кампания является черновиком и не может быть остановлена». Сначала модерация,
она переводит кампанию в MODERATION, и только потом включение.
Отбор объявлений строго по Ids. На CampaignIds Директ отвечает «отсутствует
обязательный параметр Ids» — именно так отправка молча не сработала бы.
На модерацию уходят объявления, созданные ИМЕННО этим заходом: повторная отправка
уже проверяемого — отказ по позиции, он порвал бы возобновляемый запуск. Включение
показа безобидно при любом повторе: на включённой кампании Яндекс отвечает
предупреждением, а не отказом.
Заодно клиент Директа перестал молчать про отказы внутри ответа: раньше он смотрел
только AddResults, UpdateResults и DeleteResults, теперь ещё ModerateResults и
ResumeResults. Отказ по позиции в этих двух проходил бы насквозь незамеченным.
ПРОВЕРЕНО ЖИВЬЁМ. Кампания № 6 на бою: в кабинете 713175197, группа 5778555449,
15 объявлений, аудитория подключена, статус MODERATION, показ включён. На счету
Яндекса 8775 ₽. У клиента заморожено 3333.36 ₽ за 27778 показов до 06.08.
Тесты: 346 зелёных по рекламе, 43 по клиенту Директа, статанализ ноль замечаний.
Четыре новых теста — минимум по длине периода на боевом случае, отправка на
модерацию, порядок модерация-до-включения, отсутствие повторной отправки.
Два старых теста закрепляли частный случай в два дня — им проставлен явный срок
показа, иначе они молча описывали бы неправду.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Живой бой 30.07.2026. Сегмент дозрел за 15 часов — Яндекс опознал 1644 человека
из 1693, охват 3379. Портал нажал «Запустить» и получил отказ Директа:
Budget for this period cannot be less than 600 rub.
Стена оказалась не одна, а две. Обе — молчаливые: портал о них не знал.
Первая — аудитория. Сегмент в Аудиториях существует и при этом НЕГОДЕН, пока
Яндекс сводит загруженные номера с людьми. Портал этого не спрашивал вообще:
ни один файл не читал can_create_dependent. Он просто шёл в Директ строить
условие ретаргетинга и получал «объект не найден» — тот же отказ, что и на
несуществующий сегмент. Судить о готовности можно ТОЛЬКО по can_create_dependent:
статус is_processed означает «обрабатывается», а не «готов».
Вторая — деньги. Директ не берёт кампанию, у которой бюджет за период ниже
шестисот рублей. Приёмочная кампания на 1693 показа давала около 146 рублей.
Проверки минимума в портале не было тоже.
В обоих случаях клиент видел сырую ошибку Яндекса на английском и не понимал,
виноват ли он и что делать дальше.
Что сделано. Обе проверки выполняются ДО первого обращения к Яндексу: в кабинете
ничего не создаётся, деньги не морозятся, кампания остаётся черновиком.
аудитория ещё готовится → 202 и «подождите, обычно несколько часов»
смета мала → 422 и «нужно 6945 показов, это 833.40 рублей»
Наружу уходят ТОЛЬКО клиентские числа. Ни минимума площадки, ни нашей наценки:
по паре «минимум площадки — цена клиенту» долю Яндекса можно вычислить делением.
Минимум вынесен в настройку YANDEX_DIRECT_MIN_SPEND_RUB, Яндекс может его менять.
Тестом вперёд, в живой Яндекс из тестов не ходили. Четыре новых теста на запуск
и два на ответы портала. Реклама целиком: 342 теста зелёные.
Заодно приведён в порядок список исключений статанализа. Он был красным ЗАДОЛГО
до этой правки и не пускал ни одну правку кода: 629 замечаний до моих изменений,
638 после. Проверено в обеих папках работы — не артефакт подпапки.
Разбор 637 замечаний по составу:
611 — статанализ не понимает устройство тестов Pest и ругается на
обращения вида this->postJson и this->tenant. Таких записей в списке
исключений уже было 671 — просто новые тестовые файлы туда не дописали.
26 — придирки к типам, из них 7 в боевом коде.
Все семь в боевом коде разобраны поимённо и оказались ложной тревогой. Доказано
не рассуждением, а зелёными тестами, которые упали бы при настоящей поломке:
shows_until, три замечания — три теста прямо проверяют закрытие кампании по
истечении срока показа и разморозку остатка. Будь там строка вместо даты,
первый тест упал бы, а деньги клиента остались бы заперты навсегда.
snapshot_from и snapshot_to, два замечания — тесты ручного режима строят
аудиторию по этим датам, на строке они бы рухнули.
SetTenantContext строка 56 и CampaignMessageService строка 168 — лишние
подстраховки, вреда нет.
Корень ложных срабатываний: Larastan ищет приведения типов в старом виде —
свойством casts, а у нас метод casts. Код правильный, инструмент отстал.
После обновления списка статанализ зелёный: 0 замечаний. Теперь сторож снова
ловит НОВОЕ, а не молчит красным.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Проверка ссылок падала на девяноста двух ошибках, поэтому любая отправка в main
шла через обход. Смысл починки не в красоте: пока ошибок девяносто две, новая
битая ссылка тонет среди них и никто её не замечает. Теперь ноль, и пуш проходит
обычным порядком.
Что было внутри девяноста двух:
68 — ссылка написана от корня хранилища, а документ лежит на две-три папки
вглубь. Приписаны шаги вверх. Файлы всё это время лежали на месте.
8 — файл существует, но переехал: Jobs/Billing, Jobs/Supplier, Services.
Адреса переписаны на нынешние места.
8 — файла нет и не будет: ProcessWebhookJob снят при уходе от старого
биллинга, SupplierCsvParser удалён 09.07 как мёртвый, каталог memory
уехал в claude-brain, а HANDOFF прогрева от 25.07 не существует ни
в одном коммите. Ссылки сняты, текст оставлен.
3 — неверное число шагов вверх: две точки вместо трёх.
3 — это вообще не ссылки, а примеры в тексте: http:// как образец мусорного
ввода, https://ваш-сайт.ru из подсказки формы. Обёрнуты в кавычки-код.
1 — адрес с приписанными номерами строк, которых проверятель не понимает.
1 — Россвязь. Сайт мёртв по-настоящему: сервер 194.226.91.2 отдаёт nginx-ову
404 на любой адрес, включая корень, а сертификат выписан не на это имя;
ведомство расформировано. Замену проверить не удалось — преемник
с этой машины не отвечает вовсе. Ссылка снята, адрес читается текстом.
Вопрос «где реестр нумерации живёт теперь» остаётся открытым.
Исключения проверятеля не тронуты ни на строку. Ноль получен починкой, а не
глушением сторожа — иначе вся работа теряет смысл.
Отдельным прибором проверено, что видимый читателю текст не изменился ни в одной
из 81 правленой строки: правились только адреса ссылок. Разметка линтером чистая.
Вместе с починкой ложится промт смены, по которому работа делалась.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Настоящая причина, по которой портал не мог завести кампанию. Прежний диагноз «сегменту
нужно не меньше 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>
Слияние feat/client-telegram-ads с main 8bdd58e8. Десять швов разобраны вручную:
денежный файл AdWalletService взят из main целиком — проверено поимённо, что все три
починки на месте: свой контекст клиента, оживление брони, таяние заморозки. В расписании
объединены оба набора заданий: телеграмные два и рекламные четыре. В боковом меню и в
мобильном «Ещё» сохранён пункт «Рекламный кошелёк», подписи поправлены — на реальные
экраны ведут ОБА канала. Словарь, пример настроек и журнал схемы объединены.
Сверх самого слияния:
- Журнал схемы: телеграмные записи v8.86-v8.95 перенумерованы в v9.18-v9.27, блок
переставлен наверх, пометки «номер предварительный» сняты и заменены одной врезкой
о перенумерации. Задвоенных номеров не осталось. Врезка шапки теперь называет и
телеграмные таблицы: их DDL, как и рекламный, живёт только в дельта-миграциях.
- Новый сторож денег tests/Feature/ClientTg/TgMoneyUnderRealRoleTest.php: списание и
возврат под боевой ролью crm_app_user. С контекстом клиента деньги двигаются, без
контекста возврат падает громко. Обычные тесты ходят суперюзером и этот класс дыр
увидеть не могут.
- Помощник rejectedCampaign переименован в tgRejectedCampaign: одноимённый помощник
есть у рекламного модуля, помощники Pest глобальные, полный прогон падал фаталом.
Каждая ветка по отдельности этого увидеть не могла.
- Два теста уведомлений считали ВСЮ таблицу целиком вместо строк своего пользователя:
в одиночку зелёные, в полном прогоне красные. Счёт сужен до конкретного пользователя.
- Убраны две проверки отменённой сущности «своё имя отправителя» — сама сущность
дропнута в v9.27 как СМС-фантазия, её адрес отдаёт 405.
Прогоны: телеграм 193/193, реклама 336/336 при 1029 проверках, вместе 532/532,
экраны 1704/1708, сборка фронта чисто, полный Unit+Feature 3923/3960. Шестнадцать
падений полного прогона совпадают построчно с прогоном ветки без телеграма — слияние
не добавило ни одного. Статанализ в свежем каталоге запустить не удалось: он требует
сгенерированного файла-подсказки, которого нет в репозитории, и без него молча падает
на обеих ветках.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Записан разбор захода 7 часть 2: где была дыра, чем кончилась бы на бою, замер под
боевой ролью и почему тесты её увидеть не могут.
Отдельно записана мина: форма политики РАЗНАЯ у разных таблиц. У рекламных строгая,
без контекста ноль строк. У пользователей мягкая, без контекста открыта. Поэтому
чтение получателей уведомления уцелело - но уцелело случайно.
Числа обновлены: 395 из 395 при 1251 проверке.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Числа обновлены: портал 393 из 393 при 1249 проверках, было 391.
Добавлен разбор захода 7: поломка денег на шве пауза-возобновление, датчик класса
"каждая половинка честна, шов между ними голый", и карта швов с веткой телеграма
для той смены, которая будет её забирать.
Порядок выбран владельцем: сначала выкат показов, телеграм следом.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Разведку впервые прогнали живьём, с разрешения владельца: вызвали саму функцию робота
против боевого кабинета на двух отклонённых объявлениях кампании-пустышки. Живую кампанию
не трогали, робот только читал. Прогон окупился сразу — вскрылись две поломки, которых
не видел ни один из 74 зелёных тестов.
Первая. Робот не нашёл объявление, которое в кабинете было: провал за три секунды,
«объявления в списке нет». Замерили — список рисуется две секунды, а робот считал ячейки
сразу после человекоподобной паузы в 800 миллисекунд. Счёт не ждёт, он отвечает про
«прямо сейчас». На бою это значило бы, что КАЖДЫЙ отказ уезжает в «ждёт разбора»,
а клиент причину не узнаёт никогда.
Вторая. После первой починки робот стал находить объявление и приносить 29 знаков —
один заголовок «Модератор отклонил объявление», без причины. Та же ошибка: строка причины
появляется позже окна, робот считал её и получал ноль, раскрывать было нечего. Клиенту
уехало бы сообщение от Яндекса, в котором нет ни слова о том, что чинить.
Текст окна нарастает по частям: 29 знаков, потом 82, потом 785. Окно не отдаёт ошибку —
честно показывает то, что успело нарисоваться. Поэтому промах молчаливый: робот считал бы,
что справился.
Починка одна на обе: ждать, а не считать. Раскрытие строки подтверждаем появлением
подробности, а не паузой — пауза это надежда, элемент это факт. Не дождались подробности,
остаёмся с короткой причиной: она честная и клиенту полезна, промолчать было бы хуже.
Оба сторожа написаны ДО починки и падали с теми самыми живыми ошибками.
Хвост доклада: по решению владельца срезаются подписи кнопок кабинета «Написать в чат»
и «Написать письмо» — у клиента этих кнопок нет, а выглядят они приглашением написать
Яндексу. Режем только хвост и только точное совпадение строки: те же слова внутри
пояснения это слова Яндекса, их не трогаем.
Живая проверка обрезки вскрыла третью ловушку: «Написать письмо» срезалось, а «Написать
в чат» оставалось. Яндекс ставит между короткими словами неразрывный пробел. Глазу он
неотличим от обычного, а сравнению это совсем другой символ. Правило для этого кабинета:
сравнивать текст только по человеческому виду строки, а элементы ждать, а не считать.
Замеры записаны в разметку кабинета, раздел 7.8.
Итог живой проверки с продовой паузой: причины приезжают целиком, кнопок в них нет —
760 знаков по финансовым услугам и 589 по медицине, шесть-семь секунд на объявление.
Снимок берётся только с окна, логин и остаток счёта в кадр не попадают.
Робот 80 из 80. Портал не тронут. На боевой не выкатывалось, рубильник Директа выключен.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>