814c45e8b35d4c39df08dc40926c895bbef78310
1090 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
814c45e8b3 |
docs(приёмка Яндекса): промт смене 22 — возврат по сроку и что делать дальше
Смена 21 закрыла приёмку Яндекса почти целиком: из 29 карточек пройдены 27. Остались две, и обе ждут не работы, а события — живого отказа всех объявлений разом и платных комбинаций Я1-Я9. В промте: сегодняшний ночной замер возврата заморозки по сроку у #13 (последний бесплатный, ловится один раз), три находки смены и все четыре записи, живое состояние кошелька и кампаний, пять решений, которых ждём от владельца, и разбор того, как работать рядом со второй сменой в одной ветке. Пять уроков, из которых главный второй: сторож, который НЕ покраснел при вырезании, — это сигнал искать вторую дорогу, а не повод успокоиться. Именно так нашлась двойная подписка на остановку рекламы. |
||
|
|
652b708203 |
fix(реклама): остановка рекламы делалась дважды; добраны все бесплатные карточки приёмки
Из 29 карточек приёмки Яндекса закрыты 27. Остались две, и обе ждут не работы, а события: живого отказа всех объявлений разом и платных комбинаций Я1-Я9. Нашлась вторая за день тихая беда. Слушатель остановки рекламы был подписан на событие ДВАЖДЫ: Laravel 11 сам находит слушателей в app/Listeners по типу параметра handle(), а в провайдере он вдобавок прописан руками. Остановка не бесплатная: по каждой кампании клиента она идёт в Яндекс за числом показов, платит за показанное и глушит кампанию, - и всё это делалось по два раза. Деньги уцелели по случайности: списание идемпотентно по ключу показов, снятие заморозки тоже, а второй заход не находил кампаний, первый уже увёл их из работы. Ручная подписка убрана. Найдено не глазами, а вырезанием: новый сквозной сторож Я-Б3 не покраснел, когда подписку отключили. Если бы регистрация была одна, он обязан был. Замерено на живой кампании #13, без единого рубля сверху: - Я-Д3: показано 21, списано 10,50 руб = 21 x 500/1000; по смете было бы 850; - Я-М2: кампания running, заморозка 839,50 = 850 - 10,50, показы идут; - Я-Х4: задача из очереди под боевой ролью без контекста клиента видит 1 кампанию служебным путём и 0 обычным - тот самый молчаливый ноль, ради которого служебный путь и заведён. Новые сторожа, каждый принят красным: - Я-Б2: денег ровно на смету - запуск проходит, вторая кампания уже нет; - Я-Б3: деньги кончились - кампания РЕАЛЬНО встала и владельцу ушло письмо; - Я-М4: пока вердикта нет, не двигаются ни заморозка, ни списание; - Я-М5: после «Исправить» и повторного запуска заморозка ОДНА; - Я-Х2: десять подделок отбиты, чужой номер клиента ничего не меняет; - Я-Д1: заморозка равна смете копейка в копейку, включая некруглую; - Я-Д2: сплошной обход всех семи читающих путей - наценка не видна нигде (прежний сторож проверял один путь из семи); - остановка рекламы подписана ровно один раз. Прогоны: 424 теста рекламы зелёные, статанализ 0, формат чистый. Журнал результатов приёмки дописан разделом на 05.08 - что закрыто, чем закрыто и чего ждём. |
||
|
|
d8cfac6c32 |
docs(обзвон): промт стройки готов к запуску — чем занять смену, пока ждём разрешения
Промт дописан под сегодняшнее состояние. Числа пересобраны: вершина
|
||
|
|
90cf01b374 |
docs(обзвон): владелец закрыл ВСЕ хвосты — 11 решений, план перестал врать
Разбор хвостов перед стройкой. Ждущих ответа вопросов у стройки больше нет: шесть «Новое-1…6» из плана закрыты, плюс пять решений сверх них. Протокол 83 решения вместо 72. Что решил владелец: - Р73 этикетка чужого юрлица остаётся как есть; - Р74 дорогу назад для рекламы чинит отдельная смена — до первых живых денег обзвона; - Р75 юриста про голос за границу не спрашиваем, идём как есть. Риск законный и он остаётся — записан прямо, а не спрятан; - Р76 правило часов одно на СМС и обзвон, живой СМС-модуль правим; - Р77 пять номеров арендуем к первому живому обзвону — ОТМЕНЕНО решением Р83; - Р78 тариф 2 плюс 10 остаётся, уточним по замеру; - Р79 длину и содержание речи решает клиент, хвост про опенер снят; - Р80 робота без слов-запиналок слушаем на приёмке; - Р81 телефония уезжает на новую АТС, Манго — только испытание; - Р82 расшифровка чужих записей живёт ШЕСТЬ месяцев, звук 30 дней; - Р83 у Манго ничего не арендуем, переезд робота в план обзвона не заводим. Три решения меняют сам план, и правки внесены: - Р76 разблокировала З-3.1 целиком — развилка «одно правило или второй дом» стояла открытой и ждала ровно этого. Отмечено, что круг на боевой файл отдельный: своя приёмка, свои две строки про соседей, свой выкат; - Р82 отменила мою страховку «расшифровка уходит вместе со звуком». Сроков теперь два, и чистка обязана стереть звук не тронув текст, а требование «сотрите меня» — достать до текста, когда звука уже нет; - Р81 и Р83 превратили «до аренды» в «до новой АТС» в З-3.3. Своя неточность названа вслух: в вопросе про часы я сказал «одна настройка», хотя решается общий код правила. Часы рассылки и часы обзвона остаются разными числами — этого требует Т36 и проверка 8 в З-3.1. Записано и в протоколе, и в плане, чтобы никто не прочитал Р76 как «одни часы на двоих». Найдено при разборе: два вопроса владельцу, добавленные редактором, не попали в промт смене 8 — «с какого кода звоним» и «расшифровки чужих записей». Оба заданы и закрыты. Промт исправлен. Работа, которая теперь не живёт нигде: перевод машины робота на новую АТС. В план обзвона не заводим по решению владельца, но машина прописана на Манго намертво. Названо вслух в протоколе и в промте. ОБА СТОРОЖА ТЕКСТА БЫЛИ МЕРТВЫ — вскрылось при этом же коммите. Пакеты стояли на месте, но внутри пустые: у одного остались только лицензия и схема, у другого файл обрывался посреди строки. Причина — установка, оборванная на пакете, которому нужен компилятор C++, а его на машине нет. Два вывода записаны в промт смене 8: - прежний датчик «посчитай папки в node_modules, должно быть под 750» ЛОЖНЫЙ: он показывал 745 при полностью мёртвых сторожах. Заменён на запуск самих программ; - npx МАСКИРУЕТ поломку: не найдя программу на месте, молча качает чужую версию из сети, и проверка «проходит». Три отчёта «разметка чистая» в этой сессии опирались на неё и доказательством не были. Починено: сломанные пакеты снесены и поставлены заново без сборки. Сторож орфографии показан КРАСНЫМ на подсунутой ошибке — только после этого ему верю. Он же нашёл настоящее: слово «запиналки» отсутствовало в словаре, добавлено. Проверено командой: план 3341 строка, 21 раздел, 47 задач, по волнам 7·7·5·8·9·3·8. Протокол 83 решения, 7 разделов. Разметка и орфография чистые живыми сторожами, а не скачанными. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
7c8618becc |
fix(портал): 404 называл посторонним внутренние имена, красную подпись не охранял никто
Живой замер 05.08.2026: любой вошедший клиент, запросив несуществующий или чужой номер, получал в ответ «No query results for model [App\Models\AdCampaign].» Три беды в одной строке. Английский язык — клиенту он ничего не говорит. Названы внутренние имена: и класс, и раскладка папок портала. И сообщение подтверждало, что за номером стоит именно кампания, — а на чужой номер портал обязан отвечать ровно так же, как на несуществующий, ничего не подтверждая. Само имя класса не отмычка, но экономит время тому, кто ищет вход: видно, на чём написан портал, как называются сущности и где их искать. Теперь на любой ненайденный путь, просящий JSON, уходит «Не найдено.» — и на чужой номер ответ байт в байт тот же, что на несуществующий. В журнал прежний текст пишется без изменений: разработчику он нужен. Место общее на весь портал: так отвечал КАЖДЫЙ firstOrFail() во всех разделах. Ловушка: обработчик на ModelNotFoundException не срабатывает никогда — Laravel успевает завернуть её в NotFoundHttpException раньше. Поймано тем, что сторож остался красным с тем же самым английским текстом. Второе: под слотом баннера есть красная подпись — единственное место, где клиент узнаёт, почему картинка не загрузилась. Сторожа на неё не было ни одного. Пропади она — заметить было бы некому: клиент жмёт «загрузить», ничего не появляется, и никакого объяснения. Написаны три: подпись показывает слова сервера, а не общую отговорку; пока отказа нет, подписи нет вовсе; сервер промолчал — портал всё равно объясняет. Ловушка: extractErrorMessage достаёт слова сервера только у настоящей ошибки axios. Подделка без isAxiosError проваливается в общую отговорку, и сторож зеленеет, не проверив того, ради чего написан. Прогоны: 3909 тестов Feature зелёные, vue-tsc чисто. Заодно промт смене 21 дописан утренними итогами: выкат сделан, восемь карточек пройдены, снята ложная тревога про главную ветку, добавлен разбор чужого слияния. |
||
|
|
4ff02f6935 |
docs(обзвон): промт смене 8 — стройка кругами по методу, все бумаги готовы
Все ступени метода до стройки пройдены: спека, ворота вслепую, план, ворота вслепую и суд, редактор. Следующая смена строит. Промт устроен так, чтобы смена двигалась по методу строго, а не по своему разумению: - §0.1 велит прочесть метод ЦЕЛИКОМ и даёт команду достать его из чужой ветки — в нашей его нет; - §0.2 отдельно выносит правило «правщик — не автор», которое прошлая смена нарушила, вместе с ценой нарушения: судья нашёл шесть ошибок, чинил их автор, редактор снял пять из шести. Замер: самопроверка автора стоит примерно одну шестую от проверки чужими глазами; - §0.3 повторяет устройство круга дословно из метода: задание целиком, помощник в углу, коммит, приговор выносит надзиратель сам; - §4.3 напоминает четыре мерки метода, включая сторожа, показанного красным. Числа собраны командой и помечены как подлежащие пересбору на входе. Упавший тест назван чужим с доказательством: в одиночку файл проходит 10 из 10. Названы первые круги — З-0.2 записи разговоров и З-0.1 чужое имя, обе до первого живого звонка и обе на сервере робота, то есть только с разрешения владельца. Названы предусловия волн: волна 5 не начинается без денег и стоп-листа, З-4.9 тумблер видимости делается первой в волне 4, иначе все клиенты увидят недостроенный обзвон. Восемь хвостов владельца, из них тикающие: этикетка чужого юрлица на экране у человека (заявка десять дней), аренда пяти номеров, юрист про трансграничную передачу голоса, правка живого СМС-модуля ради клиентских часов, дорога назад для рекламы. Грабли смены дописаны: сторож орфографии смотрит docs/grilling и не смотрит docs/superpowers; чужой замок git не снимать самому; php artisan test, не composer test; кириллица в словарь только через AppendAllText. Прежний промт помечен отработанным, чтобы его не взяли в работу заново. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
c11ee3ad87 |
docs(обзвон): редактор снял пять заплаток из шести — 47 задач, этап 5 закрыт
Метод велит: правщик — не автор. Правило нарушили — шесть находок судьи чинил тот, кто их допустил. Свежий редактор проверил эти заплатки и снял ПЯТЬ ИЗ ШЕСТИ. Замер записан в план: самопроверка автора стоит примерно одну шестую от проверки чужими глазами. Что не выдержало: - «семь заходов в мост»: таблица исправлена, а в самих задачах порядок остался старый — З-4.5 звала себя последней, З-6.7 ждала её. Исполнитель читает задачу, а не таблицу; - имена наставлений робота ВЫДУМАНЫ: по мосту строка 41 — lena-vhodyashchie-promt, а план написал lena-priyomshchik-promt. Плюс в наставления подмешиваются три файла базы знаний, которых план не назвал вовсе; - З-4.8 закрыла Т23 наполовину: требуется четыре заготовки, названа одна; у Т13 молчаливый робот проходил все пять проверок; - З-6.8: имя очереди задаётся внутри задания, а не в routes/console.php — тот же класс ошибки, за который судья ругал соседнюю задачу; и задача стояла последней, хотя задания пишутся в волнах 1, 3, 5; - проверки манеры: «в тексте нет слов про манеру» покраснеть не могла — что считать словом про манеру, не сказано нигде; вторая половина находки судьи не починена вовсе; - порядок по узким горлам: из пяти расхождений судьи три остались, и добавилось новое. Выдержала одна: переписанная проверка дословности каркаса в З-4.3. Редактор нашёл сверх этого: Т17 — четвёртое требование без задачи, пропустили и автор, и судья; «с какого кода звоним» из §18 спеки в плане не было ни слова; Р72 исполнено наполовину. Внёс 33 правки, добавил раздел приёмки шестью сквозными кругами и два вопроса владельцу. Проверил числа командой и нашёл семь неверных: web.php 799 а не 783, чертёж записей на 3780 а не 3776, DealController а не DealsController, «11 задач на схему» на деле 10, «5 в расписании» 4, «восемнадцать хвостов» 24, «Р1-Р70» при 72 решениях. Поверх редактора надзиратель добавил своё: - З-4.9 тумблер видимости модуля — редактор назвал беду, но сказал прямо, что нужна новая задача, а не заплатка. Пункт «ИИ колцентр» уже стоит в меню без адреса; З-4.1 дописывает адрес — и все клиенты видят недостроенный обзвон. Своего включателя у модуля нет, это уже ловили при выкате рекламного модуля. Задача делается ПЕРВОЙ в волне 4, до З-4.1; - две устаревшие ссылки на раздел «Два места, где нужно слово владельца» — раздел переименован, а спор двенадцати и десяти закрыт решением Р71. Проверено командой: 3258 строк, 20 разделов, 47 задач, по волнам 7·7·5·8·9·3·8, висячих ссылок нет, разметка чистая. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
1daee489a7 |
feat(смс): имя отправителя выбирается по оператору получателя, а не одно на всех
Имя согласовывает ОПЕРАТОР, и у каждого оно своё. Клиентская рассылка несла одно имя всем каналам, а умолчанием ему служило `services.sms.mts.naming` — то есть имя, согласованное с МТС, подставлялось Теле2 и подставилось бы Мегафону и Билайну. Живой отказ Теле2 `invalid_source_address` 04.08.2026 — ровно этот механизм. На бою у клиентов заведено НОЛЬ своих имён, значит это касалось каждой отправки. Появился ClientSmsSenderResolver: спрашивает имя у той сети, чей номер. Пустая строка на выходе — не «имени нет», а «своего имени для этой сети нет, канал подпишется собственным согласованным именем из настроек». Переведены на него оба места отправки: рассылка (SendClientSmsCampaignJob) и авто-СМС по сделке (SendAutoSmsForDealJob). Универсальный СМС-центр передавал имя КАК ЕСТЬ — теперь тоже падает на своё (SMSC_NAMING). Раньше пустое имя туда не приходило никогда, с этой правкой стало бы приходить: ушли бы СМС без отправителя. Заплатка внутри канала Теле2 (приведение написания к своему регистру) остаётся, но больше не несущая: она чинила следствие в одном канале, а причина была общая. Экранное имя (ResolvesClientSmsSenderName) НЕ трогали — это запись намерения, а не то, чем подпишется сеть; в его шапке теперь написано почему, чтобы не «починили» обратно. 🪤 Урок по дороге: авто-СМС ловит любую ошибку и молча выходит. Справочник имён был передан не в ту функцию — вместо падения получилась ТИШИНА, не уходило ничего. Поймали три чужих теста, до того зелёных. Проверено: 4614 тестов, упал один чужой (ExampleTest — в этой папке не собран фронтенд). Статанализ 0. Стиль правленых файлов чист. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
c50b3dbe1e |
docs(обзвон): ворота вслепую и суд пройдены — третья редакция плана, 46 задач
Вторую редакцию проверили трое: двое ворот вслепую и судья. Оба проверяющих
работали по спеке, план им был запрещён словами, оба отчитались «план не
открывал» и оба назвали вещи, которых в плане нет. Судья план читал и искал
ошибки надзирателя.
Проверено командой: план 2801 строка / 19 разделов / 46 задач, спека 2126 / 18 /
96 требований, протокол 2150 / 7 / 72 решения. Разметка чистая во всех трёх.
Полный прогон: 4755 из 4760, упавший тест чужой (коммит
|
||
|
|
9b634d20dc |
fix(сторож секретов): приёмочные документы в корне docs/superpowers/ вставали насмерть у всех
Полная проверка истории находила 5 «незамаскированных телефонов» в
docs/superpowers/2026-07-27-PRIEMKA-client-sms-fixes.md и не пускала push НИКОГО.
Замерено: все пять с префиксом 7999 — синтетические по правилу проекта
(«реальные НИКОГДА»). Настоящих персональных данных нет.
Причина: список исключений покрывает подпапки docs/superpowers/{plans,runbooks,
specs,audits,findings,prototypes}, а этот документ лежит ПРЯМО в docs/superpowers/
и под них не подпадал. Файл из истории не убрать, не переписав общую ветку, —
значит, чинить надо список.
Разрешение узкое: только docs/superpowers/ГГГГ-ММ-ДД-PRIEMKA-*.md. Прочие файлы
корня docs/superpowers/ проверяются по-прежнему.
Проверено: до правки — 5 находок, после — «no leaks found» на 7589 записях.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
78e8a57b8e |
docs(обзвон): план переписан под 21 новое решение владельца — 41 задача в семи волнах
План 2026-08-04 писался, когда закрытых развилок было ноль. Сейчас закрыты 28 из 29, и пометки «заблокировано развилкой» врали в обе стороны. Вторая редакция. Было 32 задачи в шести волнах — стало 41 в семи. Проверено командой: 2292 строки, 18 разделов, 41 задача, разметка чистая, ссылок на файлы ноль. Разблокировано решениями владельца: вход клиента Р66/Р67/Р60, окно часов Р64, письма Р52, каркас и личная правка Р46, обратные звонки Р62/Р63/Р48/Р54, запись менеджера Р61, замок Т31 Р57, источники получателей Р65. Из семнадцати заблокированных задач не осталось ни одной запертой полностью. Прибавилось: - волна 5 «авто-обзвон» целиком — решение Р39, перенос измеренного устройства авто-СМС; - З-0.5 обрыв на 60-й минуте и подсказка менеджеру на 55-й — Р43; - З-1.7 письмо про обе остановки — Р56; - З-3.6 подтверждение согласия при каждой загрузке — Р42; - З-3.7 закрытые номера: тишина, счётчик, бесплатность — Р48, Р49; - З-4.4 личная правка сильнее каркаса — Р46; - З-4.5 манера живёт у робота — Р68; - З-4.2 сборка скрипта машиной — найденная дыра: требование Т7 стоит в спеке с первого дня, а задачи под него не было ни одной. Переписано против первой редакции: - стоп-лист — решение Р45 обратно прежней рекомендации: свой, пятый список; - чистка — три ступени по одной строке вместо одной операции, Р50 и Р51; - заморозка — два режима: авто по поступлению, база пачкой, Р39; - отчёты — по людям, а не по наборам номера, Р47; - З-2.4 первой редакции упразднена: Р50 сняло бессрочную обезличенную расшифровку. Два места, где нужно слово владельца, оба всплыли после закрытия развилок: 1. Двенадцать попыток дозвона требуют двенадцати разных номеров Р53, а арендовать решено десять Р41. Арифметически невыполнимо. 2. У авто-обзвона нет загрузки списка — к чему привязать подтверждение согласия, не решено. Спека выправлена шестью правками: освобождён задвоенный номер Т73 → Т98, снята устаревшая бессрочная расшифровка, названо противоречие десяти и двенадцати номеров в трёх местах. Требований 96, разделов 17, закрытых развилок 28. Промт следующей смене заменяет прежний — очереди развилок больше нет. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
784ed07392 |
docs(обзвон): все 29 развилок пройдены, решения владельца Р50-Р70
Допрос по спеке «Обзвон» доведён до конца: 28 развилок закрыты решениями
владельца, одна (плата за второе плечо перевода) намеренно отложена до замера
цены звонка. Незаданных развилок не осталось — объявлено вслух.
Хранение и закон:
- Р50/Р51 — сроков два: звук месяц, текст три месяца, потом остаются сухие
итоги. Строку не удаляем: иначе нечем подтвердить счета за прошлые кварталы;
- Р52 — в письме менеджеру и номер, и ссылка на сделку (цена: наша чистка до
почтового ящика не дотягивается);
- Р59 — об удалении своих записей клиент узнаёт надписью на их месте;
- Р61 — менеджера клиента о записи не уведомляем.
Темп и защита:
- Р53 — шесть попыток в сутки, ровно два дня, всего до двенадцати;
- Р58 — три одновременные линии, больше только через нас (работает без замера);
- Р64 — часы менеджера задаёт клиент, и они только сужают общее окно.
Деньги:
- Р54 — обратный звонок после кампании идёт в долг, ничего не резервируем;
- Р55 — большой список запускать не мешаем, но последствие названо словами и
нужно подтверждение;
- Р56 — письмо о минусе называет обе остановки: обзвон и рекламу.
Вход и скрипт:
- Р66 — клиента без записей берём: стандартный каркас, наши вопросы, его сайт;
- Р60/Р67 — пятнадцать записей ориентир, список на входе один;
- Р65 — источников получателей три, как в рассылках; моё Т43 снято;
- Р62 — приёмщица одна, но представляется по сделке звонящего;
- Р68 — манера живёт у робота, а не в скрипте;
- Р69 — машина отдаёт скрипт сразу размеченным;
- Р70 — правку раскладывает руками тот, кто её принимает;
- Р63 — на своём номере отвечаем всегда, запрет второго перезвона снят.
Правило, выведенное по ходу и решившее часть вопросов само: надпись — там, где
клиент за свои деньги получает работу; замок — там, где работа гарантированно
бесполезна. Р18 про выбор, а выбора нет, когда результат предрешён.
Три слепых сторожа найдены и переписаны:
- проверка «манера во всех скриптах одинакова» проходила бы ровно при
расползании правки, то есть зеленела в момент беды;
- проверка «три клиента попросили одно и то же» считала буквальные совпадения
в свободном тексте, которых не бывает;
- Т70 всё ещё обещало шесть месяцев звука вместо принятого месяца.
Дыра, вскрытая ответом: минус на счету гасит клиенту весь Директ, а кто
включает рекламу обратно после пополнения — не задано ни одним решением.
Настоящий мобильный номер, попавший в протокол как название проекта, замаскирован.
В истории он остаётся (коммит
|
||
|
|
e5efa5e7b4 |
docs(приёмка Яндекса): промт смене 20 — проверка починки типа группы
Главное для следующей смены: починка типа группы объявлений выкачена, но живьём не проверена — Директ не меняет тип после создания, поэтому #6 и #11 доживают срок с неправильной группой. Проверить можно только новой кампанией, и это решение владельца, оно стоит денег. Расписано: как проверять дёшево — сразу после запуска глянуть Subtype группы, не дожидаясь показов; эталон для сравнения — чужая кампания 713051718 с группой по профилю, 457 показов за четверо суток при сегменте из 134 человек. Отдельно записаны три урока смены: тихий ноль опаснее громкой ошибки, Яндекс молча принимает неверную настройку, и версии надо отвергать по одной за раз. Хвостов пять, живое состояние сведено таблицей, предупреждения про соседние смены в той же ветке и на том же боевом, про замороженный GitHub и про пароль открытым текстом в адресе gitea. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
65f6d06ef2 |
fix(реклама): создаём группу по профилю пользователей, а не по ключевым фразам
Вопрос владельца «может мы что-то напортачили в настройках?» оказался в точку. Нацеливание у нас одно — список телефонов, прицепленный отдельным условием через AudienceTargets на сегмент Яндекс.Аудиторий. Ключевых слов мы не задаём вовсе. Но группу создавали «по ключевым фразам» и оставляли список слов пустым. Дока Директа разводит два типа прямо: CpmBannerKeywordsAdGroup — с ключевыми фразами, CpmBannerUserProfileAdGroup — с условием нацеливания по профилю пользователей. Сегменты Аудиторий это второй. Почему поломка себя не выдавала: Яндекс такую группу ПРИНИМАЕТ. Она создаётся, объявления проходят модерацию, включаются в показ, кампания рапортует «Идут показы». Ни ошибки, ни отказа — просто показов нет. Замеры, которыми отвергнуты две другие версии: - ставка: подняли с 72 до 300 рублей за тысячу, выше рыночных 250, ждали четыре часа — как был один показ, так и остался; - размер списка: у чужой рабочей кампании того же кабинета в основе сегмент на 134 человека, и она дала 457 показов за четверо суток. У нас 1653 человека и один показ. Сторож переписан и принят красным. Тип группы после создания не меняется, поэтому живые #6 и #11 так и доживут с неправильной группой. Проверить починку можно только новой кампанией — это решение владельца, оно стоит денег. Прогон: 452 сторожа блока рекламы, статанализ 0, стиль чист. Схема не менялась. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
1987737681 |
fix(смс): приёмник Теле2 сам называет адрес звонившего — белый список было нечем заполнить
Пока SMS_T2_WEBHOOK_IPS пуст, приёмник никому не отказывает — а значит, и записи «звонили с такого-то адреса» в журнале не появляется. Круг замкнут: список нельзя заполнить, потому что адреса неоткуда взять, а взять неоткуда, потому что список пуст. Теперь при пустом списке каждый принятый звонок пишет строку client_sms.delivery_hook_open_gate с адресом. Как только список задан — смолкает, шуметь в журнале постоянно не будет. Памятка: откуда забрать адреса одной командой; в «Открытом» отмечено, что круг разорван, и отдельным пунктом записан незакрытый долг про имена отправителей по каналам. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
61be447b41 |
fix(реклама): Директ отвергал запрос числа показов, а портал читал отказ как ноль
Самая тяжёлая находка смены, вылезла из вопроса владельца «показано 0, почему? там один же есть» — из сверки карточки с кабинетом глазами. Замер на боевом: запрос портала к Директу отвергается с кодом 400 — «В params отсутствует обязательное поле IncludeVAT». А код ответ не проверял: брал тело, переводил в число и получал ноль. То есть «показов не было» — не факт, а проглоченная ошибка. Чем накрывается. Это единственный источник числа показов, и у него две работы: число «Показано» на карточке и основание списать деньги за открученную рекламу. Значит ни за один показ клиенту не выставлялся счёт — реклама шла за наш счёт. И тихий ноль опасен сам по себе: затирает настоящее число на карточке, а при истёкшем сроке закрывает кампанию с возвратом всей заморозки. Почему не поймали раньше: ноль выглядит как «показов ещё не было» — законное состояние новой кампании. Отличить «не было» от «не смогли спросить» портал не умел. На этом месте с самого начала висел TODO про сверку формата перед боем. Три правила и три сторожа: - IncludeVAT в запросе — без него Директ отвергает запрос целиком; - processingMode auto — иначе отчёт строится в фоне и приходит пустое тело; - громкая ошибка вместо тихого нуля при не-2xx и нечисловом ответе. Наверху ошибка уже обработана правильно: обход пишет предупреждение и идёт дальше, а «Пауза» при такой ошибке не отпускает деньги. Четвёртая мина, вскрытая сторожем уникальности: имя отчёта строилось из секунд, два вызова в одну секунду получали одно имя и второй читал кэш первого. Добавлен случайный хвост. Настоящий ноль из Яндекса остаётся нулём — отдельный сторож держит и это. Прогон: 393 сторожа блока рекламы, статанализ 0, стиль чист. Схема не менялась. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
04657bd6cd |
feat(воронка): фильтр по нише и на личном экране менеджера
Раньше «Ниша» стояла только на «Воронке отдела». Теперь тот же фильтр есть и на личном экране менеджера — он обзванивает подряд одну нишу, ему нужнее всех. Список ниш у менеджера считается по ЕГО карточкам: сужение «только свои» наложено раньше, поэтому чужая ниша в список не попадает, а ?rubric= не может стать лазейкой к чужой воронке — на это есть отдельная проверка. Поведение обоих экранов задаёт один кусок кода, composables/prospectRubricFilter.ts. Двумя копиями «Без ниши последним» и самосброс исчезнувшей ниши разъехались бы на первой же правке. Проверено: Pest по продажам 525 зелёных, Vitest по фронту 2077 зелёных, vue-tsc и Larastan чисто. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
c95c7457ea |
docs(смс): памятка t2 — ловушка «фоновый исполнитель держит старые настройки»
Поймано на бою 04.08.2026 первой же клиентской рассылкой на Теле2: номер
пропущен с пометкой skipped_no_route, хотя канал был включён и проверен.
Причина: рассылку выполняет не веб-часть, а liderra-queue.service. Он читает
настройки ОДИН раз при запуске и держит в памяти; systemctl reload php8.3-fpm
его не касается.
исполнитель запущен 10:23:30 ← про Теле2 ещё ничего не знает
канал включён 10:47
рассылка клиента 10:51:17 → skipped_no_route
Лечение — systemctl restart liderra-queue после каждой правки .env, влияющей
на рассылки. Датчик — спросить маршрутизатор тем же кодом, каким ходит
исполнитель: pick("tele2") обязан вернуть tele2.
Отдельно записано, что рядом живут ещё три исполнителя (bot, autopodbor
find/study) — рассылок они не выполняют, но при правках, касающихся их,
перезапускать надо и их.
Второй урок того же дня, в памятку не попал, но стоит помнить: проверять
таблицы модуля обычным соединением БЕСПОЛЕЗНО — построчная защита без
указания клиента честно показывает ноль. Смотреть служебным соединением
pgsql_supplier, иначе «кампаний ноль» соврёт при четырёх живых.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
38fae090f8 |
fix(реклама): число показов узнаём каждый час, а не раз в сутки
Владелец сверил карточку с кабинетом Яндекса: в отчёте «Показано 0», а в Директе за этот день один показ. Причина наша. delivered_impressions — единственное число, из которого карточка берёт «Показано», — обновляется только внутри ChargeCampaignSpendJob, а он ходил раз в сутки в 04:20. Кампанию #11 запустили в 04:39, на двадцать минут позже обхода, и до следующего утра клиент видел ноль при идущих показах. Обход стал ежечасным. Чаще списывать безопасно по устройству счётчика: он идемпотентен по ключу «yandex-imp:кампания:показы» и монотонен — уходит только прирост, повторный прогон с тем же числом показов денег не трогает. Сторож держит расписание 0 * * * *, принят красным на «20 4 * * *». Побочная польза: нехватку денег AdStopAll заметит через час, а не через сутки. Полностью в ноль это не выводит — у самого Директа статистика приходит с задержкой, и «Показано» всегда чуть отстаёт от кабинета. В отчёте приёмки записан замер рынка: цена в этом кабинете около 250-300 рублей за тысячу показов при нашей прежней ставке 72, и потолок показов упирается не в деньги, а в размер списка — кампания с бюджетом 13000 рублей за четыре дня набрала 457 показов и потратила 138. Прогон: 388 сторожей блока рекламы, статанализ 0, стиль чист. Схема не менялась. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
732de3c33b |
fix(реклама): пауза сперва платит за показанное, потом отпускает деньги
Вопрос владельца вскрыл дыру в деньгах. Списание за показы делает суточная задача в 04:20 и берёт только кампании со статусом «крутится». Кампанию, оставленную на паузе, она не видит никогда — значит показы, сделанные с последнего списания, не оплачивались вовсе. До суток рекламы клиент получал бесплатно, а Яндексу за неё платили мы. Дыра открывалась только если после паузы не возобновить: при возобновлении пропущенное списывается разницей. Теперь «Пауза» после остановки рекламы спрашивает у Директа накопительное число показов и списывает за них, и только затем возвращает остаток заморозки. Лишнего обращения это не стоит — в Директ мы в этот момент и так ходим останавливать рекламу. Три случая разведены: - обычный: списали, поставили паузу, вернули остаток; - списание само закрыло кампанию по смете или сроку — уходит в «Показы откручены» по ВЫХОДУ 1, паузу поверх не ставим, пятого выхода снятия заморозки не появляется; - не узнали число показов — рекламу остановили, но деньги НЕ отпускаем и говорим об этом клиенту. Сторож принят красным: до починки списание давало 0.00 вместо 300.00 рублей за 2500 показов. Найдена соседняя дыра того же рода при «Остановлено, нет денег» — там слушатель работает изнутри денежной транзакции, и обращение к Яндексу внутри неё держало бы замок строки на время сетевого запроса. Разобрано в отчёте, чинить отдельным заходом. Прогон: 387 сторожей блока рекламы, 177 денежных, статанализ 0, стиль чист. Схема не менялась. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
6490a3ba0d |
docs(смс): памятка t2 — исправлено враньё про универсальный канал
Прошлая правка утверждала «ключей СМС-центра на бою нет вовсе, универсального
канала не существует». Это неверно, и ошибку показал владелец.
Причина ошибки — слабый датчик: искал в .env по «SMS_», а ключи СМС-центра
зовутся SMSC_LOGIN / SMSC_PASSWORD. Поиск по SMS_ их физически не мог найти,
и отсутствие находки я принял за отсутствие канала.
Спросил у самого маршрутизатора (config('services.sms.providers') на бою):
smsc: enabled=false, serves=["*"], price_kopecks=0 — НАСТРОЕН, но ВЫКЛЮЧЕН
mts: enabled=true, serves=["mts"], price_kopecks=503
sandbox=false, failover_to_universal=false, SMSC_LOGIN задан
Универсальный канал существует и настроен, он просто выключен. Значит, до
абонентов Теле2 есть два разных пути — свой канал t2 по 6,92 ₽ или включить
универсал обратно, — и это выбор владельца, а не безальтернативность.
🪤 Отдельно записана ловушка: у СМС-центра price_kopecks = 0, потому что
SMS_SMSC_PRICE_KOP не задан. Включить универсал, не выставив цену, значит
возить смс клиентам бесплатно за свой счёт.
Раздел отката поправлен: запасного пути на автомате нет (failover_to_universal
выключен), но «возвращать некуда» — тоже неправда.
Урок на будущее: состояние канала спрашивать у маршрутизатора, а не искать
строки в .env по угаданному префиксу.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
e97d4ebe11 |
docs(смс): памятка t2 — правка по живому бою, добавлен пропущенный шаг
Сверено на боевом сервере 04.08.2026 (чтение .env и client_sms_settings). Две правки, обе по фактам, а не по памяти: 1. Ключей СМС-центра на бою НЕТ — универсального канала не существует (SMS_SMSC_ENABLED берёт умолчание false). Прежняя формулировка «номера Теле2 идут через СМС-центр, сравнить цену» была неверна: сравнивать не с чем, подорожания при включении не будет — сегодня абонентам Теле2 не уходит ничего. На бою живут только SMS_MTS_ENABLED=true, SMS_MTS_PRICE_KOP=503, SMS_SANDBOX=false. 2. Добавлен ПРОПУЩЕННЫЙ обязательный шаг 2.3-бис: в client_sms_settings стоит allowed_operators = ["mts"] (решение владельца 03.08). Пока так, номера Теле2 отсеиваются ДО отправки и до канала дело не доходит вовсе — настроенный канал молча не заработал бы. Нужно дописать "tele2". Раздел отката тоже поправлен: возвращать номера Теле2 некуда, откат означает возврат к «не уходит ничего». Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
458f206ce9 |
feat(смс): канал Теле2 — отправка, судьба сообщений и приёмник отчётов
Канал «SMS-Таргет» (target.t2.ru, HTTP API v2, Basic Auth) обеими половинами:
- отправка через POST /send_message (msisdn числом, shortcode, text);
- опрос судьбы GET /send_message/message-id-{id} — по одному сообщению,
пакетного запроса у t2 нет;
- приёмник обратных звонков GET|POST /api/webhook/t2-delivery/{secret}
со строками ?id=&status=&parts= — форма другая, чем у МТС.
Ловушки, закрытые тестами:
- слово status в ответе встречается дважды: снаружи «запрос удался»,
внутри судьба сообщения. Спутать = объявить доставленным всё подряд;
- отказ приезжает с HTTP 200 — судим по телу, не по коду;
- незнакомое слово статуса судьбу НЕ меняет, сохраняется в delivery_raw.
За «не доставлено» клиенту возвращаются деньги (В-198) — выдумка двигала
бы рубли;
- t2 наш ответ не проверяет и повторов не делает: пропущенный звонок потерян,
поэтому опрос client-sms:poll-delivery остаётся обязательным.
Деньги приёмник не двигает — возврат живёт в команде опроса (В-146).
Защита приёмника как у МТС: секрет не короче 32 знаков (пустой = закрыт
наглухо), список адресов отправителя, счётчик обращений; чужому 404, не 403.
Канал ВЫКЛЮЧЕН и живьём не пробован: имя отправителя Liderra.ru (заявка
№ 31121) на согласовании, без него t2 не выдаёт логин с паролём.
Порядок включения — docs/superpowers/runbooks/2026-08-04-vklyuchenie-kanala-t2.md
Миграций нет: графы судьбы появились ещё при МТС. Теле2 уже был в списке
разрешённых операторов и в тарифах админки — новых экранов не нужно.
Проверено: 78 тестов зелёные, Pint чист, статанализ по ВСЕМУ проекту 0 ошибок,
маршрут зарегистрирован. Против main — только вставки, удалений нет.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
2ce4bbc058 |
docs(обзвон): план стройки, ворота по плану и решения владельца Р39–Р41
План фичи «Обзвон» — 32 задачи в шести волнах, 17 помечены «заблокировано развилкой», в конце таблица «развилка → сколько задач держит». Ворота вслепую по плану, двое разными ходами (по требованиям подряд и «пройди один рабочий день в бою»). Нашли то, чего не видели ни спека, ни надзиратель: - заморозка денег под обзвон гасит клиенту рекламу: выключатель считает «баланс не меньше замороженного», при нарушении слушатель тушит все кампании; - перевод звонка — два плеча у оператора, а клиенту выставляется одно соединение: чем чаще успешный перевод, тем хуже сходится экономика; - обратный звонок сегодня попадает в наш собственный отдел продаж; - «исход» означает две разные вещи — чем кончилась попытка и чем кончилась работа с номером. Развилок в спеке стало 29. Ни одна не разрешена — это решения владельца. Новые решения владельца: - Р39 — режимов обзвона два, морозим по-разному: авто-обзвон по поступлению лида, обзвон базы пачкой. Владелец поймал вопросом ошибку в моей рекомендации: спрятать заморозку от выключателя значило сделать минус ежедневным, а резерв фикцией; - Р40 — звук храним месяц, не полгода (правит Р22): место падает с ~50 ГБ на клиента до ~8, а учит текст, а не звук; - Р41 — пул номеров общий на портал, аренду платим мы. Чинит то, что сегодня человек после робота перезванивает нашему отделу продаж. Спека дополнена авто-обзвоном (Т33а–Т33е) и разведённой заморозкой (Т58/Т58а/Т58б). Поправка к прежнему докладу: тревога «записи положат портал» была ошибочной — по чертежу звук лежит в облачном хранилище, а падение 29.05 было на другой машине. Правдой осталось, что в хранилище сегодня не кладётся ничего. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
fcf4331d24 |
fix(реклама): карточка объясняет, что происходит с объявлениями
Владелец, глядя на две живые карточки: «статус на модерации — не понятно, идут показы или ожидает! и принято, когда реально отклонили». Корень: ярлык один на всю кампанию, а объявления внутри в разных состояниях, и у объявления два независимых признака — прошло проверку и показывается. Карточка сваливала их в одно слово. Три починки: 1. Портал спрашивает Яндекс о модерации каждые 15 минут вместо двух часов. У кампании #11 проверка кончилась между обходами, и клиент полтора часа видел бы «На модерации» на уже работающей кампании. Сторож держит расписание */15. 2. Портал считает придержанные объявления — одобренные, показ которых Яндекс не включил. Признак уже ставился на объявление, но наверх не поднимался; теперь в списке есть banners_held. Два сторожа: слепок боевой #6 даёт 13, слепок #11 даёт 0. 3. Ярлык называет положение дел словами, приписка всегда даёт числа: «Яндекс проверяет объявления» + «Одобрено 9 из 15, остальные ещё проверяются»; «Одобрено, но показ не включён» + «Одобрено 13 из 15, отклонено 2. Яндекс их пока не показывает — причина в сообщениях кампании»; «Крутится» + «Одобрено 15 из 15». Числа показываются всегда, а не только при частичном отказе. Живой замер, ради которого всё это: у боевой кампании #6 все 15 объявлений выключены при 13 одобренных — Яндекс просит документы, поэтому показов нет с 30.07. Карточка про это молчала. Прогон: 386 сторожей блока рекламы, 1974 фронта, статанализ 0, стиль чист. Схема не менялась. Тесты гонялись на отдельной базе liderra_testing_s19 — общую соседняя смена сносила трижды за прогон. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
18f3352823 |
fix(реклама): отказ Директа до заведения кампании возвращает деньги клиенту
Живой случай 04.08.2026 на боевом: Директ отказал по позиции уже ПОСЛЕ заморозки, и 1 020,60 ₽ клиента повисли запертыми. Кампания осталась черновиком, а «Паузы», которой снимают заморозку, у черновика нет — вернуть деньги было нечем. Заморозка стоит до первого обращения к Директу намеренно: иначе не хватило бы денег, а в Яндексе крутилась бы включённая реклама за счёт Лидерры. Значит убирать за неудачей обязан сам запуск. Граница точная: бронь отпускается, только если в кабинете Яндекса ничего не завелось (yandex_campaign_id пуст). Есть кампания — деньги остаются заперты, она способна крутить показы; запуск возобновляемый, клиент дожмёт его повторным нажатием, а заморозка идемпотентна и второй раз денег не запрёт. Сделано обёрткой над запуском, тело не переливалось. Порядок уборки верный: сначала возвращается прежний статус кампании, потом деньги. Сторожа держат обе стороны границы: «возвращает деньги, если Директ отказал до того, как кампания заведена» (числа #11 один в один) и «оставляет деньги запертыми, если кампания в Яндексе уже заведена». Второй был зелёным сразу, поэтому проверен вырезанием — убрал условие границы, он покраснел. Разбор — в отчёте приёмки, §6ж. Прогон: 383 сторожа блока рекламы, статанализ 0, стиль чист. Схема не менялась. |
||
|
|
a599621a0d |
docs(воронка): план работ — ниша на карточке и фильтр по нише
20 задач по TDD, от колонки в базе до приёмки глазами. Портал: колонка sales_prospects.rubric, ключ сравнения ниш и приклейка к существующему написанию, приём ниши из поиска и из прогрева, список ниш текущей выборки, фильтр и «Без ниши», список всех ниш для подсказок, правка ниши в карточке, сопоставление для разовой проставки, служебная ручка и команда artisan. Фронт: плашка ниши на карточке, фильтр между «Менеджер» и «Происхождение» со сбросом и подписью, поиск похожих ниш, поле «Ниша» с подсказкой. Служба поиска: передача ниши прогона при «отдать менеджеру» и разовый проход по всем сохранённым спискам. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
e47e6f239b |
docs(воронка): спека — ниша на карточке и фильтр по нише
Ниша едет из «Поиска клиентов» в карточку воронки: новая колонка sales_prospects.rubric, приём ниши от службы поиска и из прогрева, разовая проставка всех текущих карточек по ИНН / сайту / названию+городу. Фильтр «Ниша» встаёт между «Менеджер» и «Происхождение»; список ниш считает сервер по текущей выборке — с учётом менеджера, происхождения и дат, но без учёта самой ниши. Пункт «Без ниши» — только когда такие карточки есть. Исчезла выбранная ниша — фильтр сбрасывается сам и говорит почему. Своим карточкам нишу вписывают руками: подсказка по уже существующим, опечатки и другой регистр приклеиваются к существующей молча, похожее название подсказывается, но сохранить своё разрешено. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
d1728f742d |
fix(реклама): период показа не короче суток — Яндекс не берёт нулевой
Живой запуск кампании #11 на боевом 04.08.2026 отказал во второй раз: «Для стратегий с произвольным периодом, период не может быть меньше 1 дня». Причина — вчерашняя починка «лишнего дня»: у заказа на один день конец периода совпал с началом, а такой период Яндекс не принимает. Отсюда следует, что вчерашний разбор был наполовину ошибочным. Заказ «показывать 1 день» физически занимает у Яндекса два календарных дня, и минимум площадки на него — 600 ₽, а не 300 ₽. Это пол площадки, а не завышение портала: вчера вывод был сделан из кода и не проверен живьём. Стало: конец периода — «сегодня + max(run_days − 1, 1)». Для сроков от двух дней вчерашняя починка сохраняется, однодневная кампания снова занимает два дня. Числа двух сторожей однодневного минимума возвращены к 6945 показов / 833,40 ₽, пояснения переписаны — прежние закрепляли ошибку словами. Сторож переименован: «ставит в Директе период по заказу клиента, но не короче суток». Красный проверен на однодневной длине, две другие держались. Разбор выката и обоих живых отказов — в отчёте приёмки, §6ж; §6г помечен как частично ошибочный со ссылкой туда же. Прогон: 381 сторож блока рекламы, статанализ 0, стиль чист. Схема не менялась. |
||
|
|
06f49ce0d1 |
docs+защита: спека обзвона по методу ведения фичи, чужие телефоны убраны из виду гита
Дыра: golos-test/inostrannye/ содержала выгрузку «Записи разговоров» с 44 настоящими номерами живых людей и не была в игноре — любой `git add .` затащил бы персональные данные в историю навсегда. Закрыто правилом /golos-test/, проверено check-ignore. Работа шла по docs/superpowers/METOD-vedeniya-fichi-v1.md (файл лежит в ветке feat/night-dispatcher-part1): спека → ворота вслепую → правки чужой рукой → судья. - спека фичи «Обзвон»: 72 требования, 18 развилок владельца намеренно не разрешены; - приёмочный лист ворот написан ДО работы, а не после; - протокол допроса: решение Р38 (счёт уходит в минус, долг остаётся за клиентом) и поправка к приписке про экран СМС — путь к файлу был моей догадкой, а прочитан следующими как слово владельца, и по нему ошиблись все трое. Замеры смены: ворота вслепую двумя разными ходами дали 0 из 72 «умеет»; полный прогон 4650 проверок, 4646 зелёных, красных ноль. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
a2d68a5180 |
fix(реклама): период частоты — срок кампании, а не глубина сбора аудитории
Живой запуск кампании #11 на боевом 04.08.2026 отказал: Яндекс ответил «Значение поля PeriodDays должно быть в диапазоне от 1 до 30». Деньги отказ не тронул, в Директе ничего не создано. Мастер писал в период частоты показов число из другого шага — глубину сбора аудитории (audience_days). У #11 это 90, и кампания не могла запуститься вовсе: отказ приходил на последнем шаге, когда клиент уже 15 часов ждал сведения аудитории и залил пятнадцать картинок. Обойти изнутри портала было нечем — период частоты клиенту не показывают и менять не дают. Период частоты — свойство самой кампании: клиент выбирает только «показывать N раз», значит N относится ко всему сроку показа. Сервер считает его по run_days и срезает до потолка Яндекса в 30 дней. Мастер это поле больше не отправляет. Сторожа: три длины срока (1 → 1, 14 → 14, 45 → 30) при заведомо негодном 90 в базе; на фронте — что мастер поле не шлёт. Оба приняты красными; причину отказа на бэкенде доказал, вписав в сторожа нынешние 90. Сторож фронта закреплял баг словами (период = глубина сбора) — переписан. Разбор целиком, включая вскрытую попутно дыру «отказ Директа после заморозки запирает деньги», — в отчёте приёмки, §6е. Прогон: 381 сторож блока рекламы, 2051 фронта, статанализ 0, типы Vue чисты. Схема не менялась. |
||
|
|
d1b4f42183 |
docs(приёмка Яндекса): промт смене 19 — запуск #11 и остатки находок
Обе крупные находки приёмки закрыты сменой 18: лишний день в периоде и кнопка «Запустить» у клиента. Промт передаёт дальше запуск кампании #11, хвосты смены и остатки списка. Хвосты названы отдельным разделом: запуск #11 не наступил по времени из-за 15-часового сведения сегмента; предупреждение о минимуме площадки в мастере разобрано, но не начато; замер красной подписи на 10 минут не сделан; пять починок лежат только в ветке и на бою не работают. Записаны уроки смены: общее сообщение о падении не доказывает причину — впиши в сторожа нынешнее поведение и убедись; числа в чужих сторожах пересчитывать от исходных величин, а не подгонять под выданное, и править их пояснения; путь к файлу с сообщением коммита — два уровня вверх от корня репозитория. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
656cf80f31 |
feat(реклама): кнопка «Запустить» у клиента на карточке кампании
Кампания в состоянии «Готова к запуску» не двигалась ничем — ни человеком, ни расписанием. Клиент собирал рекламу, заливал пятнадцать картинок, отправлял заявку и упирался в тупик: на карточке только «Изменить» и «Отчёт». Запуск был возможен лишь обращением, к которому в кабинете нет кнопки. Поймано приёмкой 03.08.2026 на кампании #11. Новых дыр кнопка не открывает: путь запуска и так лежал в клиентской группе, все проверки — деньги, минимум площадки, годность аудитории — внутри самого запуска. Три ответа портала показываются по-разному: «готовим картинки» и «аудитория ещё готовится» — спокойным синим сообщением, это нормальный ход, деньги не тронуты и в кабинете ничего не создано; смета мала, не хватает денег, Яндекс молчит — красным. Денежная плашка перечитывается только после удачного запуска. Отдельного «вы уверены?» намеренно нет: кнопка подписана, смета показов стоит тут же на карточке, пауза возвращает заморозку целиком — проверено живьём 03.08 на кампании #6. Вместо окна под кнопкой строчка «При запуске 1 020,60 руб. будут зарезервированы на кошельке»: клиент знает про деньги до нажатия, а не узнаёт потом из кошелька. Сторожа: кнопка есть только у «Готовы к запуску» и нет у работающей и у черновика; удачный запуск перечитывает список и денежную плашку; «аудитория ещё готовится» даёт спокойное сообщение и не тревожит плашку; отказ даёт красное; под кнопкой сказано про резерв сметы. Прогон: 2049 сторожей фронта зелёные, типы Vue чисты, стиль чист. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
f85452a805 |
fix(реклама): период показа ровно на столько дней, сколько заказал клиент
Конец периода считался «сегодня + run_days», а Яндекс берёт StartDate и EndDate включительно. Заказанный один день превращался в период из двух, неделя — из восьми. Следом раздувался минимум площадки, 300 руб. за каждый календарный день: клиенту, заказавшему день, портал требовал 833.40 руб. вместо честных 416.76 руб. Поймано приёмкой 03.08.2026 на кампании #11. Стало: конец периода — «сегодня + run_days минус 1». Сторож держит связь, а не число: «ставит в Директе период ровно на столько дней, сколько заказал клиент» — три длины сразу, 1 день, неделя, месяц; проверяются StartDate и EndDate в самом запросе к Директу. Красный проверен вырезанием наоборот: сообщение Laravel «ожидаемого запроса не было» не говорит, из-за чего именно, поэтому в сторожа временно вписали нынешнее неверное поведение — он позеленел, доказав, что расходится ровно дата конца. После доказательства сторож вернули как надо. Числа трёх прежних сторожей пересчитаны от 300 руб. за день, а не подогнаны под выданное: 6945/833.40 -> 3473/416.76; 27778/3333.36 -> 24306/2916.72; сегодня+14 -> сегодня+13. Комментарии, закреплявшие ошибку словами, переписаны. На кампанию #11 починка подействует — её конец периода ещё не записан. На #6 не подействует и не должна: её shows_until записан 30.07 и переиспользуется. Прогон: 378 сторожей блока рекламы зелёные, статанализ 0 ошибок, стиль чист. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
b04db56290 |
docs(промт смены 18): верный счёт записей на перемере
Промт писался до собственной записи и называл 117 записей впереди main, а стало 118. Число на перемере сверяет следующая смена — расхождение подняло бы ложную тревогу. Заодно названы обе записи смены 17. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
47cc32451d |
docs(приёмка Яндекса): разбор трёх снятых мелочей и промт смене 18
В отчёт приёмки добавлен §6в — перемер мелочей по коду. Три из семи
не подтвердились, и две из них оказались промашками приёмщика,
принятыми за поломку портала:
1. Журнал «Яндекс не дал включить показ» — НЕ мусор, а единственный
машинный след того, что кампания стоит: по нему портал говорит клиенту
«принято, но пока не показывают» вместо вранья «идут показы».
На этом стоит сторож. Убирать нельзя.
2. Красная подпись про размер баннера — по коду снимается сама.
Похоже, описан собственный промах вечера 03.08. Нужен замер глазами.
3. Черновики-пустышки — защита уже стоит, больше одной пустышки
одновременно не накопится. #12 появился законно.
Отсюда правило для чтения отчёта: крупные дефекты подтверждены замерами
и запросами, мелочи — только глазами. Прежде чем чинить мелочь,
проверь её по коду.
Там же отмечено починенное 04.08 (запись
|
||
|
|
75883700c0 |
docs(приёмка): промт смене 17 — довести платный прогон Яндекса
Кампания 11 заведена и ждёт одного действия: сегмент Аудиторий 58389879 создан 03.08 около 18:44 МСК, Яндексу нужно ~15 часов на сведение, значит после ~10:00 МСК 04.08 можно запускать. Запуск заморозит 1 020,60 ₽ и заведёт кампанию в Директе. В промте: - срочное первым: как именно жать «Запустить», раз кнопки у клиента нет, что должно произойти и что замерить сразу после; - перемер числа записей и грабля с часами машины; - доступ к боевому, к виртуалке робота-грузчика и готовый запрос по кошельку, броням и летописи; - что сделано и переделывать не надо: четыре починки кошелька одной записью и отчёт; обе починки живут ТОЛЬКО в ветке, на боевой не выкачены; - состав кампании 11 целиком, включая где лежат картинки; - стены: «документы» обойдена сменой текста (гипотеза проверится завтра), «полный отказ» осталась — Я-М1 недостижим; - порядок работ: запуск, Я-Д1, Я-М2, Я-Д3/Д4/Д6, Я-Ф3, замер 07.08 по кампании 6, починка лишнего дня в периоде, комбинации Я1-Я9; - семь новых дефектов вечера с оценкой важности; - что на боевом трогать нельзя, включая обращение с личными номерами; - правила, церемония записи и решения владельца. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
80c527dd06 |
docs(приёмка Яндекс): вечерний прогон — обход стены и заведение платной кампании
Решение владельца: стену с документами не ломать, а обойти — завести кампанию на обычном тексте Лидерры, без финансовой темы. Сделано вечером 03.08, всё глазами через кабинет. Кампания 11: разовый запуск, свой список 1693 номера (распознано 1693, отброшено 0), аудитория ~1701 человек, частота 5, ~8505 показов, смета 1 020,60 ₽ (потолок поднят владельцем с 1000 ₽). Баннеры 15 из 15, обычный текст Лидерры, палитра Forest. Сегмент Аудиторий 58389879 создан, идёт сведение около 15 часов. Деньги НЕ тронуты: кошелёк 9 991 / 3 333,36, брони под 11 нет. Порядок, которым это далось: первый «Запустить» ставит задание роботу-грузчику; робот залил 15 картинок за минуту; второй «Запустить» создаёт сегмент и честно отвечает «аудитория ещё готовится». Сведение начинается ТОЛЬКО со второго захода, не с отправки заявки — кто этого не знает, теряет сутки. Подтвердилось живьём: размерный сторож баннеров отвергает файл не того размера; робот- грузчик жив и быстр; отказ по минимуму площадки объясняется по-русски и с числом; неудачный запуск денег не трогает. Найдено новое: 1. «Показывать 1 день» превращается в период ДВУХ дней — конец периода ставится «сегодня + срок», а Яндекс считает оба края. Минимум площадки удваивается: 600 ₽ вместо 300 ₽. Клиент заказал день, платит минимум за два. Касается денег каждого. 2. У клиента НЕТ кнопки «Запустить» — кампания висит «Готова к запуску» и ждёт оператора. 3. Про минимум узнаёшь только на запуске — мастер даёт собрать кампанию и залить 15 картинок, и лишь потом отказывает. 4. Счётчик аудитории не пересчитывается после загрузки списка: показывал 15 человек при загруженных 1693. Клиент решит, что список не принялся. 5. Красная подпись про неверный размер остаётся на карточке и после загрузки верного файла. 6. Каждый заход во вкладку «Новая реклама» плодит черновик. 7. Пятнадцать картинок точных размеров — стена для обычного клиента: портал ничего не рисует сам. Дальше: завтра после ~10:00 МСК третий заход «Запустить» — он заморозит смету и заведёт кампанию в Директе. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
edaa0163bd |
docs(приёмка): все находки в отчёт + промт смене 16
Отчёт приёмки Яндекса дополнен до полного. Добавлено:
- Честный счёт по карточкам: пройдено полностью 8, наполовину 6, не тронуто 15,
комбинационных прогонов Я1-Я9 сделано 0 из 9. Ранбук пройден примерно на треть.
- Две стены, о которые разобьётся следующий прогон:
1) «Займы под залог» НЕ дают полного отказа — Яндекс отклонил 2 объявления из 15,
остальные 13 принял. Отклоняет не за тему, а за отсутствие предупреждения на
картинке. Для Я-М1 нужен отказ ВСЕХ, значит нынешний текст не годится.
2) Показов не будет, пока Яндекс не получит документы. Кампания 6 работает пять
суток при 13 принятых объявлениях и нуле показов. Без показов недостижимы
Я-Д3, Я-Д4, Я-Д6 и половина Я-Ф3.
- Мелочи: кампания 6 морозилась трижды, две записи в десяти секундах друг от друга —
похоже на двойной запуск, карточка Я-Л1 не проверена; мастер пускает дальше при
нулевой аудитории; в журнале боевого чужая ошибка прав на миграции СМС от 02.08;
числа для расчёта платного прогона; рекламный кошелёк на бою ровно один;
браузер приёмки рвёт связь и оставляет процесс.
Промт смене 16: как попасть на боевой, обе стены, что уже сделано и не надо
переделывать, порядок работ, числа для расчёта, правила и церемония записи.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
317cf835a2 |
docs(приёмка Яндекс): второй прогон — пауза и возобновление на живых деньгах
Полноценный платный запуск за смену не пройти: Яндекс сводит загруженный список телефонов с людьми около 15 часов. Пауза и возобновление живой кампании проверяют денежный оборот, не потратив ни рубля — показов у неё нет, списывать нечего. Замерено на боевом, кампания 6, кошелёк тенанта 2: - до: заморожено 3333.36, свободно 6657.64, кампания running - пауза: заморожено 0.00, свободно 9991.00, бронь released, кампания paused - возобновление: заморожено 3333.36, свободно 6657.64, бронь active, running Закрыто: Я-Д5 (заморозка снимается целиком и до копейки, списаний нет), Я-Л5 по деньгам (после возобновления бронь ОДНА, не две), Я-Б4. Найдено: денежная плашка на экране врёт в обе стороны. После паузы показывает старые 3333.36 замороженных, после возобновления — что свободно 9991. Значок кампании обновляется правильно, не обновляется именно кошелёк; правда появляется только после перезагрузки. Последствие: клиент считает новую кампанию на несуществующие деньги и получает неожиданный отказ. Ещё: снятие 3333.36 не оставило в летописи ни одной строки. Вместе с заморозкой, которая пишется нулём, выходит, что летопись не фиксирует ни постановку, ни снятие заморозки. Боевое возвращено в исходное состояние, сверено запросом: все три числа и статус совпали с замером до начала. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
9f3be090e4 |
fix(реклама Яндекс): деньги морозим ДО первого обращения к Директу
Приёмка блока Яндекса 03.08.2026 вскрыла неверный порядок в запуске кампании. Заморозка стояла последней — уже после создания в Директе кампании, группы, объявлений, отправки на модерацию и ВКЛЮЧЕНИЯ показа. Не хватило денег: клиент получал отказ, кампания в портале оставалась черновиком, а в Яндексе крутилась включённая реклама, за которую платила Лидерра. Правильный порядок в проекте был известен — у кнопки «Возобновить» деньги резервируются до обращения к Директу, и там об этом написано прямо. В запуске он просто не был применён. Сначала сторож, потом починка. CampaignLauncherTest → «не хватает денег — в Директе ничего не создано»: кошелёк 100 руб, смета 12000 руб. На прежнем коде упал с «записано неожиданное обращение». Держит именно ПОРЯДОК: пока деньги не заморожены, Директ можно только читать — ни одного вызова с методом кроме get. Проверка «деньги не списались» тут бесполезна, она зелёная и при поломке. Новый порядок в CampaignLauncher::run: 1. годность аудитории — может ответить «зайдите позже», деньги ещё не тронуты; 2. сверка настоящих размеров креативов — это чтение, метод get; 3. заморозка денег; 4. и только теперь все записи в Директ. Заодно закрылась вторая беда: сверка размеров стояла после создания кампании и группы, и несовпадение размера оставляло в кабинете кампанию-сироту. Сторож принят вырезанием: старый порядок — красный, починка — зелёный. 362 теста блока рекламы зелёные, статанализ 0 ошибок. Отчёт приёмки с остальными находками — в docs/superpowers/runbooks/. Боевого не касался: кошелёк и кампании остались как были. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
cfd0cdd147 |
docs(приёмка): три ранбука по рекламным блокам + промт смене 15
Разложены на карточки все три рекламных блока — Яндекс, Телеграм, СМС. Метод тот же, что дал результат на приёмке боевого 22.06: карточка уровня C (самодостаточный шаг), комбинации all-pairs, матрица покрытия, фиксация «ожидали / получили». Карточки строились НЕ по памяти: сперва разобран код каждого модуля, поэтому проверки про настоящие деньги и настоящие статусы. Яндекс (27 карточек): смета=заморозка копейка в копейку, списание по факту показов, возврат остатка, наценка не протекает клиенту; отказ модерации возвращает всё, одобрение не списывает вперёд, частичный приём не роняет кампанию; блокировка при нехватке денег останавливает ВСЕ каналы; нарушения логики и обход портала запросом мимо экрана. Телеграм: три звена вместо двух — портал, робот, кабинет МТС. Заморозка на запуске, списание по факту кассы, возврат при отказе и при сбое робота; потолок траты; нечитаемая сумма = отказ платить; сторож сессии; «создать» не равно «запустить». СМС: поштучное списание за каждый отправленный номер, цена по накоплению за месяц; ночная тишина по часу ПОЛУЧАТЕЛЯ и граница суток; три источника номеров порознь (03.08 два из трёх на бою не отправляли вообще); стоп-лист, отписка, имя отправителя, ПДн. Комбинации: по девять прогонов на блок (L9) вместо 81 — каждая пара значений любых двух признаков встречается ровно раз. Недостижимые сочетания помечены честно: пишутся SKIP с причиной, а не выдаются за проверенные. Как добывать исход модерации (решение владельца): отказ — текст про «займы под залог» (отклоняют наверняка), одобрение — обычный текст Лидерры. Промт смене 15: измеренное состояние, порядок блоков (Яндекс первым), что уже сломано или не проверено ни разу, чего на бою нельзя касаться, пути отката, церемония записи. Пароли и учётки в файлы не писать — только в переписке. |
||
|
|
51ca003c9f |
docs+код: голосовая Лена — промт следующей смене и мост в репозитории
Промт смене 3 отменяет часть смены 2: там мозг Claude и одна Лена, здесь GPT-4.1 и две — обзвонщица на 8090, приёмщица на 8091. Мост most.py до сих пор жил в одном экземпляре на сервере, а сервер 03.08 дважды МОЛЧА откатил его к вчерашней версии. Второй откат обесценил целое сравнение мозгов: три «разных мозга» оказались одним Claude. Причина не найдена, поэтому рядом лёг сторож — сверяет слепок раз в 10 секунд и записывает, кто был рядом в момент подмены. Датчик тишины меряет мёртвый воздух по записи, а не по журналу: в журнале пауза считается от решения поставщика «человек договорил», и время на само это решение не видно вообще. Ключа Inworld ни в одном файле нет — мост читает его из ~/lena-inworld/.kluch. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
4c1fd3e92c |
docs,передача: промт смене 14 — состояние после выката кошелька и сторожей
Задач владелец ещё не назначил, поэтому промт — про измеренное состояние и четыре открытых вопроса, каждый из которых требует его решения: отставший боевой «Поиск клиентов», непроверенное живьём чтение вердикта модерации, засветившийся токен робота, копящиеся по две сборки экранов. Записаны уроки смены: состав выката считать командой, а не по списку из промта; причину не объявлять до замера; приёмка на живых данных вскрывает то, чего нет в тестовых; сторожа принимать вырезанием прямо на бою через подмену настройки только в памяти процесса. Разметка чистая, 0 замечаний. |
||
|
|
7327483c61 | Merge branch 'feat/prospects-manual-testing-kp' into fix/tg-zagolovok-obyavleniya | ||
|
|
7979e00474 |
docs,выкат: промт соседней смене на выкат кошелька телеграм-рекламы — сперва робот, потом портал
Владелец решил отдать выкат другой смене, чтобы две смены снова не затёрли работу друг друга на боевом. Главное в промте — ПОРЯДОК, и он не косметический. Портал новый при старом роботе даёт худший исход: запуск заморозит деньги клиента, робот по-старому не заплатит, кампания станет «черновик готов», и заморозка повиснет навсегда — этот статус ничего не отпускает. Обратный порядок терпим: реклама поедет, клиент переплатит как раньше. Поэтому сперва два файла робота на машине владельца, потом портал. Записано то, что нельзя брать по памяти: потолок траты робота BUDGET_CAP_RUB=5000 при балансе кабинета около 5010 ₽ — одна кампания способна съесть почти весь баланс, менять или нет решает владелец. Отдельным разделом — три вопроса к боевой базе ДО выката, все три про деньги живых клиентов: есть ли уже пострадавшие (заплатили по старой модели и получили «черновик готов»), хватит ли клиентам денег в НОВОМ месте (деньги телеграма переехали на рекламный кошелёк, у кого они на общем балансе — запуск откажет), и нет ли кампаний в полёте, которым правила поменяются посреди прогона. Решать за владельца там нечего — только принести ответы. Ловушки собраны из записки соседей и из этой ночи: сверка состава экранов до и после, побайтовая сверка боя с веткой, db/ сверять со снятием \r, миграции только под crm_migrator с паролем из Lockbox по ID, config:cache от www-data, права кэша после распаковки от root, pkill -f по ssh убивает свою же команду. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
3e2b93e65c |
docs,смс: промт смене 13 — модуль не определяет оператора номера
Первая боевая СМС через портал дошла: рассылка 3, сообщение доставлено, списано ровно 9 рублей с кошелька клиента. Та же проба вскрыла дыру: оператор номера не проставляется нигде, кроме лидов от поставщика, поэтому рассылки по своей базе и списком руками на бою не отправят ни одного сообщения. В промте: доказательства дыры с точными местами в коде, объяснение почему её не видят тесты (они проставляют оператора руками), чем чинить — справочник Россвязи на 453 080 записей и сервис, к которому модуль ни разу не обращается. Отдельно записано: - след пробы в боевой базе и обход, который надо снять после починки; - ловушка чтения боевой базы через tinker: защита строк вернула ноль клиентов там, где живут десять; - часы машины и владельца красноярские, ночью я принял их за московские; - порядок выката и сверки после трёх взаимных затираний за сутки. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
5fd6f81cbe |
docs,деньги: промт смене на кошелёк и заморозку телеграм-рекламы — с найденной денежной дырой
Владелец нащупал вопросом, замер подтвердил: клиент платит за РАЗМЕР СПИСКА, а МТС берёт с нас за ФАКТИЧЕСКИЕ показы, и сверки между этим нет вообще. Замерено, а не предположено: - списание считается от числа кандидатов (CampaignController.php:212-217); - счёт кампании 2231134 выставлен за 420 показов, а не за размер списка; - в RunTelegramCampaignJob.php:152 прямая запись, что живое списание по факту отложено «на Сессию 6, когда известен источник фактической цены». Итог: у кого нет телеграма — реклама не покажется, а деньги списаны. Разница остаётся у нас, это переплата клиента. Главный вывод промта: одной заморозки МАЛО. Заморозить можно, но чтобы разморозить лишнее, надо знать факт, а портал числа показов не хранит — это ровно тот пункт, который владелец 02.08 велел отложить как косметику. Сделать заморозку раньше факта значит разморозить ту же неправильную сумму. Отсюда порядок из трёх шагов: сперва научиться узнавать факт, потом кошелёк с заморозкой, потом показать клиенту строкой. Записано, чтобы не переоткрывали: кошелёк убрали НАМЕРЕННО 29.07 (коммит |
||
|
|
3eed377a27 |
docs,голос: промт следующей смене — Лена уже звонит, дальше решает владелец
Самодостаточный промт: смена сможет работать, ничего не помня из этой сессии. Порядок чтения, где стоим по замерам, пять вопросов владельцу с блокирующим первым, шесть работ без владельца, чего не делать, восемь оплаченных грабель, стенд с командами, состояние репозитория, порядок работ. Главное, что отменяет прежний промт: ручки «предел длины ответа» у поставщика НЕТ — она принимается и не применяется. Длину держит только текст наставления. Живого звонка на телефон не было: это деньги и живой человек, ждёт слова владельца. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
0b7453afc7 |
docs,выкат: записка соседней смене — как выкатывать, чтобы не затирать друг друга
За ночь 03.08 две смены дважды затёрли работу друг друга на боевом. Второй раз поломка была невидимой: портал отвечал 200, журнал чист, код и таблицы целы, а экраны СМС просто исчезли из сборки. Записка объясняет соседней смене корень беды и порядок работы: - что уже сделано за них: их запись сведена, боевой починен, обе работы живы; - почему затирание происходит: выкладка несёт весь интерфейс одним куском, выложить «только свои экраны» физически нельзя; - что ветка на сервере gitea не источник правды — серверная копия рабочей ветки отстаёт на 206 записей, а репозиторий у смен общий, забирать надо локально; - готовая команда побайтовой сверки боя с веткой и три ловушки к ней; - что на боевом трогать нельзя: ограничение операторов, боевой .env, роль миграций и права каталога кэша после распаковки. Отдельно отмечено изменение в их правке: мы оба завели одно имя значка, дубль разобран в пользу более точного, спрошено согласие. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |