73bd36b6ce
Этап 3 «Жизненный цикл и модерация», задача 3.1. Плюс закрыта задача 3.0 (живая разведка экрана отказа) — вердикт МТС по кампании «займ» (2231134) пришёл: «Отклонена». Разведка read-only, деньги не тронуты. Задача 3.1 — колонка mts_campaign_id + РАННЕЕ и надёжное сохранение: - Миграция client_tg_campaigns.mts_campaign_id (varchar(32) NULL, после status_reason) + запись CHANGELOG_schema v8.89 (предварит., ветка). RLS не меняется; rls-reviewer не требуется (nullable-колонка данных). - Робот (Node): чистый хелпер parseCampaignId(url) в cabinet.js (покрыт тестом); runner.js захватывает id СРАЗУ после создания черновика (шаг аудитории) и печатает маркер MTS_CAMPAIGN_ID=<id> в stderr; id теперь идёт и в ветке ОТКАЗА (раньше терялся). - Обёртка (PHP): TelegramRobotRunner восстанавливает id из stderr-маркера во всех путях (таймаут/непарсабельный вывод/JSON без id); RobotResult::failed принимает id. - Джоб: finalize сохраняет mts_campaign_id при ЛЮБОМ исходе (успех/отказ), не затирая ранее сохранённый id. Метод failed() не трогали — туда результат не доходит (осознанный residual, закроют уборщик 3.1b и sweeper 3.3). Разведка отказа (3.0) записана в bots/mts-telegram-ads/FLOW-FINDINGS.md: причина показана текстом в слайд-модалке «Причины отклонения кампании» (кнопка «Причины»); поля загрузки файла на экране отказа нет — документ грузится через «Исправить» → шаг «Сообщение» → «Комментарий для модератора»; кнопка пересдачи — «Исправить». TDD, робот замокан, тесты на liderra_testing (номера 7999…). Приёмка: Node 48/48 (npm test), Pest ExternalIdTest 2/2 + регрессия ClientTg 122/122, phpstan (4 боевых файла) 0, deptrac 0 нарушений, pint чисто. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>