Commit Graph

1090 Commits

Author SHA1 Message Date
Дмитрий 814c45e8b3 docs(приёмка Яндекса): промт смене 22 — возврат по сроку и что делать дальше
Смена 21 закрыла приёмку Яндекса почти целиком: из 29 карточек пройдены 27.
Остались две, и обе ждут не работы, а события — живого отказа всех объявлений
разом и платных комбинаций Я1-Я9.

В промте: сегодняшний ночной замер возврата заморозки по сроку у #13 (последний
бесплатный, ловится один раз), три находки смены и все четыре записи, живое
состояние кошелька и кампаний, пять решений, которых ждём от владельца, и разбор
того, как работать рядом со второй сменой в одной ветке.

Пять уроков, из которых главный второй: сторож, который НЕ покраснел при
вырезании, — это сигнал искать вторую дорогу, а не повод успокоиться. Именно так
нашлась двойная подписка на остановку рекламы.
2026-08-05 11:03:38 +03:00
Дмитрий 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 - что закрыто, чем
закрыто и чего ждём.
2026-08-05 09:52:10 +03:00
Дмитрий d8cfac6c32 docs(обзвон): промт стройки готов к запуску — чем занять смену, пока ждём разрешения
Промт дописан под сегодняшнее состояние. Числа пересобраны: вершина 90cf01b37,
протокол 83 решения, план 3341 строка.

Главная правка — §4.1. Первые два круга идут на сервере робота, а он делит машину
с рендером боевого портала и с платным Поиском клиентов, поэтому нужен явный
допуск владельца. Раньше промт говорил только это — и смена могла сесть ждать.
Теперь сказано прямо: спросить первым сообщением одной фразой и НЕ ждать сложа
руки. Названа работа, которая сервера не касается вовсе: волна 1 деньги, семь
задач, самый крупный кусок стройки; волна 2 материалы клиента, пять задач;
тумблер видимости, обязанный быть сделанным до того, как экран получит адрес.

Тут же названо, чего в это время делать нельзя: объявлять волны 1 и 2 закрытыми.
Их проверки упираются в перевод звонка, а он на сервере. Написать можно, закрыть
нельзя.

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

Проверено: 379 строк, 8 разделов, разметка чистая живым сторожем.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 09:31:11 +03:00
Дмитрий 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>
2026-08-05 09:19:27 +03:00
Дмитрий 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 дописан утренними итогами: выкат сделан, восемь карточек
пройдены, снята ложная тревога про главную ветку, добавлен разбор чужого слияния.
2026-08-05 08:32:03 +03:00
Дмитрий 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>
2026-08-05 07:48:50 +03:00
Дмитрий 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>
2026-08-05 05:53:33 +03:00
Дмитрий 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>
2026-08-05 05:28:59 +03:00
Дмитрий c50b3dbe1e docs(обзвон): ворота вслепую и суд пройдены — третья редакция плана, 46 задач
Вторую редакцию проверили трое: двое ворот вслепую и судья. Оба проверяющих
работали по спеке, план им был запрещён словами, оба отчитались «план не
открывал» и оба назвали вещи, которых в плане нет. Судья план читал и искал
ошибки надзирателя.

Проверено командой: план 2801 строка / 19 разделов / 46 задач, спека 2126 / 18 /
96 требований, протокол 2150 / 7 / 72 решения. Разметка чистая во всех трёх.
Полный прогон: 4755 из 4760, упавший тест чужой (коммит 1ed82b6b3), в одиночку
файл проходит 10 из 10 — грязь от соседнего теста, не поломка продукта.

Шесть ошибок надзирателя, найденных судьёй, все настоящие:
- план утверждал, что приёмщица живёт в отдельной папке bots/lena-vhodyashchie —
  такой папки нет, это тот же most.py вторым процессом. Заходов в мост стало
  семь, порядок в таблице горл исправлен;
- наставления робота лежат вне репозитория, задачи на это не было — З-0.7;
- Т13, Т23, Т24 не были поручены никому, все три про то, что робот говорит
  вслух — З-4.8;
- своя очередь и живое обновление экрана выброшены целиком — З-6.8;
- проверка дословности каркаса и три проверки манеры проходили при сломанной
  работе — переписаны;
- порядок по узким горлам врал в пяти местах; З-2.4 обещала поле в карточке
  сделки, не назвав ни одного файла экрана.

Улов ворот, внесённый в план и в новый §18 спеки:
- стирание по требованию человека не достаёт до звука и расшифровки: сделка
  стёрта, голос остался — З-2.5;
- безопасный ритм 40-100 наборов в день на номер измерен нами же, Т38 перебивает
  его в разы; счёт попыток не привязан к человеку — до 24 наборов за двое
  суток — З-3.8;
- дорезерв на ходу Т60 не сработает ни разу: заморозка не складывается;
- реклама физически не возвращается — состояние тупиковое, ручка ждёт другого
  состояния; письмо обещает невозможное;
- замкнутый круг: обзвон морозит деньги, списание тушит Директ, тушение отпускает
  бронь рекламы, её доедает обзвон;
- проверка «наружу не ушло ничего» неисполнима — единого журнала исходящих нет;
- своё окно клиента ломает несущее правило часов «живёт здесь и больше нигде»;
- робот не слушается скрипта — измерено, а вся защита проверяется по тексту
  скрипта; проверки переведены на запись разговора;
- этикетка на экране у человека — чужого юрлица ООО Лизинговая Омега;
- трансграничная передача голоса: в 96 требованиях ни слова.

Два решения владельца, принятых по ходу проверки:
- Р71 арендуем двенадцать номеров, не десять — спор Р41 и Р53 закрыт. Замерено:
  у оператора восемь номеров, докупить надо пять, а не двенадцать;
- Р72 у лидов, пришедших через нашу платформу, согласие уже есть — подтверждение
  спрашиваем у двух источников из четырёх.

Замеры смены: номеров у оператора восемь, в телефонию заведено два, наружу
звоним одним; письмо о нехватке денег одно и боевое; на машине робота 13 ГБ
свободно, записей 68 МБ в 89 файлах, чистки нет.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 05:21:43 +03:00
Дмитрий 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>
2026-08-04 21:00:14 +03:00
Дмитрий 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>
2026-08-04 20:15:57 +03:00
Дмитрий 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 всё ещё обещало шесть месяцев звука вместо принятого месяца.

Дыра, вскрытая ответом: минус на счету гасит клиенту весь Директ, а кто
включает рекламу обратно после пополнения — не задано ни одним решением.

Настоящий мобильный номер, попавший в протокол как название проекта, замаскирован.
В истории он остаётся (коммит 06f49ce0d) — вычищать историю ветки без владельца
не стал.

План под новые решения НЕ переписан: в нём 32 задачи с пометками
«заблокировано развилкой», расставленными до ответов. Стройку не начинать.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 22:41:27 +03:00
Дмитрий e5efa5e7b4 docs(приёмка Яндекса): промт смене 20 — проверка починки типа группы
Главное для следующей смены: починка типа группы объявлений выкачена, но живьём
не проверена — Директ не меняет тип после создания, поэтому #6 и #11 доживают
срок с неправильной группой. Проверить можно только новой кампанией, и это
решение владельца, оно стоит денег.

Расписано: как проверять дёшево — сразу после запуска глянуть Subtype группы,
не дожидаясь показов; эталон для сравнения — чужая кампания 713051718 с группой
по профилю, 457 показов за четверо суток при сегменте из 134 человек.

Отдельно записаны три урока смены: тихий ноль опаснее громкой ошибки, Яндекс
молча принимает неверную настройку, и версии надо отвергать по одной за раз.

Хвостов пять, живое состояние сведено таблицей, предупреждения про соседние
смены в той же ветке и на том же боевом, про замороженный GitHub и про пароль
открытым текстом в адресе gitea.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 21:55:04 +03:00
Дмитрий 65f6d06ef2 fix(реклама): создаём группу по профилю пользователей, а не по ключевым фразам
Вопрос владельца «может мы что-то напортачили в настройках?» оказался в точку.

Нацеливание у нас одно — список телефонов, прицепленный отдельным условием
через AudienceTargets на сегмент Яндекс.Аудиторий. Ключевых слов мы не задаём
вовсе. Но группу создавали «по ключевым фразам» и оставляли список слов пустым.

Дока Директа разводит два типа прямо: CpmBannerKeywordsAdGroup — с ключевыми
фразами, CpmBannerUserProfileAdGroup — с условием нацеливания по профилю
пользователей. Сегменты Аудиторий это второй.

Почему поломка себя не выдавала: Яндекс такую группу ПРИНИМАЕТ. Она создаётся,
объявления проходят модерацию, включаются в показ, кампания рапортует «Идут
показы». Ни ошибки, ни отказа — просто показов нет.

Замеры, которыми отвергнуты две другие версии:
- ставка: подняли с 72 до 300 рублей за тысячу, выше рыночных 250, ждали четыре
  часа — как был один показ, так и остался;
- размер списка: у чужой рабочей кампании того же кабинета в основе сегмент на
  134 человека, и она дала 457 показов за четверо суток. У нас 1653 человека и
  один показ.

Сторож переписан и принят красным.

Тип группы после создания не меняется, поэтому живые #6 и #11 так и доживут с
неправильной группой. Проверить починку можно только новой кампанией — это
решение владельца, оно стоит денег.

Прогон: 452 сторожа блока рекламы, статанализ 0, стиль чист. Схема не менялась.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 21:45:55 +03:00
Дмитрий 1987737681 fix(смс): приёмник Теле2 сам называет адрес звонившего — белый список было нечем заполнить
Пока SMS_T2_WEBHOOK_IPS пуст, приёмник никому не отказывает — а значит, и записи
«звонили с такого-то адреса» в журнале не появляется. Круг замкнут: список нельзя
заполнить, потому что адреса неоткуда взять, а взять неоткуда, потому что список пуст.

Теперь при пустом списке каждый принятый звонок пишет строку
client_sms.delivery_hook_open_gate с адресом. Как только список задан — смолкает,
шуметь в журнале постоянно не будет.

Памятка: откуда забрать адреса одной командой; в «Открытом» отмечено, что круг
разорван, и отдельным пунктом записан незакрытый долг про имена отправителей по
каналам.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 20:14:34 +03:00
Дмитрий 61be447b41 fix(реклама): Директ отвергал запрос числа показов, а портал читал отказ как ноль
Самая тяжёлая находка смены, вылезла из вопроса владельца «показано 0, почему?
там один же есть» — из сверки карточки с кабинетом глазами.

Замер на боевом: запрос портала к Директу отвергается с кодом 400 — «В params
отсутствует обязательное поле IncludeVAT». А код ответ не проверял: брал тело,
переводил в число и получал ноль. То есть «показов не было» — не факт, а
проглоченная ошибка.

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

Почему не поймали раньше: ноль выглядит как «показов ещё не было» — законное
состояние новой кампании. Отличить «не было» от «не смогли спросить» портал не
умел. На этом месте с самого начала висел TODO про сверку формата перед боем.

Три правила и три сторожа:
- IncludeVAT в запросе — без него Директ отвергает запрос целиком;
- processingMode auto — иначе отчёт строится в фоне и приходит пустое тело;
- громкая ошибка вместо тихого нуля при не-2xx и нечисловом ответе.

Наверху ошибка уже обработана правильно: обход пишет предупреждение и идёт
дальше, а «Пауза» при такой ошибке не отпускает деньги.

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

Настоящий ноль из Яндекса остаётся нулём — отдельный сторож держит и это.

Прогон: 393 сторожа блока рекламы, статанализ 0, стиль чист. Схема не менялась.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 18:13:25 +03:00
Дмитрий 04657bd6cd feat(воронка): фильтр по нише и на личном экране менеджера
Раньше «Ниша» стояла только на «Воронке отдела». Теперь тот же фильтр есть
и на личном экране менеджера — он обзванивает подряд одну нишу, ему нужнее всех.

Список ниш у менеджера считается по ЕГО карточкам: сужение «только свои»
наложено раньше, поэтому чужая ниша в список не попадает, а ?rubric= не может
стать лазейкой к чужой воронке — на это есть отдельная проверка.

Поведение обоих экранов задаёт один кусок кода, composables/prospectRubricFilter.ts.
Двумя копиями «Без ниши последним» и самосброс исчезнувшей ниши разъехались бы
на первой же правке.

Проверено: Pest по продажам 525 зелёных, Vitest по фронту 2077 зелёных,
vue-tsc и Larastan чисто.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 18:04:07 +03:00
Дмитрий 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>
2026-08-04 18:01:03 +03:00
Дмитрий 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>
2026-08-04 17:59:20 +03:00
Дмитрий 732de3c33b fix(реклама): пауза сперва платит за показанное, потом отпускает деньги
Вопрос владельца вскрыл дыру в деньгах. Списание за показы делает суточная
задача в 04:20 и берёт только кампании со статусом «крутится». Кампанию,
оставленную на паузе, она не видит никогда — значит показы, сделанные с
последнего списания, не оплачивались вовсе. До суток рекламы клиент получал
бесплатно, а Яндексу за неё платили мы.

Дыра открывалась только если после паузы не возобновить: при возобновлении
пропущенное списывается разницей.

Теперь «Пауза» после остановки рекламы спрашивает у Директа накопительное
число показов и списывает за них, и только затем возвращает остаток
заморозки. Лишнего обращения это не стоит — в Директ мы в этот момент и так
ходим останавливать рекламу.

Три случая разведены:
- обычный: списали, поставили паузу, вернули остаток;
- списание само закрыло кампанию по смете или сроку — уходит в «Показы
  откручены» по ВЫХОДУ 1, паузу поверх не ставим, пятого выхода снятия
  заморозки не появляется;
- не узнали число показов — рекламу остановили, но деньги НЕ отпускаем и
  говорим об этом клиенту.

Сторож принят красным: до починки списание давало 0.00 вместо 300.00 рублей
за 2500 показов.

Найдена соседняя дыра того же рода при «Остановлено, нет денег» — там
слушатель работает изнутри денежной транзакции, и обращение к Яндексу внутри
неё держало бы замок строки на время сетевого запроса. Разобрано в отчёте,
чинить отдельным заходом.

Прогон: 387 сторожей блока рекламы, 177 денежных, статанализ 0, стиль чист.
Схема не менялась.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 16:56:15 +03:00
Дмитрий 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>
2026-08-04 16:33:57 +03:00
Дмитрий 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>
2026-08-04 15:57:37 +03:00
Дмитрий 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>
2026-08-04 15:06:01 +03:00
Дмитрий 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>
2026-08-04 14:32:59 +03:00
Дмитрий 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>
2026-08-04 14:20:41 +03:00
Дмитрий 18f3352823 fix(реклама): отказ Директа до заведения кампании возвращает деньги клиенту
Живой случай 04.08.2026 на боевом: Директ отказал по позиции уже ПОСЛЕ
заморозки, и 1 020,60 ₽ клиента повисли запертыми. Кампания осталась
черновиком, а «Паузы», которой снимают заморозку, у черновика нет —
вернуть деньги было нечем.

Заморозка стоит до первого обращения к Директу намеренно: иначе не хватило
бы денег, а в Яндексе крутилась бы включённая реклама за счёт Лидерры. Значит
убирать за неудачей обязан сам запуск.

Граница точная: бронь отпускается, только если в кабинете Яндекса ничего не
завелось (yandex_campaign_id пуст). Есть кампания — деньги остаются заперты,
она способна крутить показы; запуск возобновляемый, клиент дожмёт его
повторным нажатием, а заморозка идемпотентна и второй раз денег не запрёт.

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

Сторожа держат обе стороны границы: «возвращает деньги, если Директ отказал
до того, как кампания заведена» (числа #11 один в один) и «оставляет деньги
запертыми, если кампания в Яндексе уже заведена». Второй был зелёным сразу,
поэтому проверен вырезанием — убрал условие границы, он покраснел.

Разбор — в отчёте приёмки, §6ж.

Прогон: 383 сторожа блока рекламы, статанализ 0, стиль чист. Схема не менялась.
2026-08-04 12:23:03 +03:00
Дмитрий a599621a0d docs(воронка): план работ — ниша на карточке и фильтр по нише
20 задач по TDD, от колонки в базе до приёмки глазами.

Портал: колонка sales_prospects.rubric, ключ сравнения ниш и приклейка к
существующему написанию, приём ниши из поиска и из прогрева, список ниш
текущей выборки, фильтр и «Без ниши», список всех ниш для подсказок,
правка ниши в карточке, сопоставление для разовой проставки, служебная
ручка и команда artisan.

Фронт: плашка ниши на карточке, фильтр между «Менеджер» и «Происхождение»
со сбросом и подписью, поиск похожих ниш, поле «Ниша» с подсказкой.

Служба поиска: передача ниши прогона при «отдать менеджеру» и разовый
проход по всем сохранённым спискам.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 12:18:28 +03:00
Дмитрий e47e6f239b docs(воронка): спека — ниша на карточке и фильтр по нише
Ниша едет из «Поиска клиентов» в карточку воронки: новая колонка
sales_prospects.rubric, приём ниши от службы поиска и из прогрева,
разовая проставка всех текущих карточек по ИНН / сайту / названию+городу.

Фильтр «Ниша» встаёт между «Менеджер» и «Происхождение»; список ниш
считает сервер по текущей выборке — с учётом менеджера, происхождения
и дат, но без учёта самой ниши. Пункт «Без ниши» — только когда такие
карточки есть. Исчезла выбранная ниша — фильтр сбрасывается сам и
говорит почему.

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

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 11:57:17 +03:00
Дмитрий d1728f742d fix(реклама): период показа не короче суток — Яндекс не берёт нулевой
Живой запуск кампании #11 на боевом 04.08.2026 отказал во второй раз:
«Для стратегий с произвольным периодом, период не может быть меньше 1 дня».
Причина — вчерашняя починка «лишнего дня»: у заказа на один день конец
периода совпал с началом, а такой период Яндекс не принимает.

Отсюда следует, что вчерашний разбор был наполовину ошибочным. Заказ
«показывать 1 день» физически занимает у Яндекса два календарных дня,
и минимум площадки на него — 600 ₽, а не 300 ₽. Это пол площадки, а не
завышение портала: вчера вывод был сделан из кода и не проверен живьём.

Стало: конец периода — «сегодня + max(run_days − 1, 1)». Для сроков от двух
дней вчерашняя починка сохраняется, однодневная кампания снова занимает
два дня. Числа двух сторожей однодневного минимума возвращены к 6945
показов / 833,40 ₽, пояснения переписаны — прежние закрепляли ошибку словами.

Сторож переименован: «ставит в Директе период по заказу клиента, но не
короче суток». Красный проверен на однодневной длине, две другие держались.

Разбор выката и обоих живых отказов — в отчёте приёмки, §6ж; §6г помечен
как частично ошибочный со ссылкой туда же.

Прогон: 381 сторож блока рекламы, статанализ 0, стиль чист. Схема не менялась.
2026-08-04 11:35:59 +03:00
Дмитрий 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>
2026-08-04 11:08:18 +03:00
Дмитрий 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 чисты.
Схема не менялась.
2026-08-04 10:40:52 +03:00
Дмитрий d1b4f42183 docs(приёмка Яндекса): промт смене 19 — запуск #11 и остатки находок
Обе крупные находки приёмки закрыты сменой 18: лишний день в периоде и
кнопка «Запустить» у клиента. Промт передаёт дальше запуск кампании #11,
хвосты смены и остатки списка.

Хвосты названы отдельным разделом: запуск #11 не наступил по времени
из-за 15-часового сведения сегмента; предупреждение о минимуме площадки
в мастере разобрано, но не начато; замер красной подписи на 10 минут
не сделан; пять починок лежат только в ветке и на бою не работают.

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

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 09:09:25 +03:00
Дмитрий 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>
2026-08-04 09:04:27 +03:00
Дмитрий 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>
2026-08-04 08:44:38 +03:00
Дмитрий b04db56290 docs(промт смены 18): верный счёт записей на перемере
Промт писался до собственной записи и называл 117 записей впереди main,
а стало 118. Число на перемере сверяет следующая смена — расхождение
подняло бы ложную тревогу. Заодно названы обе записи смены 17.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 00:11:40 +03:00
Дмитрий 47cc32451d docs(приёмка Яндекса): разбор трёх снятых мелочей и промт смене 18
В отчёт приёмки добавлен §6в — перемер мелочей по коду. Три из семи
не подтвердились, и две из них оказались промашками приёмщика,
принятыми за поломку портала:

1. Журнал «Яндекс не дал включить показ» — НЕ мусор, а единственный
   машинный след того, что кампания стоит: по нему портал говорит клиенту
   «принято, но пока не показывают» вместо вранья «идут показы».
   На этом стоит сторож. Убирать нельзя.
2. Красная подпись про размер баннера — по коду снимается сама.
   Похоже, описан собственный промах вечера 03.08. Нужен замер глазами.
3. Черновики-пустышки — защита уже стоит, больше одной пустышки
   одновременно не накопится. #12 появился законно.

Отсюда правило для чтения отчёта: крупные дефекты подтверждены замерами
и запросами, мелочи — только глазами. Прежде чем чинить мелочь,
проверь её по коду.

Там же отмечено починенное 04.08 (запись 1908f841).

Новый файл — промт смене 18: запуск кампании #11 (не сделан, сегмент
сведён, жать после 10:00 МСК), затем два крупных дефекта — отсутствие
у клиента кнопки «Запустить» и лишний день в периоде, удваивающий
минимум площадки. У обоих расписаны замер, чем плохо и чем чинить,
с ловушками про существующий сторож на числах 6945/833.40 и про уже
записанный shows_until у #6 и #11.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 00:10:09 +03:00
Дмитрий 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>
2026-08-03 22:55:16 +03:00
Дмитрий 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>
2026-08-03 22:48:31 +03:00
Дмитрий 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>
2026-08-03 20:01:48 +03:00
Дмитрий 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>
2026-08-03 19:51:16 +03:00
Дмитрий 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>
2026-08-03 19:42:47 +03:00
Дмитрий cfd0cdd147 docs(приёмка): три ранбука по рекламным блокам + промт смене 15
Разложены на карточки все три рекламных блока — Яндекс, Телеграм, СМС. Метод тот же,
что дал результат на приёмке боевого 22.06: карточка уровня C (самодостаточный шаг),
комбинации all-pairs, матрица покрытия, фиксация «ожидали / получили».

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

Яндекс (27 карточек): смета=заморозка копейка в копейку, списание по факту показов,
возврат остатка, наценка не протекает клиенту; отказ модерации возвращает всё, одобрение
не списывает вперёд, частичный приём не роняет кампанию; блокировка при нехватке денег
останавливает ВСЕ каналы; нарушения логики и обход портала запросом мимо экрана.

Телеграм: три звена вместо двух — портал, робот, кабинет МТС. Заморозка на запуске,
списание по факту кассы, возврат при отказе и при сбое робота; потолок траты; нечитаемая
сумма = отказ платить; сторож сессии; «создать» не равно «запустить».

СМС: поштучное списание за каждый отправленный номер, цена по накоплению за месяц;
ночная тишина по часу ПОЛУЧАТЕЛЯ и граница суток; три источника номеров порознь (03.08
два из трёх на бою не отправляли вообще); стоп-лист, отписка, имя отправителя, ПДн.

Комбинации: по девять прогонов на блок (L9) вместо 81 — каждая пара значений любых двух
признаков встречается ровно раз. Недостижимые сочетания помечены честно: пишутся SKIP с
причиной, а не выдаются за проверенные.

Как добывать исход модерации (решение владельца): отказ — текст про «займы под залог»
(отклоняют наверняка), одобрение — обычный текст Лидерры.

Промт смене 15: измеренное состояние, порядок блоков (Яндекс первым), что уже сломано
или не проверено ни разу, чего на бою нельзя касаться, пути отката, церемония записи.
Пароли и учётки в файлы не писать — только в переписке.
2026-08-03 18:11:39 +03:00
Дмитрий 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>
2026-08-03 17:06:52 +03:00
Дмитрий 4c1fd3e92c docs,передача: промт смене 14 — состояние после выката кошелька и сторожей
Задач владелец ещё не назначил, поэтому промт — про измеренное состояние и
четыре открытых вопроса, каждый из которых требует его решения: отставший
боевой «Поиск клиентов», непроверенное живьём чтение вердикта модерации,
засветившийся токен робота, копящиеся по две сборки экранов.

Записаны уроки смены: состав выката считать командой, а не по списку из
промта; причину не объявлять до замера; приёмка на живых данных вскрывает то,
чего нет в тестовых; сторожа принимать вырезанием прямо на бою через подмену
настройки только в памяти процесса.

Разметка чистая, 0 замечаний.
2026-08-03 15:11:05 +03:00
Дмитрий 7327483c61 Merge branch 'feat/prospects-manual-testing-kp' into fix/tg-zagolovok-obyavleniya 2026-08-03 13:00:29 +03:00
Дмитрий 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>
2026-08-03 12:56:38 +03:00
Дмитрий 3e2b93e65c docs,смс: промт смене 13 — модуль не определяет оператора номера
Первая боевая СМС через портал дошла: рассылка 3, сообщение доставлено, списано
ровно 9 рублей с кошелька клиента. Та же проба вскрыла дыру: оператор номера не
проставляется нигде, кроме лидов от поставщика, поэтому рассылки по своей базе
и списком руками на бою не отправят ни одного сообщения.

В промте: доказательства дыры с точными местами в коде, объяснение почему её не
видят тесты (они проставляют оператора руками), чем чинить — справочник Россвязи
на 453 080 записей и сервис, к которому модуль ни разу не обращается.

Отдельно записано:
- след пробы в боевой базе и обход, который надо снять после починки;
- ловушка чтения боевой базы через tinker: защита строк вернула ноль клиентов
  там, где живут десять;
- часы машины и владельца красноярские, ночью я принял их за московские;
- порядок выката и сверки после трёх взаимных затираний за сутки.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 10:53:34 +03:00
Дмитрий 5fd6f81cbe docs,деньги: промт смене на кошелёк и заморозку телеграм-рекламы — с найденной денежной дырой
Владелец нащупал вопросом, замер подтвердил: клиент платит за РАЗМЕР СПИСКА, а МТС
берёт с нас за ФАКТИЧЕСКИЕ показы, и сверки между этим нет вообще.

Замерено, а не предположено:
- списание считается от числа кандидатов (CampaignController.php:212-217);
- счёт кампании 2231134 выставлен за 420 показов, а не за размер списка;
- в RunTelegramCampaignJob.php:152 прямая запись, что живое списание по факту
  отложено «на Сессию 6, когда известен источник фактической цены».

Итог: у кого нет телеграма — реклама не покажется, а деньги списаны. Разница
остаётся у нас, это переплата клиента.

Главный вывод промта: одной заморозки МАЛО. Заморозить можно, но чтобы
разморозить лишнее, надо знать факт, а портал числа показов не хранит — это ровно
тот пункт, который владелец 02.08 велел отложить как косметику. Сделать заморозку
раньше факта значит разморозить ту же неправильную сумму. Отсюда порядок из трёх
шагов: сперва научиться узнавать факт, потом кошелёк с заморозкой, потом показать
клиенту строкой.

Записано, чтобы не переоткрывали: кошелёк убрали НАМЕРЕННО 29.07 (коммит
6f469fb13, таблицы ad_wallets целы), возврат по сальдо проводок сделан правильно и
его надо сохранить, Яндекс живёт на кошельке с заморозкой.

Состояние на бою и в ветке — замерами: вершина 0b7453afc содержит обе работы,
статанализ 0, экраны 251/2033, телеграм+СМС 714/714. На бою разъехались половинки:
сборка из объединённого дерева, серверный код нет, расходятся ровно 5 файлов, и
CampaignController.php ломает показ цен. Выкат владелец решил отдать соседней смене.

Ловушки собраны из этой ночи: сверять состав экранов ДО и ПОСЛЕ выкладки (при
первом выкате проверка была, при втором нет — с боя исчезли экраны СМС, и снаружи
всё выглядело здоровым), миграции только под crm_migrator с паролем из Lockbox по
ID, старые пароли в liderra-secrets.txt от прежней базы, сведение приносит новые
зависимости, цену в текст экрана не вбивать.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 10:44:11 +03:00
Дмитрий 3eed377a27 docs,голос: промт следующей смене — Лена уже звонит, дальше решает владелец
Самодостаточный промт: смена сможет работать, ничего не помня из этой сессии.

Порядок чтения, где стоим по замерам, пять вопросов владельцу с блокирующим
первым, шесть работ без владельца, чего не делать, восемь оплаченных грабель,
стенд с командами, состояние репозитория, порядок работ.

Главное, что отменяет прежний промт: ручки «предел длины ответа» у поставщика
НЕТ — она принимается и не применяется. Длину держит только текст наставления.

Живого звонка на телефон не было: это деньги и живой человек, ждёт слова
владельца.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 09:51:15 +03:00
Дмитрий 0b7453afc7 docs,выкат: записка соседней смене — как выкатывать, чтобы не затирать друг друга
За ночь 03.08 две смены дважды затёрли работу друг друга на боевом. Второй раз
поломка была невидимой: портал отвечал 200, журнал чист, код и таблицы целы,
а экраны СМС просто исчезли из сборки.

Записка объясняет соседней смене корень беды и порядок работы:

- что уже сделано за них: их запись сведена, боевой починен, обе работы живы;
- почему затирание происходит: выкладка несёт весь интерфейс одним куском,
  выложить «только свои экраны» физически нельзя;
- что ветка на сервере gitea не источник правды — серверная копия рабочей ветки
  отстаёт на 206 записей, а репозиторий у смен общий, забирать надо локально;
- готовая команда побайтовой сверки боя с веткой и три ловушки к ней;
- что на боевом трогать нельзя: ограничение операторов, боевой .env, роль
  миграций и права каталога кэша после распаковки.

Отдельно отмечено изменение в их правке: мы оба завели одно имя значка, дубль
разобран в пользу более точного, спрошено согласие.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 08:56:21 +03:00