Commit Graph

1849 Commits

Author SHA1 Message Date
Дмитрий 03e07cafd1 docs: промт смене 23 — забрать четыре прогона и поймать возврат по сроку
Работа смены: снять урожай с четырёх запущенных прогонов приёмки, поймать
последнюю бесплатную карточку Я-Д5 по летописи проводок и провести Я5.

Что несёт промт помимо состояния:

  — пороги, на которых закрываются Я3 и Я4: 1667 и 834 показа, и правило
    мерить скорость последним часом, а не средним за сутки;
  — рычаг управляемого отказа модерации — два конкретных креатива, которые
    Яндекс забраковал дважды подряд; прежнее решение про текст не работает;
  — два способа добиться «впритык» без правки денежной летописи: долив до
    круглой суммы или временная кампания-подпорка;
  — предупреждение, что сторож зависимостей красный из-за недоделанных
    файлов соседней смены и чужое чинить не нужно;
  — четыре ложные тревоги смены 22 одного корня: вывод по косвенному
    признаку вместо вопроса той стороне, которая знает ответ. Главная —
    «робота картинок не устанавливали», а он работал на третьей машине,
    и журнал веб-сервера отвечал на это одной командой;
  — ловушка записи на бою: tinker молча глотает длинные и многострочные
    куски, нужен самостоятельный php-скрипт с загрузкой приложения.

Разметка чисто, 239 строк.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 16:11:11 +03:00
Дмитрий 81f2a77c04 docs,обзвон: приговор по двум кругам владельца и седьмая ошибка надзирателя
Круги З-1.8 и З-1.9 приняты своей рукой, а не по отчётам помощников. Сторожа
резал сам и оба раза другим ножом, чем исполнитель: у запрета двух нулей
покраснели две проверки из шести, у экрана тарифа — ровно одна, та самая, что
стережёт оговорку про цену вслепую. Возврат обоих файлов доказан слепком, а не
памятью. Прогоны своей рукой: сервер 4930 проверок при нуле красных, интерфейс
2095 проверок в 257 файлах при нуле красных.

Вырезание второго рода нашло дыру, которой не видел никто: запрет ловит два нуля
разом, но ноль за минуту при ненулевом соединении пропускает, и тогда час
разговора стоит клиенту два рубля, а оператору за шестьдесят минут платим мы.
Замерено прибором, решение Р90 не переоткрывается — это вопрос владельцу.

Седьмая ошибка надзирателя: в проверки задачи З-1.9 записано «видно, кто и когда
менял цену и с чего на что», а выполнить это нельзя — ручка отдаёт только дату,
ручки чтения журнала админов не существует. Проверка списана с задачи З-1.3, где
речь про запись следа, а не про его показ. Класс тот же, что у первых шести:
утверждение о коде без замера кода. Нашёл снова исполнитель. Починено в бумагах:
проверка переписана на выполнимое, показ истории правок прибавлен в состав З-4.7.

Попутно в приёмочном листе разведены три одинаковых заголовка «Приговор» — они
мешали разметке и были там до этой правки, крючок этот путь не проверяет.
2026-08-05 16:03:37 +03:00
Дмитрий a0bb03efdd З-1.9: экран тарифа обзвона — цену меняет мышка, а не программист
Решение владельца Р91: ручка AdminObzvonTariffController готова с задачи
З-1.3, но экрана к ней не было. Добавлен экран AdminObzvonTariffView.vue
с двумя полями цены, оговоркой про вслепую назначенные цифры и человеческими
текстами отказов, один пункт меню /admin/obzvon-tariff в роутере, 9 проверок
интерфейса в admin-obzvon-tariff-view.spec.ts.

Честно: пункт 5 задания (кто и когда менял цену, с чего на что) закрыт
частично — ручка отдаёт только дату правки, кто менял и старое/новое значение
пишутся в saas_admin_audit_log, но ручки чтения этого журнала для экрана нет
и заводить её было нельзя (правка бэкенда запрещена заданием). Подробности —
в отчёте docs/superpowers/priyomka/stroyka-1/z-1-9-ekran-tarifa-2026-08-05.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 15:52:34 +03:00
Дмитрий 1c2195ebee fix(обзвон): два нуля в тарифе разом запрещены, один ноль можно
Решение владельца Р90 (docs/grilling/2026-08-03-metodika-lena-pod-klienta.md):
цена звонка — два поля, за соединение и за минуту. Оба нуля разом означали
молча бесплатный обзвон: клиент не платит, а оператору за звонки платим мы.
Одна цифра в нуле — осмысленный случай, например не брать отдельно за
соединение.

AdminObzvonTariffController::update() теперь сравнивает обе цены ПОСЛЕ
перевода в копейки и отказывает с объяснением, если обе — ноль. Ловит «0»,
«0.00» и «0.0» одинаково, а не по виду строки. Прежние отказы (пустая,
отрицательная, нечисловая цена) не тронуты.

ObzvonTariffTest.php: +6 проверок «З-1.8». Сторож доказан красным дважды —
до фикса (естественный RED) и после снятия запрета (искусственный RED),
восстановление сверено побайтово через git hash-object.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 15:29:09 +03:00
Дмитрий a9de42ccbc docs(приёмка Яндекса): Я3 и Я4 запущены; поправлена неверная оценка скорости
Владелец: «я3-я4 делай». Обе кампании собраны на минимуме площадки — 1667
показов за двое суток, смета 833,50 руб. Меньше площадка не берёт, а больше
и не нужно: чем меньше смета, тем быстрее она откручивается целиком.

  #19 «Я3 — приняли часть, вся смета» — 13 принятых картинок плюс те две,
      что Яндекс бракует у #6; в Директе 713318844, заперто 833,50 руб;
  #21 «Я4 — впритык, одобрено, половина сметы» — только принятые картинки,
      заперто 833,50 руб.

ПОПРАВКА к записи того же утра, и она важная. Я записал «Яндекс показывает
около одного раза в час, вся смета — полтора месяца, ускорить нечем». Это
ошибка измерения одной точкой. Летопись списаний по #13 показывает не ровную
струйку, а разгон: 09:00 — 6 показов за час, 10:00 — 5, 11:00 — 13, 12:00 —
25. Минимум площадки 834 показа в сутки, значит «вся смета» требует в среднем
35 показов в час, «половина» — 17. На 25 и с ростом это сутки-двое, а не
месяцы. Прежний абзац оставлен зачёркнутым, чтобы урок не потерялся: скорость
рекламной выдачи меряется последним часом, а не средним за сутки.

Как добились «впритык» второй раз: свободных было 6 666,50, а смета нужна
833,50. Доливом остаток только растёт, поэтому лишнее временно заняли
кампанией-подпоркой на 5 833,00 — она ушла на модерацию, то есть показов не
делает и денег не тратит. Свободные стали ровно 833,50, Я4 запустилась при
точном равенстве, подпорку сняли. Условие проверяется в момент запуска,
приём честный. Подпорка останется в кабинете на паузе: удаляются только
черновики.

Д=«Исправить» в обоих прогонах — SKIP, и это не лень: кнопка по построению
работает только у отклонённой кампании. Проверено живьём на #6 (принята
часть) и #13 (принята целиком) — обе ответили отказом 409 с понятным текстом,
статус и баннеры не тронуты, кошелёк не шелохнулся.

Счёт по комбинациям: закрыты 4 из 9, ещё четыре запущены и ждут вердикта.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 15:24:37 +03:00
Дмитрий 1eb56f5b18 docs,обзвон: две работы владельца заведены в план, и шестая ошибка надзирателя названа
Решения Р90 и Р91 стали задачами З-1.8 «два нуля в цене нельзя» и З-1.9 «экран
тарифа». План вырос с 47 задач до 49.

Шестая ошибка надзирателя: промт смене 2 утверждал, что экрана тарифа в плане
нет ни одной задачи. Неверно — он был внутри З-4.7 первой строкой состава.
Утверждение о плане делалось без сверки с планом, тот же класс, что первые пять.
Нашлось при заведении задачи: пошёл смотреть, куда её класть, и увидел, что она
уже там. Тариф вынесен из З-4.7 в раннюю З-1.9, потому что причина владельца
обратная по сроку — цену придётся менять сразу после первых живых звонков, а
З-4.7 стоит в волне 4 за цепочкой из трёх экранов. В З-4.7 оставлена ссылка,
чтобы не построили дважды.

В таблицу узких горл добавлено то, чего в ней не было вовсе: сквозные проверки
перед коммитом — deptrac, статанализ и сторож канона схемы — тоже горло. Они
смотрят на всё хозяйство разом, и незаконченный кусок одного круга рубит коммит
всем. Проверено дорого на стройке первой смены: два круга с разведёнными файлами
заблокировали друг другу коммит. Отсюда правило расстановки: спрашивать не только
про разные файлы, но и про новые классы и таблицы.
2026-08-05 15:16:42 +03:00
Дмитрий 0784384a9e docs,обзвон: в промте смене 2 вершина ветки устарела за сутки
В эту ветку коммитят и чужие смены — вершина ушла на чужой коммит ad4a41fb9,
и по ней больше нельзя судить, что сделано нами. Числа в разделе 2 пересчитаны
командой: список коммитов смены поимённо, третий полный прогон 4924 проверки
при нуле красных, и отдельная оговорка, что вершина признаком нашей работы
не является.

Бумаги плана, спеки и протокола перемеряны — 47 задач, 96 требований,
92 решения, без изменений.
2026-08-05 15:07:13 +03:00
Дмитрий ad4a41fb9d docs(реклама): порог предупреждения о деньгах разобран до конца
Владелец решил поднять порог с 20 до 2000 руб. Разобрано, вывод неудобный.

Настройка не там, где казалось. У кампании свой порог WarningBalance есть, но
Директ отказывает прямым текстом: значение должно быть в диапазоне от 1 до 50.
Больше 50 руб там не поставить в принципе.

Настоящая настройка у общего счёта: MoneyWarningValue = 20, счёт 712890032,
письмо на Sasha261185@yandex.ru. Видно через старую ручку AccountManagement.

Менять её программой нельзя: та же ручка на запись отвечает «Сервис временно
недоступен» во всех трёх формах запроса. Чтение работает, запись закрыта.
В новом API такого поля нет вовсе.

Браузером тоже не зайти: вход робота креативов слетел, профиль перебрасывает
на паспорт. Восстанавливается только руками владельца — пароль и код из СМС.

Значит порог меняется только в кабинете руками. Место записано.

Что программой сделать УДАЛОСЬ: у всех шести наших кампаний было выключено
SendWarnings — письма о событиях кампании, включая отказ модерации, не
приходили вовсе. Включено по решению владельца, проверено чтением обратно.

Записана и ловушка на будущее: портал этот порог при создании кампании не
задаёт вообще, Яндекс ставит свой умолчательный 20 руб.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 15:04:26 +03:00
Дмитрий 957f3d68d1 docs,обзвон: девять решений владельца, приёмочный лист смены и промт смене 2
Первая смена стройки закрыла пять задач денежной волны из семи плюс круг
починки. Бумаги приведены в соответствие с тем, что вышло.

Протокол допроса: 83 решения стало 92. Владелец решил по ходу стройки:
- Р84 на машине робота работать можно, осторожно;
- Р85 тихую недостачу трёх живых каналов чинит отдельная смена;
- Р86 письмо про возврат рекламы не трогаем до починки дороги назад;
- Р87 потолок цены решаем после первых живых звонков;
- Р88 пятый исход звонка не заводим;
- Р89 правку общего файла хуков оставляем;
- Р90 два нуля в цене запрещаем;
- Р91 экран тарифа заводим задачей в план;
- Р92 метки маскирования ПДн отложены.

Владелец поправил бумагу, и поправка настоящая: план говорит, что за звонок
снял трубку и сразу бросил берутся 2 рубля. Проверено прибором - двенадцать.
Два рубля за соединение плюс десять за начатую минуту, минута уже началась.
Ошибка плана, найденная заказчиком.

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

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

Проверено командой: протокол 92 решения и 7 разделов, план 3341 строка и
47 задач, спека 96 требований. Полный прогон своей рукой три раза за смену:
4802, 4860 и 4924 проверки, красных ноль каждый раз.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 14:56:52 +03:00
Дмитрий 49ef80cd55 fix(реклама): возобновление выдавало непроверенную кампанию за крутящуюся
Прогон Я6 приёмочного листа на боевом, с разрешения владельца трогать кошелёк
тенанта 2. Кошелёк только пополняли — из летописи ничего не вынимали.

Подгонка под «впритык»: свободных было 7 422,76 руб, долили 77,24 руб до
круглых 7 500,00 и завели кампанию на 15 000 показов по 500 руб за 1000 =
смета ровно 7 500,00 руб. Деньги отработали безупречно: запуск при точном
равенстве прошёл, заперли ровно смету, свободных осталось ровно ноль и в
минус не ушли; пауза вернула всё целиком, возобновление заперло столько же;
в летописи три честные строки, строка заморозки одна и та же.

А вот статус врал. На паузу можно поставить и кампанию, которую Яндекс ещё
проверяет — это задумано. Но «Возобновить» ставило running БЕЗУСЛОВНО, и
портал показывал зелёное «Крутится» рекламе, которой Яндекс показываться не
разрешал: показов нет и быть не может. Та же болезнь, что уже ловили на
кампании 6. Потери вердикта нет — часовой обход берёт и крутящиеся тоже.

Починка: «Крутится» ставим только когда вердикт вынесен, иначе возвращаем
«на модерации». Мерка «решено или нет» взята ТА ЖЕ, что у часового обхода:
баннер не решён, пока он не ACCEPTED и не REJECTED. Две разные мерки одного
и того же однажды разойдутся.

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

Ловушка сторожа записана в журнал: без заглушки на отчёт о показах пауза
отвечает 200, но деньги намеренно не отпускает.

Счёт по комбинациям: закрыты 4 из 9 — Я6, Я7, Я8, Я9.

Прогоны: 430 тестов рекламы зелёные, pint чисто, статанализ 0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 14:04:07 +03:00
Дмитрий 1ceba0c567 docs(приёмка Яндекса): Я1 и Я2 запущены живьём, ждут вердикт модерации
Владелец разрешил живые запуски. Заведены две кампании тенанта 2:

  #16 «Я1 приёмка — одобрение и ноль показов» — 15 баннеров с принятыми
      креативами #13, в Директе 713315004, заперто 850,00 руб;
  #17 «Я2 приёмка — отказ модерации» — только два баннера, которые Яндекс
      уже отклонял у #6, в Директе 713315067, заперто 850,00 руб.

Обе в pending_moderation, все баннеры в MODERATION. Заморозка сошлась со
сметой копейка в копейку — ещё одно живое подтверждение Я-Д1.

Записан честный вывод: управляемого способа получить отказ модерации у
портала НЕТ. Решение владельца от 03.08 «отказ — текст про займы под залог»
на деле не работает: название кампании до модерации не доезжает. Живое
доказательство — #6 с этим самым названием и обычной страницей liderra.ru:
Яндекс принял 13 баннеров из 15. Отклонённые два — 320x50 и 320x100, причину
Яндекс не назвал, а те же размеры у #13 приняты, значит дело не в размере.
Поэтому #17 собрана из тех самых отклонённых креативов — это ставка на
повтор вердикта, а не управляемый отказ.

Что проверить следующей смене: вердикты по обеим, полный возврат заморозки
при отказе, и остановить обе после замера, чтобы не капало по 0,50 руб в час.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 13:42:18 +03:00
Дмитрий a0761a5c38 fix(реклама): отказ по деньгам говорил клиенту по-английски
Прогон Я7 приёмочного листа на боевом: черновик со сметой 15 000 руб при
свободных 9 122,76 руб. Запуск не состоялся правильно — в Директе не создано
ничего, деньги не тронуты, кампания осталась черновиком. А вот текст отказа
доезжал до клиента как есть:

    Insufficient balance: price_kopecks=1500000, balance_rub=9122.76

Причина: InsufficientBalanceException наследует RuntimeException, и ручка
запуска ловила его общим уловителем, отдавая наружу getMessage. Кнопка
«Возобновить» этой болезнью не болела — там свой уловитель с человеческим
текстом, и он же показал, что сломан именно запуск.

Починка: отдельный уловитель ВЫШЕ общего. Наружу — только клиентские рубли:
сколько нужно и сколько свободно. Ни копеек, ни английского, ни нашей доли.

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

Заодно: срок мобильного прокси продлён до 03.09.2026 — поправлена устаревшая
подсказка в config/services.php. На боевом дата уже заменена, плитка стала
зелёной: «жив, IP 92.36.112.83, до 03.09.2026».

В журнал приёмки записаны прогоны Я7, Я8, Я9 — все три закрыты живьём и
бесплатно — и пересчёт цены комбинаций: деньги уходят только за настоящие
показы, поэтому семь прогонов из девяти стоят ноль. Настоящая стена не
денежная, а временная: сегмент 1653 человека даёт около показа в час, и
«вся смета» это полтора месяца.

Прогоны: 427 тестов рекламы зелёные, pint чисто, статанализ 0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 13:09:35 +03:00
Дмитрий 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
Дмитрий 2c72a0cb6b feat(смс): своё имя есть, но у этой сети не согласовано — номер не уходит вовсе
Решение владельца 05.08.2026. Прежний вариант (подписать нашим «Liderra.ru»)
подменял бренд, за который клиент заплатил, причём незаметно: журнал сообщений
имя отправителя не хранит вовсе. Так уже работает отдел продаж
(SmsRecipientSelector → SKIP_NO_SENDER), и надпись на клиентском экране
(«нет имени отправителя для оператора») давно лежала готовая — её просто никогда
не показывали.

Два случая разведены НАМЕРЕННО и закреплены отдельными тестами:
  • своего имени нет вовсе (или приостановлено) — шлём от имени Лидерры, как
    и раньше. Смешай их — и рассылка встала бы у ВСЕХ разом: на бою своих имён
    заведено НОЛЬ;
  • своё имя есть, но у этой сети не согласовано — номер не уходит.

Отсев в ОТБОРЩИКЕ, а не при отправке: деньги не замораживаются, и причина видна
клиенту в предпросмотре — до оплаты.

🪤 Полный прогон поймал мою ошибку, которую выборочные тесты не видели: имя
спрашивалось ПО КАЖДОМУ номеру — 20 000 запросов в базу, отбор 20 000 получателей
вырос с 20 до 29 секунд и упёрся в сторожа скорости. Добавлена памятка на тенанта
(та же защита, что у AllowedSmsOperators, который прямо об этом предупреждает).
После лечения — 1,47 с.

Два теста часовой давности переписаны: они закрепляли ПРЕЖНЕЕ решение и после
смены решения стали неправдой. Теперь проверяют и «в канал не ушло ничего», и
«деньги целы».

Узкая щель записана в коде: если админ сузит список операторов у имени ПОСЛЕ
создания рассылки, такой номер уже лежит в снимке и уйдёт подписанным нашим именем.
Случай редкий — пересогласование переводит имя в «на рассмотрении», а это снимает
блокировку.

Проверено: 4621 тест, упал один чужой (ExampleTest — в рабочей папке не собран
фронтенд). Статанализ 0. Стиль чист.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 08:21:28 +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
Дмитрий d1e6fcab7f fix(смс): новая запись про адрес звонившего писалась уровнем, который бой выбрасывает
Приёмка на бою вскрыла, что хвост был закрыт только на вид: строку писали
уровнем «к сведению», а на боевом LOG_LEVEL=warning — всё, что ниже, молча
отбрасывается. Запись не появилась бы в журнале НИКОГДА, и круг «список нельзя
заполнить, потому что адреса неоткуда взять» остался бы замкнут — только теперь
незаметно, с виду починенный.

Замерено живым вызовом приёмника на бою 04.08: соседнее предупреждение про
незнакомое сообщение в журнал легло, а строка «к сведению» — нет.

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

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 21:14:30 +03:00
Дмитрий 57c36985a5 fix(сторож ссылок): ссылка на ТЗ лендинга не пускала push из любой отдельной папки
CLAUDE.md §0 ссылается на «лендинг/TZ_landing_v1_0.md», но каталог `лендинг/`
лежит в .gitignore (строка 137) — в git этого файла нет и не будет.

В общем дереве ссылка «работает» только потому, что файл есть там на диске.
В любой отдельной рабочей папке его нет, проверка ссылок краснела и не пускала
push — а сам CLAUDE.md править мимо плагина нельзя (правило §5 п.10).

Исключение узкое: ровно этот файл. Проверено — 38 из 38 ссылок CLAUDE.md
целы, ошибок ноль.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 21:07:56 +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
Дмитрий 13efef2050 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:10 +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
Дмитрий fa4b9e83a4 feat(воронка): колонка rubric у карточки — ниша фирмы 2026-08-04 12:25:09 +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