Канал «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>
План фичи «Обзвон» — 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>
Владелец, глядя на две живые карточки: «статус на модерации — не понятно,
идут показы или ожидает! и принято, когда реально отклонили».
Корень: ярлык один на всю кампанию, а объявления внутри в разных состояниях,
и у объявления два независимых признака — прошло проверку и показывается.
Карточка сваливала их в одно слово.
Три починки:
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>
Живой случай 04.08.2026 на боевом: Директ отказал по позиции уже ПОСЛЕ
заморозки, и 1 020,60 ₽ клиента повисли запертыми. Кампания осталась
черновиком, а «Паузы», которой снимают заморозку, у черновика нет —
вернуть деньги было нечем.
Заморозка стоит до первого обращения к Директу намеренно: иначе не хватило
бы денег, а в Яндексе крутилась бы включённая реклама за счёт Лидерры. Значит
убирать за неудачей обязан сам запуск.
Граница точная: бронь отпускается, только если в кабинете Яндекса ничего не
завелось (yandex_campaign_id пуст). Есть кампания — деньги остаются заперты,
она способна крутить показы; запуск возобновляемый, клиент дожмёт его
повторным нажатием, а заморозка идемпотентна и второй раз денег не запрёт.
Сделано обёрткой над запуском, тело не переливалось. Порядок уборки верный:
сначала возвращается прежний статус кампании, потом деньги.
Сторожа держат обе стороны границы: «возвращает деньги, если Директ отказал
до того, как кампания заведена» (числа #11 один в один) и «оставляет деньги
запертыми, если кампания в Яндексе уже заведена». Второй был зелёным сразу,
поэтому проверен вырезанием — убрал условие границы, он покраснел.
Разбор — в отчёте приёмки, §6ж.
Прогон: 383 сторожа блока рекламы, статанализ 0, стиль чист. Схема не менялась.
20 задач по TDD, от колонки в базе до приёмки глазами.
Портал: колонка sales_prospects.rubric, ключ сравнения ниш и приклейка к
существующему написанию, приём ниши из поиска и из прогрева, список ниш
текущей выборки, фильтр и «Без ниши», список всех ниш для подсказок,
правка ниши в карточке, сопоставление для разовой проставки, служебная
ручка и команда artisan.
Фронт: плашка ниши на карточке, фильтр между «Менеджер» и «Происхождение»
со сбросом и подписью, поиск похожих ниш, поле «Ниша» с подсказкой.
Служба поиска: передача ниши прогона при «отдать менеджеру» и разовый
проход по всем сохранённым спискам.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Ниша едет из «Поиска клиентов» в карточку воронки: новая колонка
sales_prospects.rubric, приём ниши от службы поиска и из прогрева,
разовая проставка всех текущих карточек по ИНН / сайту / названию+городу.
Фильтр «Ниша» встаёт между «Менеджер» и «Происхождение»; список ниш
считает сервер по текущей выборке — с учётом менеджера, происхождения
и дат, но без учёта самой ниши. Пункт «Без ниши» — только когда такие
карточки есть. Исчезла выбранная ниша — фильтр сбрасывается сам и
говорит почему.
Своим карточкам нишу вписывают руками: подсказка по уже существующим,
опечатки и другой регистр приклеиваются к существующей молча, похожее
название подсказывается, но сохранить своё разрешено.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Живой запуск кампании #11 на боевом 04.08.2026 отказал во второй раз:
«Для стратегий с произвольным периодом, период не может быть меньше 1 дня».
Причина — вчерашняя починка «лишнего дня»: у заказа на один день конец
периода совпал с началом, а такой период Яндекс не принимает.
Отсюда следует, что вчерашний разбор был наполовину ошибочным. Заказ
«показывать 1 день» физически занимает у Яндекса два календарных дня,
и минимум площадки на него — 600 ₽, а не 300 ₽. Это пол площадки, а не
завышение портала: вчера вывод был сделан из кода и не проверен живьём.
Стало: конец периода — «сегодня + max(run_days − 1, 1)». Для сроков от двух
дней вчерашняя починка сохраняется, однодневная кампания снова занимает
два дня. Числа двух сторожей однодневного минимума возвращены к 6945
показов / 833,40 ₽, пояснения переписаны — прежние закрепляли ошибку словами.
Сторож переименован: «ставит в Директе период по заказу клиента, но не
короче суток». Красный проверен на однодневной длине, две другие держались.
Разбор выката и обоих живых отказов — в отчёте приёмки, §6ж; §6г помечен
как частично ошибочный со ссылкой туда же.
Прогон: 381 сторож блока рекламы, статанализ 0, стиль чист. Схема не менялась.
Дыра: 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>
Живой запуск кампании #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 чисты.
Схема не менялась.
Обе крупные находки приёмки закрыты сменой 18: лишний день в периоде и
кнопка «Запустить» у клиента. Промт передаёт дальше запуск кампании #11,
хвосты смены и остатки списка.
Хвосты названы отдельным разделом: запуск #11 не наступил по времени
из-за 15-часового сведения сегмента; предупреждение о минимуме площадки
в мастере разобрано, но не начато; замер красной подписи на 10 минут
не сделан; пять починок лежат только в ветке и на бою не работают.
Записаны уроки смены: общее сообщение о падении не доказывает причину —
впиши в сторожа нынешнее поведение и убедись; числа в чужих сторожах
пересчитывать от исходных величин, а не подгонять под выданное, и править
их пояснения; путь к файлу с сообщением коммита — два уровня вверх от
корня репозитория.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Кампания в состоянии «Готова к запуску» не двигалась ничем — ни человеком,
ни расписанием. Клиент собирал рекламу, заливал пятнадцать картинок,
отправлял заявку и упирался в тупик: на карточке только «Изменить» и
«Отчёт». Запуск был возможен лишь обращением, к которому в кабинете нет
кнопки. Поймано приёмкой 03.08.2026 на кампании #11.
Новых дыр кнопка не открывает: путь запуска и так лежал в клиентской группе,
все проверки — деньги, минимум площадки, годность аудитории — внутри самого
запуска.
Три ответа портала показываются по-разному: «готовим картинки» и «аудитория
ещё готовится» — спокойным синим сообщением, это нормальный ход, деньги не
тронуты и в кабинете ничего не создано; смета мала, не хватает денег, Яндекс
молчит — красным. Денежная плашка перечитывается только после удачного
запуска.
Отдельного «вы уверены?» намеренно нет: кнопка подписана, смета показов
стоит тут же на карточке, пауза возвращает заморозку целиком — проверено
живьём 03.08 на кампании #6. Вместо окна под кнопкой строчка «При запуске
1 020,60 руб. будут зарезервированы на кошельке»: клиент знает про деньги
до нажатия, а не узнаёт потом из кошелька.
Сторожа: кнопка есть только у «Готовы к запуску» и нет у работающей и у
черновика; удачный запуск перечитывает список и денежную плашку; «аудитория
ещё готовится» даёт спокойное сообщение и не тревожит плашку; отказ даёт
красное; под кнопкой сказано про резерв сметы.
Прогон: 2049 сторожей фронта зелёные, типы Vue чисты, стиль чист.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Конец периода считался «сегодня + 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>
Промт писался до собственной записи и называл 117 записей впереди main,
а стало 118. Число на перемере сверяет следующая смена — расхождение
подняло бы ложную тревогу. Заодно названы обе записи смены 17.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
В отчёт приёмки добавлен §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>
Кампания 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>
Решение владельца: стену с документами не ломать, а обойти — завести кампанию на обычном
тексте Лидерры, без финансовой темы. Сделано вечером 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>
Отчёт приёмки Яндекса дополнен до полного. Добавлено:
- Честный счёт по карточкам: пройдено полностью 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>
Полноценный платный запуск за смену не пройти: Яндекс сводит загруженный список
телефонов с людьми около 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>
Приёмка блока Яндекса 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>
Разложены на карточки все три рекламных блока — Яндекс, Телеграм, СМС. Метод тот же,
что дал результат на приёмке боевого 22.06: карточка уровня C (самодостаточный шаг),
комбинации all-pairs, матрица покрытия, фиксация «ожидали / получили».
Карточки строились НЕ по памяти: сперва разобран код каждого модуля, поэтому проверки
про настоящие деньги и настоящие статусы.
Яндекс (27 карточек): смета=заморозка копейка в копейку, списание по факту показов,
возврат остатка, наценка не протекает клиенту; отказ модерации возвращает всё, одобрение
не списывает вперёд, частичный приём не роняет кампанию; блокировка при нехватке денег
останавливает ВСЕ каналы; нарушения логики и обход портала запросом мимо экрана.
Телеграм: три звена вместо двух — портал, робот, кабинет МТС. Заморозка на запуске,
списание по факту кассы, возврат при отказе и при сбое робота; потолок траты; нечитаемая
сумма = отказ платить; сторож сессии; «создать» не равно «запустить».
СМС: поштучное списание за каждый отправленный номер, цена по накоплению за месяц;
ночная тишина по часу ПОЛУЧАТЕЛЯ и граница суток; три источника номеров порознь (03.08
два из трёх на бою не отправляли вообще); стоп-лист, отписка, имя отправителя, ПДн.
Комбинации: по девять прогонов на блок (L9) вместо 81 — каждая пара значений любых двух
признаков встречается ровно раз. Недостижимые сочетания помечены честно: пишутся SKIP с
причиной, а не выдаются за проверенные.
Как добывать исход модерации (решение владельца): отказ — текст про «займы под залог»
(отклоняют наверняка), одобрение — обычный текст Лидерры.
Промт смене 15: измеренное состояние, порядок блоков (Яндекс первым), что уже сломано
или не проверено ни разу, чего на бою нельзя касаться, пути отката, церемония записи.
Пароли и учётки в файлы не писать — только в переписке.
Промт смене 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>
Задач владелец ещё не назначил, поэтому промт — про измеренное состояние и
четыре открытых вопроса, каждый из которых требует его решения: отставший
боевой «Поиск клиентов», непроверенное живьём чтение вердикта модерации,
засветившийся токен робота, копящиеся по две сборки экранов.
Записаны уроки смены: состав выката считать командой, а не по списку из
промта; причину не объявлять до замера; приёмка на живых данных вскрывает то,
чего нет в тестовых; сторожа принимать вырезанием прямо на бою через подмену
настройки только в памяти процесса.
Разметка чистая, 0 замечаний.
Кошелёк и заморозка телеграм-рекламы выкачены на боевой из ветки
fix/tg-zagolovok-obyavleniya. Пока работа жила только там, рабочая ветка
расходилась с боевым на 29 файлов, и следующая сверка приняла бы это за
чужое затирание. Теперь в рабочей ветке есть всё, что стоит на бою.
Сведение без склеек. Проверки на объединённом дереве: статанализ 0,
телеграм плюс СМС 732 из 732, экраны 251 файл и 2034 теста, робот 160 из 160.
🪤 Статанализ сперва упал одной ошибкой на новой колонке mts_cost_rub, и
причина была не в коде: файл подсказок типов _ide_helper_models.php лежит вне
git, у каждой рабочей папки свой, и в этой он про колонку не знал, потому что
местная база разработки отставала на три миграции. Лечение: догнать местную
базу и пересобрать подсказки ключом -M -n. Собственный сторож проекта поймал,
что я пересобрал их неверным ключом, — без него анализатор упал бы молча.
Владелец решил отдать выкат другой смене, чтобы две смены снова не затёрли работу
друг друга на боевом.
Главное в промте — ПОРЯДОК, и он не косметический. Портал новый при старом роботе
даёт худший исход: запуск заморозит деньги клиента, робот по-старому не заплатит,
кампания станет «черновик готов», и заморозка повиснет навсегда — этот статус
ничего не отпускает. Обратный порядок терпим: реклама поедет, клиент переплатит
как раньше. Поэтому сперва два файла робота на машине владельца, потом портал.
Записано то, что нельзя брать по памяти: потолок траты робота 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>
Первая боевая СМС через портал дошла: рассылка 3, сообщение доставлено, списано
ровно 9 рублей с кошелька клиента. Та же проба вскрыла дыру: оператор номера не
проставляется нигде, кроме лидов от поставщика, поэтому рассылки по своей базе
и списком руками на бою не отправят ни одного сообщения.
В промте: доказательства дыры с точными местами в коде, объяснение почему её не
видят тесты (они проставляют оператора руками), чем чинить — справочник Россвязи
на 453 080 записей и сервис, к которому модуль ни разу не обращается.
Отдельно записано:
- след пробы в боевой базе и обход, который надо снять после починки;
- ловушка чтения боевой базы через tinker: защита строк вернула ноль клиентов
там, где живут десять;
- часы машины и владельца красноярские, ночью я принял их за московские;
- порядок выката и сверки после трёх взаимных затираний за сутки.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Владелец нащупал вопросом, замер подтвердил: клиент платит за РАЗМЕР СПИСКА, а МТС
берёт с нас за ФАКТИЧЕСКИЕ показы, и сверки между этим нет вообще.
Замерено, а не предположено:
- списание считается от числа кандидатов (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>
Самодостаточный промт: смена сможет работать, ничего не помня из этой сессии.
Порядок чтения, где стоим по замерам, пять вопросов владельцу с блокирующим
первым, шесть работ без владельца, чего не делать, восемь оплаченных грабель,
стенд с командами, состояние репозитория, порядок работ.
Главное, что отменяет прежний промт: ручки «предел длины ответа» у поставщика
НЕТ — она принимается и не применяется. Длину держит только текст наставления.
Живого звонка на телефон не было: это деньги и живой человек, ждёт слова
владельца.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
За ночь 03.08 две смены дважды затёрли работу друг друга на боевом. Второй раз
поломка была невидимой: портал отвечал 200, журнал чист, код и таблицы целы,
а экраны СМС просто исчезли из сборки.
Записка объясняет соседней смене корень беды и порядок работы:
- что уже сделано за них: их запись сведена, боевой починен, обе работы живы;
- почему затирание происходит: выкладка несёт весь интерфейс одним куском,
выложить «только свои экраны» физически нельзя;
- что ветка на сервере gitea не источник правды — серверная копия рабочей ветки
отстаёт на 206 записей, а репозиторий у смен общий, забирать надо локально;
- готовая команда побайтовой сверки боя с веткой и три ловушки к ней;
- что на боевом трогать нельзя: ограничение операторов, боевой .env, роль
миграций и права каталога кэша после распаковки.
Отдельно отмечено изменение в их правке: мы оба завели одно имя значка, дубль
разобран в пользу более точного, спрошено согласие.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
На сервере рендера построен мост между телефонией и Inworld: человек говорит
в трубку, Asterisk отдаёт звук мосту, мост разговаривает с Леной и возвращает
её речь обратно. Служба поднимается сама, в том числе после перезагрузки.
Дашина Лена НЕ ТРОНУТА — её вход цел, обе линии Манго зарегистрированы,
существующее не переписывалось, только дописано рядом.
Замерено:
- задержка ответа с самого сервера 0,95–1,26 с (с домашней машины было 1,3–1,5);
- длина реплики срезана вдвое: 23,3 с → 11,8 с;
- звук идёт A-law 8000 Гц в обе стороны — совпадает с Манго, не пережимается.
Два вывода, отменяющих вчерашние:
- ручки «предел длины ответа» у поставщика НЕТ. Она принимается и не
применяется: при потолке в 16 токенов ответ вышел 1144 знака целым текстом.
Вчерашний промт называл её «главным делом смены» — исправлено;
- «сильное правило лучше слабого» не подтвердилось: 232 против 236 знаков
при перехлёсте разбросов трёх прогонов.
Мина в базе знаний («откуда мой номер») сработала в каждом прогоне с этим
вопросом, включая живой голосовой разговор. Чинить надо в самой базе —
решение владельца, чужой текст не переписывал.
Живого звонка на телефон не было: это деньги и живой человек, ждёт слова
владельца. Команда готова, голос пока не выбран.
Ключей в файлах нет — проверено датчиком с контролем.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
За два часа ночи 03.08 две смены дважды затёрли работу друг друга на боевом.
Второй раз — невидимо: портал отвечал 200, журнал был чист, код и таблицы целы,
а экраны СМС просто исчезли из сборки, потому что соседняя выкладка несёт весь
фронт одним куском.
В промт добавлен раздел с готовой командой побайтовой сверки боя с веткой через
git hash-object, оговоркой про переводы строк в db и правилом отличать мёртвые
хвосты прошлых выкатов от чужой работы мимо git.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Дописано в handoff по Inworld:
- §17 два мозга: пересадка посреди разговора 0,2 с, у каждого мозга свой кэш,
он переживает возврат; экономия ~1 ₽ за звонок, смысл только в скорости.
- §18 филлер: разобран целиком (порог, язык, дыры, обрывки) и ОТКЛОНЁН владельцем.
Дыр между филлером и ответом нет — худшая 0,02 с. Обрывки речи ловятся
регуляркой строчная+заглавная, датчик пригодится и без филлера.
- §19 раздумья: вопрос закрыт текстом отказа поставщика — мозг Anthropic
думает только при температуре 1, у нас 0.45. Раздумья выключены физически.
- §20 ручка «когда начинать говорить»: не задавалась ни разу за все прогоны,
трогать только после укорачивания реплик.
- §21 обрывы соединения: три раза за день, только длинные звуковые разговоры.
Открытый риск, до боевого не выходить.
Поправлен файл ручек: убрана неправда про память как лекарство от предела
в байтах (замер показал обратное), раздел про раздумья и филлер переписан.
Новый файл — промт следующей смене: с чего начать, что доказано замером,
пять вопросов владельцу, чего не делать и шесть грабель этой смены.
Ключей в файлах нет — проверено датчиком с контролем.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Выкат состоялся в ночь на 03.08 с разрешения владельца: код, 36 миграций,
правила доступа, сборка интерфейса, перезапуск служб. Порталы отвечают 200,
журнал чист, службы активны.
ПИЛОТ.md — новая запись «03.08.2026 (ночь)»: что применено, чем проверено,
куда откатываться. Отдельно записаны две мины, которые стоили времени и
повторятся при следующем выкате:
- штатная роль crm_app_user не имеет права создавать таблицы, миграции идут
только под crm_migrator, и пароль в Lockbox берётся по номеру секрета,
а не по имени — с именем команда молча отдаёт пустоту;
- распаковка посылки от root сменила владельца каталога кэша, из-за чего
сброс кэша настроек молча не сработал и переопределение роли не действовало
вовсе. Датчик — спросить у базы, кто подключился, тем же путём, каким
пойдут миграции.
Главное, что выкат едва не сломал: на бою работал код второй смены, которого
не было ни в общей ветке, ни в нашей. Экраны собираются одним куском, поэтому
выкладка откатила бы их рекламу назад. Сведено заранее, после выката слепки
их файлов на бою те же, что были до него.
Ограничение операторов на бою поставлено по решению владельца: рассылки берут
только абонентов МТС, потому что имя отправителя подключено лишь на одного
оператора. Проверено вырезанием.
Промт переписан под смену 12: осталась одна работа — живой платный прогон
через сам портал после 10 утра, номер даёт владелец в момент прогона.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Замер боевого перед выкатом СМС-модуля показал мину: на liderra.ru сейчас
работает код ветки fix/tg-zagolovok-obyavleniya — 33 записи телеграм-рекламы,
которых не было ни в main, ни в рабочей ветке. Сверено слепками файлов:
config/client_tg.php и TelegramTariffService.php на бою совпадают с их версией
и расходятся с нашей. Экраны портала собираются одним куском, поэтому выкат
СМС в прежнем виде откатил бы их живую рекламу назад. Решение владельца —
сперва забрать их работу к себе.
Разрешено пять склеек, все — сохранением обеих сторон:
- Tariff.php: их пояснение про закупочную цену плюс наша строка для подсказчика
типов;
- phpstan-baseline.neon: взята наша вычищенная версия. Их 260 строк заметания
не возвращены — статанализ после сведения дал 0 без них, потому что чинили мы
не baseline, а сам механизм, и он вылечил их новые тесты тоже;
- advertising-telegram-view.spec.ts: наш типизированный мок оставлен;
- CHANGELOG_schema.md: столкновения номеров НЕ было — у нас v9.33/v9.34, у них
v9.64/v9.65. Обе пары сохранены, ничего не двигали; в файл дописано, что дыра
v9.35–v9.63 это след их завышенного замера, а не потерянные записи;
- STATUS.md: служебный файл наблюдателя, взят свежий.
Что сведение вскрыло дополнительно:
- их сторож значков поймал НАШИ три имени с экранов СМС — mdi-file-sign,
mdi-account-badge-outline, mdi-file-download-outline в карте Lucide
отсутствовали и на бою рисовались бы вопросом в кружке. Добавлены по смыслу;
- проверка типов фронта: 1 ошибка стала 0. Образец имени отправителя в тесте
админки не задавал два поля, и Partial подмешивал в них undefined.
Проверено после сведения: PHP 4614 тестов, 4610 зелёных, 4 пропущено,
0 падений; экраны 250 файлов, 2018 зелёных, 3 пропущено, 0 падений;
статанализ 0 через composer stan; проверка типов 0.
Унаследованное, не мной внесённое и не тронутое: линтер фронта показывает
1 замечание на неразрывный пробел в advertising-sms-view.spec.ts — знак там
нужен по смыслу проверки, было до сведения.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
ДЕНЕЖНЫЙ ПРОГОН СДЕЛАН — последний незакрытый блокер Х-2 снят.
Прогон вне тестов, на стенде, с настоящим движением денег. Пробный режим
выключен переменной окружения, файл настроек стенда не трогали вовсе.
Оператор — заглушка, отвечающая живой формой ответа МТС; вызовы шли по сети
тем же кодом канала, что и на бою. Операторская половина доказана отдельно
в тот же день на настоящем МТС: сообщение доставлено, списано 5,03 рубля.
Что доказано деньгами:
- списание 1000,00 на 984,91 — ровно три раза по 5,03, отдельной записью
на каждое сообщение с ключом;
- заморозка сметы снята, незакрытых не осталось;
- за неотправленное деньги вернулись: 984,91 на 989,94, отметка возврата
стоит только у неотправленного;
- двойного возврата нет: опрос прогнан ещё дважды, баланс и число возвратов
не изменились. Это мина 3 плана, самая денежная;
- досыл создаётся отдельной рассылкой с привязкой к родителю, двойной клик
отбит: второй оплаченной рассылки не завёл, деньги не тронул;
- джоб, вызванный напрямую в час ночи, отказался отправлять вне окна 10-20.
Честно не доказано: досыл до отправки не дошёл, ждёт разрешённого окна,
а окно сверяется по времени базы, не по подкрученным часам процесса.
Подделывать состояние в базе не стал — это голый шов. Закроется первым
дневным прогоном либо на бою.
Итоговая таблица Этапа 5 обновлена: строка 5.6 закрыта полностью, 5.7
закрыта по расчёту с оговоркой, что операторскую сторону надо брать из
баланса кабинета, а не из поля цены — оно у МТС всегда ноль.
Промт переписан: задача следующей смены — выкат. Заголовок, состояние
веток и обстановка приведены к правде после сведения; раздел про разъезд
кода и бумаг снят, он закрыт; добавлен рецепт повторения денежного прогона.
Записан выбор поставщика для обеих Лен и всё, чем он подтверждён.
Замерено живьём, не по рекламе:
- разговор одной линией, 7 ходов: 0,88 с до первого звука, ровно от начала
до конца. Втрое быстрее нынешней Александры и без просадки.
- кэш работает, 96-98 процентов попаданий, счётчик поставщик отдаёт.
Значит длина наставления по деньгам почти ничего не стоит — решение
владельца не резать базу оправдалось.
- звонок стоит около 6,4 рубля, из них 5,6 голос. Экономить надо на
длине реплик, а не на длине наставления.
- стена: лимит 20 000 байт считает весь разговор вместе с историей,
а не только наставление. На бесплатном тарифе разговор умирает
на пятом ходу. Оплата — условие любой осмысленной проверки.
Все ручки поставщика разобраны отдельным файлом, 15 проверены живым
опросом канала. Три опасных умолчания: предела длины ответа нет вовсе,
голос по умолчанию младшей модели, температура единица.
Мина на будущее: замер кэша сделан на чужом мозге, который кэширует сам.
Claude так не умеет, ему нужна отдельная ручка, а она по умолчанию
выключена. Проверить первым делом после оплаты.
Выгрузка из Даши — оба промта Лен и три документа базы знаний, дословно.
В kb-02 помечены две мины: ответ про происхождение номера противоречит
обоим промтам, и бесплатный тест описан двумя разными способами.
Ключей и персональных данных в файлах нет, проверено датчиком
с контрольной пробой.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
docs: голосовой канал — выбран Inworld, замерены цена, скорость и все ручки
Записан выбор поставщика для обеих Лен и всё, чем он подтверждён.
Замерено живьём, не по рекламе:
- разговор одной линией, 7 ходов: 0,88 с до первого звука, ровно от начала
до конца. Втрое быстрее нынешней Александры и без просадки.
- кэш работает, 96-98 процентов попаданий, счётчик поставщик отдаёт.
Значит длина наставления по деньгам почти ничего не стоит — решение
владельца не резать базу оправдалось.
- звонок стоит около 6,4 рубля, из них 5,6 голос. Экономить надо на
длине реплик, а не на длине наставления.
- стена: лимит 20 000 байт считает весь разговор вместе с историей,
а не только наставление. На бесплатном тарифе разговор умирает
на пятом ходу. Оплата — условие любой осмысленной проверки.
Все ручки поставщика разобраны отдельным файлом, 15 проверены живым
опросом канала. Три опасных умолчания: предела длины ответа нет вовсе,
голос по умолчанию младшей модели, температура единица.
Мина на будущее: замер кэша сделан на чужом мозге, который кэширует сам.
Claude так не умеет, ему нужна отдельная ручка, а она по умолчанию
выключена. Проверить первым делом после оплаты.
Выгрузка из Даши — оба промта Лен и три документа базы знаний, дословно.
В kb-02 помечены две мины: ответ про происхождение номера противоречит
обоим промтам, и бесплатный тест описан двумя разными способами.
Ключей и персональных данных в файлах нет, проверено датчиком
с контрольной пробой.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@
Прежняя редакция устарела за ночь: писалась до пуша, до плана выката и до находки
с телефонами. Переписан целиком, чтобы не было двух конкурирующих промтов.
Что изменилось по существу:
- Смена начинается с развилки, на которую владелец ещё не ответил: идти на боевой
или сперва разобрать телефоны на ветке СМС. Рекомендуемый ответ дан — сперва
выкат, он готов и снимает уже работающую беду, а ветка СМС не выкачена.
- Появилась дорога Б: 4 незамаскированных телефона в приёмочном листе СМС,
коммит 868a681cf. Замерено, что он только на ветке prospects, на сервер не
уезжал, в телеграм-ветке его нет. Правка истории необратима — записано делать
только с разрешения владельца и предложив ему выбор, а не решать за него.
- Список файлов выката уточнён с 35 до 34: phpstan-baseline.neon на боевой не
едет, он только для проверки кода у нас.
- Добавлены замеры сборки и миграций туда-обратно, которых в прежней редакции не
было вовсе.
- Ловушка про обход сторожей дополнена главным: не обходить вслепую. Сперва
gitleaks по своим коммитам через --log-opts, и только увидев ноль — исключать.
- Записано, что ветка живёт в отдельной рабочей папке и git checkout из корня на
неё не переключится. Прошлая смена начала именно с этого вопроса.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Прежняя редакция устарела за ночь: писалась до пуша, до плана выката и до находки
с телефонами. Переписан целиком, чтобы не было двух конкурирующих промтов.
Что изменилось по существу:
- Смена начинается с развилки, на которую владелец ещё не ответил: идти на боевой
или сперва разобрать телефоны на ветке СМС. Рекомендуемый ответ дан — сперва
выкат, он готов и снимает уже работающую беду, а ветка СМС не выкачена.
- Появилась дорога Б: 4 незамаскированных телефона в приёмочном листе СМС,
коммит 868a681cf. Замерено, что он только на ветке prospects, на сервер не
уезжал, в телеграм-ветке его нет. Правка истории необратима — записано делать
только с разрешения владельца и предложив ему выбор, а не решать за него.
- Список файлов выката уточнён с 35 до 34: phpstan-baseline.neon на боевой не
едет, он только для проверки кода у нас.
- Добавлены замеры сборки и миграций туда-обратно, которых в прежней редакции не
было вовсе.
- Ловушка про обход сторожей дополнена главным: не обходить вслепую. Сперва
gitleaks по своим коммитам через --log-opts, и только увидев ноль — исключать.
- Записано, что ветка живёт в отдельной рабочей папке и git checkout из корня на
неё не переключится. Прошлая смена начала именно с этого вопроса.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Боевой не тронут. Это подготовка.
Что проверено прогоном, а не рассуждением:
- сборка экранов npm run build проходит за 4,39 с;
- все миграции ложатся С НУЛЯ через migrate:fresh, включая обе новые;
- обратный ход обеих миграций работает: rollback --step=2 откатил, migrate
накатил снова, тариф после круга правильный — 0.480 / 0.720 / 0.816;
- телеграм-модуль 325 тестов, экраны 244 файла и 1855 тестов, статанализ 0.
В плане: поимённый список 34 файлов приложения плюс собранные экраны, порядок
шагов, откат по каждому пункту и приёмка глазами шестью пунктами.
Отдельным шагом ДО заливки — проверить право USAGE на счётчике
client_tg_contacts_id_seq. В эту таблицу до сегодня не вставляла ни одна строка
кода, значит право боем ни разу не проверялось.
Вердикт GO по коду при двух условиях: явное «да» владельца и выполненный шаг с
правом на счётчик. Записано и то, чего выкат не закрывает: числа 0.720 и 0.816 —
вывод по правилу «кабинет пишет без НДС», а не замер.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Боевой не тронут. Это подготовка.
Что проверено прогоном, а не рассуждением:
- сборка экранов npm run build проходит за 4,39 с;
- все миграции ложатся С НУЛЯ через migrate:fresh, включая обе новые;
- обратный ход обеих миграций работает: rollback --step=2 откатил, migrate
накатил снова, тариф после круга правильный — 0.480 / 0.720 / 0.816;
- телеграм-модуль 325 тестов, экраны 244 файла и 1855 тестов, статанализ 0.
В плане: поимённый список 34 файлов приложения плюс собранные экраны, порядок
шагов, откат по каждому пункту и приёмка глазами шестью пунктами.
Отдельным шагом ДО заливки — проверить право USAGE на счётчике
client_tg_contacts_id_seq. В эту таблицу до сегодня не вставляла ни одна строка
кода, значит право боем ни разу не проверялось.
Вердикт GO по коду при двух условиях: явное «да» владельца и выполненный шаг с
правом на счётчик. Записано и то, чего выкат не закрывает: числа 0.720 и 0.816 —
вывод по правилу «кабинет пишет без НДС», а не замер.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Отмечено сделанным: ветка СМС в общей, общая сведена в рабочую. Записано,
как пришлось обходить занятость ветки чужой рабочей папкой и что откат
делается одной командой.
Предсказание прежней версии промта НЕ СБЫЛОСЬ и это сказано прямо: под
сторожем сети тесты СМС-модуля покраснеть были должны, но не покраснели.
Числа перемерены: 238 файлов экранов и 1906 зелёных, полный набор 4533
теста и 4529 зелёных, статанализ ноль.
В порядок выката добавлен новый первый шаг — установка зависимостей.
Модуль тянет phpoffice/phpword, на боевом её нет, и без установки он там
не заработает вовсе. Там же записано, что звать надо composer stan, а не
анализатор напрямую: перед командой проекта стоит сторож, который ловит
негодный справочник типов, и обойдя команду, обходишь и сторожа.
Про МТС: канал отправляет, цена сошлась до копейки, поле цены оператора
признано непригодным — сверять по балансу. Осталась одна незакрытая
строка приёмки: живое списание через портал, а оно требует выката.
Добавлен список из шести мест с миной «тест кладёт московское время
строкой без пояса». Сейчас они зелёные, чинить вслепую не стал.
Общая ветка переведена вперёд на ветку клиентских СМС — перемоткой, без
слияния, поэтому конфликтов там быть не могло. Затем общая сведена в рабочую
ветку, которая отставала на 79 записей.
Столкновение было одно и знакомое — словарь орфографии cspell-words.txt.
Разрешено правилом «обе стороны настоящие»: слова обеих веток сохранены,
ничего не выброшено. Журнал схемы БД на этот раз свёлся сам, столкновения
номеров не было.
СТОРОЖ ПДн ОСТАНОВИЛ ЗАПИСЬ И БЫЛ ПРАВ. В образце базы номеров, который
клиент скачивает перед своей первой рассылкой, стоял рабочий телефон
владельца — в двух видах. Это не утечка чужих данных: тот же номер публично
опубликован в реквизитах ИП по требованию ЮKassa. Но клиент заполняет этот
файл своими номерами и запускает рассылку — забытая строка означала бы СМС
владельцу за деньги клиента. Заменён на выдуманный из тестового диапазона.
Подсказки на экране рассылок («+7 999 123-45-67») выдуманы изначально;
разрешены в .gitleaks.toml ПО ЗНАЧЕНИЮ, а не по файлу, чтобы сами файлы
остались под охраной. Сторож проверен вырезанием: подложенный номер, не
подпадающий ни под одно разрешение, он поймал; после снятия подложки — чисто.
Служебный счётчик наблюдателя убран в тайник на время сведения и возвращён
после — он машинный и пересоздаётся хуками.
Файлы второй смены, которая работает в этой же папке, не тронуты и в
слияние не попали.
Проверки сведённой ветки: тесты 4533, зелёных 4529, падений нет; экраны
238 файлов и 1906 зелёных, сторож сети не сработал ни разу; статанализ 0;
формат чист.
Замечания: находки 1, 3, 4, 5 и «срок показа» помечены закрытыми с номерами
коммитов и с решением владельца по каждой. Находка 2 про пузырь чата осталась
открытой — её владельцу не задавали.
Промт следующей смене на выкат. Что в нём измерено заново, а не переписано:
- Список выката пересчитан командой от последнего известного выката ad7a31271:
получилось 35 файлов приложения и ДВЕ миграции, а прежний промт называл 24
файла и одну. Расхождение не разбиралось — записано прямо, с указанием
пересчитать список ещё раз перед самой заливкой.
- Отдельным пунктом — проверить право USAGE на счётчике client_tg_contacts_id_seq.
В эту таблицу до сегодня не вставляла ни одна строка кода, значит право ни разу
не проверялось боем. Замер в тестовой базе: счётчик без права, но так же
выглядит и счётчик кампаний, куда портал на бою пишет. Дано и как проверить, и
чем починить.
- Опорные точки дополнены новыми и отмечено, что tg-tariff-add и
tg-tariff-remove-* больше не существуют.
Записана ловушка этой смены: незакоммиченная чужая работа выглядит как её
отсутствие. Смена началась с вопроса «куда делась работа», а 17 файлов лежали в
отдельной рабочей папке .claude/worktrees/tg-zagolovok — из корня репозитория на
эту ветку не переключиться.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Замечания: находки 1, 3, 4, 5 и «срок показа» помечены закрытыми с номерами
коммитов и с решением владельца по каждой. Находка 2 про пузырь чата осталась
открытой — её владельцу не задавали.
Промт следующей смене на выкат. Что в нём измерено заново, а не переписано:
- Список выката пересчитан командой от последнего известного выката ad7a31271:
получилось 35 файлов приложения и ДВЕ миграции, а прежний промт называл 24
файла и одну. Расхождение не разбиралось — записано прямо, с указанием
пересчитать список ещё раз перед самой заливкой.
- Отдельным пунктом — проверить право USAGE на счётчике client_tg_contacts_id_seq.
В эту таблицу до сегодня не вставляла ни одна строка кода, значит право ни разу
не проверялось боем. Замер в тестовой базе: счётчик без права, но так же
выглядит и счётчик кампаний, куда портал на бою пишет. Дано и как проверить, и
чем починить.
- Опорные точки дополнены новыми и отмечено, что tg-tariff-add и
tg-tariff-remove-* больше не существуют.
Записана ловушка этой смены: незакоммиченная чужая работа выглядит как её
отсутствие. Смена началась с вопроса «куда делась работа», а 17 файлов лежали в
отдельной рабочей папке .claude/worktrees/tg-zagolovok — из корня репозитория на
эту ветку не переключиться.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Сообщение 44850834 доставлено за 16 минут. Баланс кабинета ушёл
с 5 010,00 руб. на 5 004,97 руб. — списано ровно 5,03 руб., в точности та
цена, что вписана на боевой как SMS_MTS_PRICE_KOP=503.
В-211 закрыт, и ответ обратный ожидаемому: поле cost в ответе МТС
НЕПРИГОДНО. Оно ноль не только у неотправленных, но и у доставленного
сообщения, а признак типа трафика так и остаётся неопределённым. Считать
расход оператора по этому полю нельзя ни в каких единицах — сверка должна
смотреть на баланс, раздел «Расходы» кабинета либо на счёт.
Код при этом уже правильный и переделывать его не надо: provider_cost
хранится как есть и никуда не подставляется. Надо лишь знать, что это
всегда ноль, и не строить на нём тревогу.
Отдельно замечено: списание не мгновенное. Баланс уже уменьшился, а раздел
«Расходы — Сообщения» в тот же момент был ещё пуст.
Осталось одно: живой денежный прогон через портал, а он требует, чтобы
модуль стоял на боевом.
Х-5 закрыт, но с третьего захода. Причин было две.
Первая — не подключён тариф, найдена днём и верна. Вторая нашлась только
поздним вечером: имя отправителя liderra.ru подключено ТОЛЬКО НА ОДНОГО
оператора, а все пробы десять дней слались на номер владельца, который на
Tele2. МТС принимал сообщение, видел чужого оператора без согласованного
имени и молча клал в NotSent с пометкой «тип трафика не определён».
Прибор был негодным изначально, и это не заметили ни разу.
Доказано вырезанием: одно сообщение на живой номер МТС тем же боевым путём —
44850834, статус Sending, кабинет пишет «Отправлено». Больше ничего не меняли.
Три подряд неверных вывода об одной чужой системе записаны целиком в
приёмочный лист. Помогло единственное: повторить то же действие руками в
форме кабинета — она отказалась отправлять и написала причину словами,
тогда как API не сообщал её вовсе. Письмо в поддержку МТС было готово и
чуть не ушло третьей ошибкой подряд.
Правила, которые отсюда следуют, вписаны в промт следующей смены: отказ без
причины в API — повтори действие в интерфейсе чужой системы; проверяй канал
на том получателе, для которого он предназначен.
Осталось доспросить окончательный статус сообщения 44850834, чтобы увидеть
поле cost — единицы цены оператора. МТС хранит статусы трое суток, то есть
до 05.08.
В браузерную обвязку добавлена возможность держать ОДНО окно кабинета
открытым между шагами: _hold.mjs поднимает окно и живёт, _step.mjs цепляется
к нему и не закрывает. Кабинет — тяжёлый SPA, и открытие браузера на каждый
шаг стоило минуты.
Х-5 снова открыт. Запись 02.08 днём «канал ожил» была неверной и снята
тем же вечером: ответ OK на приёме приняли за доказательство отправки —
ровно мина «принял не значит отправил» В-212, ради которой и делается
Этап 5. Судьбу проб никто не спросил.
Замерено вечером глазами в кабинете МТС: тариф «Оплата по факту» подключён,
баланс 5010 руб. и зарезервировано 0, имя отправителя liderra.ru подключено
и стоит в .env боевого, чёрные списки пусты, API активно с токеном до 2027.
И всё равно три пробы 02.08 вернули NotSent с cost 0 — последняя через
5,5 часа после подключения тарифа. Дальше сторона владельца: письмо в
поддержку МТС с номерами сообщений 44846577 и 44849057.
Task 11 закрыт в части, не зависящей от МТС: тесты модуля 387 из 387,
сделки 17 из 17, фронт 237 файлов и 1899 зелёных при 3 пропущенных,
статанализ 0, pint чисто, vue-tsc 5 давних чужих. Права спрошены у базы
матрицей: счётчиков без USAGE нет, у служебной роли на журнале сообщений
есть UPDATE, обратная сторона тоже проверена. Денежная половина строк
5.6 и 5.7 и единицы поля cost остаются недоказанными — и это сказано
вслух, а не спрятано.
Х-1 снят замером: сторожа текста живы, 129 замечаний разметки в приёмочном
листе унаследованы — столько же даёт версия из git show HEAD.
Побочно доказано живьём: за неотправленное МТС денег не берёт.