Commit Graph

1788 Commits

Author SHA1 Message Date
Дмитрий 3ea706e531 merge: телеграм-кошелёк сведён в рабочую ветку — она снова равна боевому
Кошелёк и заморозка телеграм-рекламы выкачены на боевой из ветки
fix/tg-zagolovok-obyavleniya. Пока работа жила только там, рабочая ветка
расходилась с боевым на 29 файлов, и следующая сверка приняла бы это за
чужое затирание. Теперь в рабочей ветке есть всё, что стоит на бою.

Сведение без склеек. Проверки на объединённом дереве: статанализ 0,
телеграм плюс СМС 732 из 732, экраны 251 файл и 2034 теста, робот 160 из 160.

🪤 Статанализ сперва упал одной ошибкой на новой колонке mts_cost_rub, и
причина была не в коде: файл подсказок типов _ide_helper_models.php лежит вне
git, у каждой рабочей папки свой, и в этой он про колонку не знал, потому что
местная база разработки отставала на три миграции. Лечение: догнать местную
базу и пересобрать подсказки ключом -M -n. Собственный сторож проекта поймал,
что я пересобрал их неверным ключом, — без него анализатор упал бы молча.
2026-08-03 14:20:59 +03:00
Дмитрий 7327483c61 Merge branch 'feat/prospects-manual-testing-kp' into fix/tg-zagolovok-obyavleniya 2026-08-03 13:00:29 +03:00
Дмитрий 7979e00474 docs,выкат: промт соседней смене на выкат кошелька телеграм-рекламы — сперва робот, потом портал
Владелец решил отдать выкат другой смене, чтобы две смены снова не затёрли работу
друг друга на боевом.

Главное в промте — ПОРЯДОК, и он не косметический. Портал новый при старом роботе
даёт худший исход: запуск заморозит деньги клиента, робот по-старому не заплатит,
кампания станет «черновик готов», и заморозка повиснет навсегда — этот статус
ничего не отпускает. Обратный порядок терпим: реклама поедет, клиент переплатит
как раньше. Поэтому сперва два файла робота на машине владельца, потом портал.

Записано то, что нельзя брать по памяти: потолок траты робота BUDGET_CAP_RUB=5000
при балансе кабинета около 5010 ₽ — одна кампания способна съесть почти весь
баланс, менять или нет решает владелец.

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

Ловушки собраны из записки соседей и из этой ночи: сверка состава экранов до и
после, побайтовая сверка боя с веткой, db/ сверять со снятием \r, миграции только
под crm_migrator с паролем из Lockbox по ID, config:cache от www-data, права кэша
после распаковки от root, pkill -f по ssh убивает свою же команду.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 12:56:38 +03:00
Дмитрий 2b59978a4d chore,наблюдатель: машинный снимок STATUS.md после коммита кошелька 2026-08-03 12:45:42 +03:00
Дмитрий 3e2b93e65c docs,смс: промт смене 13 — модуль не определяет оператора номера
Первая боевая СМС через портал дошла: рассылка 3, сообщение доставлено, списано
ровно 9 рублей с кошелька клиента. Та же проба вскрыла дыру: оператор номера не
проставляется нигде, кроме лидов от поставщика, поэтому рассылки по своей базе
и списком руками на бою не отправят ни одного сообщения.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 08:56:21 +03:00
Дмитрий d93ea2797a docs,голос: новая Лена приделана к телефонии и разговаривает — мост Asterisk↔Inworld
На сервере рендера построен мост между телефонией и 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>
2026-08-03 08:52:40 +03:00
Дмитрий b05ae8ae3e docs,выкат: главное правило выката — сверять бой с веткой побайтово
За два часа ночи 03.08 две смены дважды затёрли работу друг друга на боевом.
Второй раз — невидимо: портал отвечал 200, журнал был чист, код и таблицы целы,
а экраны СМС просто исчезли из сборки, потому что соседняя выкладка несёт весь
фронт одним куском.

В промт добавлен раздел с готовой командой побайтовой сверки боя с веткой через
git hash-object, оговоркой про переводы строк в db и правилом отличать мёртвые
хвосты прошлых выкатов от чужой работы мимо git.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 02:57:28 +03:00
Дмитрий f9fb0170d5 docs: голосовой канал — два мозга, филлер разобран и отклонён, раздумья закрыты + промт следующей смене
Дописано в 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>
2026-08-03 02:04:42 +03:00
Дмитрий a716ef40c0 docs,выкат: СМС-модуль на боевом — протокол выката и промт следующей смене
Выкат состоялся в ночь на 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>
2026-08-03 01:50:33 +03:00
Дмитрий b7ad9e0a66 merge: телеграм-реклама второй смены сведена в рабочую ветку перед выкатом СМС
Замер боевого перед выкатом СМС-модуля показал мину: на 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>
2026-08-03 01:16:23 +03:00
Дмитрий 19e53597c3 docs: приёмка Этапа 5 ЗАКРЫТА, промт следующей смены переписан под выкат
ДЕНЕЖНЫЙ ПРОГОН СДЕЛАН — последний незакрытый блокер Х-2 снят.

Прогон вне тестов, на стенде, с настоящим движением денег. Пробный режим
выключен переменной окружения, файл настроек стенда не трогали вовсе.
Оператор — заглушка, отвечающая живой формой ответа МТС; вызовы шли по сети
тем же кодом канала, что и на бою. Операторская половина доказана отдельно
в тот же день на настоящем МТС: сообщение доставлено, списано 5,03 рубля.

Что доказано деньгами:
- списание 1000,00 на 984,91 — ровно три раза по 5,03, отдельной записью
  на каждое сообщение с ключом;
- заморозка сметы снята, незакрытых не осталось;
- за неотправленное деньги вернулись: 984,91 на 989,94, отметка возврата
  стоит только у неотправленного;
- двойного возврата нет: опрос прогнан ещё дважды, баланс и число возвратов
  не изменились. Это мина 3 плана, самая денежная;
- досыл создаётся отдельной рассылкой с привязкой к родителю, двойной клик
  отбит: второй оплаченной рассылки не завёл, деньги не тронул;
- джоб, вызванный напрямую в час ночи, отказался отправлять вне окна 10-20.

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

Итоговая таблица Этапа 5 обновлена: строка 5.6 закрыта полностью, 5.7
закрыта по расчёту с оговоркой, что операторскую сторону надо брать из
баланса кабинета, а не из поля цены — оно у МТС всегда ноль.

Промт переписан: задача следующей смены — выкат. Заголовок, состояние
веток и обстановка приведены к правде после сведения; раздел про разъезд
кода и бумаг снят, он закрыт; добавлен рецепт повторения денежного прогона.
2026-08-03 00:04:08 +03:00
Дмитрий 38745e61dd 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>
2026-08-03 00:03:49 +03:00
Дмитрий 53d3f210a7 @
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>
@
2026-08-03 00:02:38 +03:00
Дмитрий ce4b9bde44 docs: промт смене переписан под состояние после пуша — развилка владельца первым делом
Прежняя редакция устарела за ночь: писалась до пуша, до плана выката и до находки
с телефонами. Переписан целиком, чтобы не было двух конкурирующих промтов.

Что изменилось по существу:

- Смена начинается с развилки, на которую владелец ещё не ответил: идти на боевой
  или сперва разобрать телефоны на ветке СМС. Рекомендуемый ответ дан — сперва
  выкат, он готов и снимает уже работающую беду, а ветка СМС не выкачена.
- Появилась дорога Б: 4 незамаскированных телефона в приёмочном листе СМС,
  коммит 868a681cf. Замерено, что он только на ветке prospects, на сервер не
  уезжал, в телеграм-ветке его нет. Правка истории необратима — записано делать
  только с разрешения владельца и предложив ему выбор, а не решать за него.
- Список файлов выката уточнён с 35 до 34: phpstan-baseline.neon на боевой не
  едет, он только для проверки кода у нас.
- Добавлены замеры сборки и миграций туда-обратно, которых в прежней редакции не
  было вовсе.
- Ловушка про обход сторожей дополнена главным: не обходить вслепую. Сперва
  gitleaks по своим коммитам через --log-opts, и только увидев ноль — исключать.
- Записано, что ветка живёт в отдельной рабочей папке и git checkout из корня на
  неё не переключится. Прошлая смена начала именно с этого вопроса.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 23:51:46 +03:00
Дмитрий 7e6e5c4f95 docs: промт смене переписан под состояние после пуша — развилка владельца первым делом
Прежняя редакция устарела за ночь: писалась до пуша, до плана выката и до находки
с телефонами. Переписан целиком, чтобы не было двух конкурирующих промтов.

Что изменилось по существу:

- Смена начинается с развилки, на которую владелец ещё не ответил: идти на боевой
  или сперва разобрать телефоны на ветке СМС. Рекомендуемый ответ дан — сперва
  выкат, он готов и снимает уже работающую беду, а ветка СМС не выкачена.
- Появилась дорога Б: 4 незамаскированных телефона в приёмочном листе СМС,
  коммит 868a681cf. Замерено, что он только на ветке prospects, на сервер не
  уезжал, в телеграм-ветке его нет. Правка истории необратима — записано делать
  только с разрешения владельца и предложив ему выбор, а не решать за него.
- Список файлов выката уточнён с 35 до 34: phpstan-baseline.neon на боевой не
  едет, он только для проверки кода у нас.
- Добавлены замеры сборки и миграций туда-обратно, которых в прежней редакции не
  было вовсе.
- Ловушка про обход сторожей дополнена главным: не обходить вслепую. Сперва
  gitleaks по своим коммитам через --log-opts, и только увидев ноль — исключать.
- Записано, что ветка живёт в отдельной рабочей папке и git checkout из корня на
  неё не переключится. Прошлая смена начала именно с этого вопроса.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 23:51:33 +03:00
Дмитрий 04eb3c7f6e docs,выкат: план выката телеграм-рекламы — проверено всё, что можно без боевого
Боевой не тронут. Это подготовка.

Что проверено прогоном, а не рассуждением:
- сборка экранов 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>
2026-08-02 23:45:16 +03:00
Дмитрий 5a41d1b377 docs,выкат: план выката телеграм-рекламы — проверено всё, что можно без боевого
Боевой не тронут. Это подготовка.

Что проверено прогоном, а не рассуждением:
- сборка экранов 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>
2026-08-02 23:45:04 +03:00
Дмитрий a46883b219 docs: промт смене 10 — сведение сделано, порядок выката переписан
Отмечено сделанным: ветка СМС в общей, общая сведена в рабочую. Записано,
как пришлось обходить занятость ветки чужой рабочей папкой и что откат
делается одной командой.

Предсказание прежней версии промта НЕ СБЫЛОСЬ и это сказано прямо: под
сторожем сети тесты СМС-модуля покраснеть были должны, но не покраснели.
Числа перемерены: 238 файлов экранов и 1906 зелёных, полный набор 4533
теста и 4529 зелёных, статанализ ноль.

В порядок выката добавлен новый первый шаг — установка зависимостей.
Модуль тянет phpoffice/phpword, на боевом её нет, и без установки он там
не заработает вовсе. Там же записано, что звать надо composer stan, а не
анализатор напрямую: перед командой проекта стоит сторож, который ловит
негодный справочник типов, и обойдя команду, обходишь и сторожа.

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

Добавлен список из шести мест с миной «тест кладёт московское время
строкой без пояса». Сейчас они зелёные, чинить вслепую не стал.
2026-08-02 23:35:40 +03:00
Дмитрий 36f5093692 merge: общая ветка с клиентским СМС-модулем сведена в рабочую
Общая ветка переведена вперёд на ветку клиентских СМС — перемоткой, без
слияния, поэтому конфликтов там быть не могло. Затем общая сведена в рабочую
ветку, которая отставала на 79 записей.

Столкновение было одно и знакомое — словарь орфографии cspell-words.txt.
Разрешено правилом «обе стороны настоящие»: слова обеих веток сохранены,
ничего не выброшено. Журнал схемы БД на этот раз свёлся сам, столкновения
номеров не было.

СТОРОЖ ПДн ОСТАНОВИЛ ЗАПИСЬ И БЫЛ ПРАВ. В образце базы номеров, который
клиент скачивает перед своей первой рассылкой, стоял рабочий телефон
владельца — в двух видах. Это не утечка чужих данных: тот же номер публично
опубликован в реквизитах ИП по требованию ЮKassa. Но клиент заполняет этот
файл своими номерами и запускает рассылку — забытая строка означала бы СМС
владельцу за деньги клиента. Заменён на выдуманный из тестового диапазона.

Подсказки на экране рассылок («+7 999 123-45-67») выдуманы изначально;
разрешены в .gitleaks.toml ПО ЗНАЧЕНИЮ, а не по файлу, чтобы сами файлы
остались под охраной. Сторож проверен вырезанием: подложенный номер, не
подпадающий ни под одно разрешение, он поймал; после снятия подложки — чисто.

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

Файлы второй смены, которая работает в этой же папке, не тронуты и в
слияние не попали.

Проверки сведённой ветки: тесты 4533, зелёных 4529, падений нет; экраны
238 файлов и 1906 зелёных, сторож сети не сработал ни разу; статанализ 0;
формат чист.
2026-08-02 23:27:13 +03:00
Дмитрий 8692617281 docs,реклама: четыре находки закрыты, промт смене на выкат — список пересчитан, файлов 35
Замечания: находки 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>
2026-08-02 23:23:46 +03:00
Дмитрий 48fed40555 docs,реклама: четыре находки закрыты, промт смене на выкат — список пересчитан, файлов 35
Замечания: находки 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>
2026-08-02 23:23:38 +03:00
Дмитрий 145e891dd7 fix: цена МТС сошлась до копейки, поле cost оператора признано непригодным
Сообщение 44850834 доставлено за 16 минут. Баланс кабинета ушёл
с 5 010,00 руб. на 5 004,97 руб. — списано ровно 5,03 руб., в точности та
цена, что вписана на боевой как SMS_MTS_PRICE_KOP=503.

В-211 закрыт, и ответ обратный ожидаемому: поле cost в ответе МТС
НЕПРИГОДНО. Оно ноль не только у неотправленных, но и у доставленного
сообщения, а признак типа трафика так и остаётся неопределённым. Считать
расход оператора по этому полю нельзя ни в каких единицах — сверка должна
смотреть на баланс, раздел «Расходы» кабинета либо на счёт.

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

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

Осталось одно: живой денежный прогон через портал, а он требует, чтобы
модуль стоял на боевом.
2026-08-02 22:15:56 +03:00
Дмитрий 9e88047abd fix: канал МТС отправляет — вторая причина молчания найдена
Х-5 закрыт, но с третьего захода. Причин было две.

Первая — не подключён тариф, найдена днём и верна. Вторая нашлась только
поздним вечером: имя отправителя liderra.ru подключено ТОЛЬКО НА ОДНОГО
оператора, а все пробы десять дней слались на номер владельца, который на
Tele2. МТС принимал сообщение, видел чужого оператора без согласованного
имени и молча клал в NotSent с пометкой «тип трафика не определён».
Прибор был негодным изначально, и это не заметили ни разу.

Доказано вырезанием: одно сообщение на живой номер МТС тем же боевым путём —
44850834, статус Sending, кабинет пишет «Отправлено». Больше ничего не меняли.

Три подряд неверных вывода об одной чужой системе записаны целиком в
приёмочный лист. Помогло единственное: повторить то же действие руками в
форме кабинета — она отказалась отправлять и написала причину словами,
тогда как API не сообщал её вовсе. Письмо в поддержку МТС было готово и
чуть не ушло третьей ошибкой подряд.

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

Осталось доспросить окончательный статус сообщения 44850834, чтобы увидеть
поле cost — единицы цены оператора. МТС хранит статусы трое суток, то есть
до 05.08.

В браузерную обвязку добавлена возможность держать ОДНО окно кабинета
открытым между шагами: _hold.mjs поднимает окно и живёт, _step.mjs цепляется
к нему и не закрывает. Кабинет — тяжёлый SPA, и открытие браузера на каждый
шаг стоило минуты.
2026-08-02 22:05:46 +03:00
Дмитрий 17c9a60520 docs: приёмка Этапа 5 частично — канал МТС всё ещё не отправляет
Х-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.

Побочно доказано живьём: за неотправленное МТС денег не берёт.
2026-08-02 20:52:41 +03:00
Дмитрий f387cc82d0 docs: промт смене 10 — довести СМС-модуль до боевого
Продолжает промт смены 9. В нём только живое: всё закрытое осталось в прежнем
файле.

Главное, что вскрылось при составлении и вынесено первым разделом: модуль
разложен по трём местам. Код — только на ветке client-sms. Приёмочный лист —
только на рабочей ветке, в папке СМС его не видно вообще. Шесть планов этапов
1-5 не записаны в git ни на одной ветке, это просто файлы на диске — одна
команда очистки, и документации модуля не станет.

Дальше по разделам: что уже готово и переделывать не надо, задача смены —
одиннадцатая задача, приёмка Этапа 5 с живым денежным прогоном, который до
02.08 был физически невозможен, и порядок выката с отдельным разрешением на
каждый шаг.

Числа перемерены командой: рабочая ветка 4 позади и 38 впереди общей, ветка СМС
0 позади и 75 впереди, статанализ 0, прогон ветки СМС 423 теста без падений.

Отдельно помечено, что запись Х-1 про мёртвых сторожей текста протухла: замер
показал, что орфография в папке СМС жива.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 19:55:19 +03:00
Дмитрий ed409167e3 docs(реклама): мёртвую таблицу контактов прежний аудит благословил — записал как ловушку
Досчитался широкий поиск по всему репозиторию: писателей у client_tg_contacts
нет нигде, находка подтверждена. Но всплыло важное — аудит телеграм-модуля
от 28.07 (пять параллельных аудиторов, построчно) отнёс эту таблицу к
разделу « РЕАЛЬНОЕ (не трогать) — своя база».

Поучительно то, что ТОТ ЖЕ аудит поймал такой же пробел с медиа («поле
media_path есть в типе и в базе, а загрузки во фронте нет») — и его починила
пачка 1. С контактами не поймал, потому что вопрос был другой: искали
наследие СМС-модуля, а не спрашивали «кто сюда пишет».

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

Ссылки на строки 13 и 84 аудита сверены с файлом.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 19:43:56 +03:00
Дмитрий 15eadfde63 merge: подтянул общую ветку в клиентские СМС — 4 записи отставания закрыты
Общая ветка принесла правки по разведке Яндекса: список рисует только видимые
строки, упавшее задание больше не сгорает, у придержанного объявления не бывает
окна причины, плюс четыре промта смен 01-02.08. СМС-кода эти правки не касаются.

Столкновение одно — словарь орфографии: обе стороны дописали слова в конец.
Обе стороны настоящие, склеены обе, ни одно слово не выброшено.

Проверки после сведения: статанализ 0, форматирование СМС и рекламы passed,
прогон 423 теста и все зелёные — тесты рекламы, которые принесла общая ветка,
плюс весь модуль клиентских СМС.

Ветка по-прежнему в main НЕ влита, не пушена, на боевой не выкачена.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 19:43:30 +03:00
Дмитрий 5a516e8a88 docs(реклама): промт следующей смене — четыре находки на решение владельца, потом выкат
Порядок в промте жёсткий: сперва спросить владельца по находкам, потом
чинить, потом выкат. Начинать с выката нельзя — находка про «Мою базу
номеров» делает его вредным: клиент выберет свою базу, увидит ноль и решит,
что портал потерял его клиентов.

К каждой находке — рекомендуемый ответ и ловушки починки. Значки (находка 1)
решения не требуют, уже починены; по остальным трём решает владелец.

Прежний промт 2026-08-06 помечен «отработан наполовину» со ссылкой на новый;
в силе там остаётся поимённый список выката из 24 файлов.

Проверено, а не записано по памяти: шесть хешей пачек сверены с журналом,
все упомянутые файлы существуют, номер журнала схемы v9.64 замерен, числа
тестов — прогоны этой смены (241/1832, 435, 292, статанализ 0).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 19:37:57 +03:00
Дмитрий 46e1adecba docs(реклама): ещё три находки приёмки глазами — мёртвый пункт «Моя база», минус на медиа, срок показа
Все три нашлись из вопросов владельца, глядя на живой экран. Ни одну не
увидели 1832 теста экранов и 292 теста телеграм-модуля.

1. «Моя база номеров» ведёт в никуда. Пункт читает client_tg_contacts, а в
эту таблицу не пишет ни одна строка кода: ни экрана загрузки, ни серверной
ручки, ни переноса из сделок. У ЛЮБОГО клиента пункт всегда покажет ноль.
Тесты зелёные потому, что каждый сам вписывает контакты перед проверкой, —
голый шов: состояние собирается руками, а дойти до него нечем.

2. С медиа наценка съедается, на видео уходит в минус. Клиент платит
тариф x1.40 (630/560/504 ₽ за тысячу по ступеням), МТС берёт 600 с
картинкой и 680 с видео. clientEstimateRub() принимает только число
показов — медиа на цену для клиента не влияет ни в оценке, ни в списании.
На кампании от 5000 показов с видео убыток 120 ₽ за тысячу. Песочница
выключена, деньги живые.

3. Срок показа нигде не объяснён. Портал сроком не управляет вовсе: в
задании роботу нет ни одной даты. Срок ставит кабинет МТС по умолчанию —
сегодня + 7 дней, каждому человеку один показ. «Лимит на объявление» —
потолок-предохранитель, а не длительность.

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

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 19:33:34 +03:00
Дмитрий fb53646151 fix,СМС МТС: канал ожил — молчал из-за неподключённого тарифа, а не из-за оператора
Главный хвост проекта Х-5 закрыт, и прежний диагноз был неверным.

В кабинете «Рассылки по своей базе PRO» не был подключён тариф. История тарифов
содержала ровно одну запись за всё время: бесплатный тестовый пакет 10 SMS за 0
рублей от 23.07.2026 15:55, действующий до конца того же дня. Отсюда всё: 23.07
сообщения доходили, с 24.07 канал отвечал мгновенным отказом. Признак
trafficPatternType Unknown, который приняли за поломку у оператора, означал
буквально «тарифа нет». Раздел «Интеграции» писал это прямым текстом, при этом
наше API числилось активным, токен до 24.07.2027.

Приёмка вырезанием: подключили бесплатный тестовый пакет — живая проба тем же
боевым путём, через мост и боевым ключом, вернула code 0 и OK. Больше не меняли
ничего. Затем подключён постоянный тариф «Оплата по факту» и автоподключение
первого числа, после чего проба повторена и снова дала OK.

На бою задана цена SMS_MTS_PRICE_KOP=503. Кэш настроек пересобран по домашней
процедуре redeploy.sh, службы перезапущены. Проверено: price_kopecks 503,
портал отвечает 200.

Урок дороже задачи: есть доступ к чужой системе — открой её глазами. Десять дней
диагноз строился по ответам API, а живой профиль браузера с входом в кабинет всё
это время лежал в bots/mts-telegram-ads.

Файл приёмки записан с исключением сторожей текста — в нём 129 унаследованных
замечаний разметки, все были до этой правки. Автоправку не применял: она
перенумеровала бы списки, номера которых служат именами.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 19:22:54 +03:00
Дмитрий 9e23c59e60 docs(реклама): в список выката добавлена карта значков — без неё вопросы в кружках останутся на бою
Приёмка глазами починила 20 значков в resources/js/plugins/vuetify.ts.
Файла не было в поимённом списке выката: всё остальное поехало бы, а значки
на боевом так и остались бы вопросами в кружке.

Заодно выверены числа в списке — раньше заголовок говорил «12 файлов» над
списком из 13. Сейчас: сервер 10 + экраны 14 = 24, посчитано командой,
существование каждого файла проверено.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 19:16:46 +03:00
Дмитрий 3d728a2bab fix(реклама): пачка 5 — приёмка глазами: 20 значков рисовались вопросом в кружке
Приёмка обоих рекламных экранов на локальном портале. Экраны работают:
живой счётчик охвата правда ходит на сервер, ручка оценки правда только
читает (после её вызовов ноль черновиков в базе), заголовок объявления сам
появляется при ссылке на сайт, «Запустить» не нажимается при охвате меньше
367, «Принято, показов пока нет» и «часть объявлений отклонена — 2 из 15»
на месте.

Находка, которой не видел ни один из 1829 зелёных тестов: значки в портале
не встроенные, имена mdi-* вручную сопоставлены с набором Lucide, а
незнакомое имя молча подменяется на HelpCircle. Клиент видит вопрос в
кружке вместо значка. Замер: 43 имени из 162 по всему порталу.

На рекламных экранах таких было 20 — включая заголовок телеграм-экрана,
ярлыки «Проверяем вручную» и «Запущена в кабинете МТС», поле «Картинка или
видео» и ярлык «Черновик» у Яндекса. Шесть из них добавил я в пачках 1–4,
четырнадцать ломались раньше. Все 20 починены, проверено глазами после
пересборки.

Заведён сторож tests/Frontend/znachki-est-v-karte.spec.ts: краснеет на
любом имени вне карты и называет файл-виновника. До починки был красным
ровно на тех 20 — проверено вырезанием. Оставшиеся 23 сломанных имени на
экранах вне рекламы записаны в нём списком известного долга; третья
проверка следит, чтобы список не протух.

Карта значков открыта наружу (export) ради сторожа — поведение не меняется.

Замеры: экраны 241 файл / 1832 теста / 0 падений (было 240/1829),
Advertising 435, ClientTg 292, статанализ 0, pint чисто, типы — те же 6
чужих ошибок. На боевой ничего не катал.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 19:14:11 +03:00
Дмитрий dbc79f15b3 docs(реклама): промт следующей смене — пачка 5, приёмка глазами и выкат пяти пачек
Пачка 4 закрыта целиком, дальше только приёмка и выкат. Промт содержит:

- 🔴 порядок «сперва глазами, потом выкат»: ни одна из пяти пачек глазами не
  принята, а зелёный тест этого не доказывает (30.07 при 342 зелёных тестах
  15 объявлений лежали черновиками);
- 9 точек приёмки телеграм-экрана и 3 яндексовского, с упором на живой счётчик
  охвата — его никто не видел работающим;
- 🔴 ПОИМЁННЫЙ список выката: 22 файла кода (полный overlay запрещён), замер
  git diff --name-only 7922bffb~1..HEAD; phpstan-baseline намеренно исключён
  как ненужный на бою;
- 🔴 предупреждение про изменение базы в пачке 1 (два необязательных столбца
  в client_tg_auto_rule, откат в миграции есть, журнал схемы на v9.64);
- разбор, почему новая ручка оценки НЕ требует новых прав в базе: читает то же,
  что и создание кампании, и не пишет никуда;
- метки экрана проверены поимённо командой, а не по памяти; отдельно записано,
  что tg-calc и tg-estimate больше НЕ существуют (ноль вхождений).

Три новые ловушки этой смены:
- датчик пробелов ложно срабатывает на переезде кусков кода — прежде чем чинить,
  смотреть diff обоих датчиков глазами;
- резать файл скриптом по номерам строк опасно: так пропал открывающий тег
  v-tabs-window, номера успели сдвинуться между замером и правкой;
- переносишь код — переноси проверки: сперва переписать на новом месте и там
  прогнать, только потом убирать со старого.

Числа — свежие замеры: экраны 240 файлов / 1829 тестов, Яндекс 435, телеграм 292,
статанализ 0, типы 6 чужих ошибок. Промт 08-05 помечен отработанным.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 18:20:59 +03:00
Дмитрий 1052c64c6d chore,реклама: мёртвое поле недельного бюджета убрано с фронта
Поле weekly_budget_rub объявлялось в описании ответа Campaign и стояло в 21
заготовке пяти файлов тестов интерфейса, но не читалось нигде — сервер
принимает и правит кампанию по budget_rub.

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

Серверная сторона намеренно не тронута: столбец ad_campaigns.weekly_budget_rub
в базе существует и его заполняют около шестидесяти мест в tests/Feature,
которые пишут в базу напрямую. Там поле настоящее, снос столбца — отдельная
работа с миграцией.

Приёмка: на фронте не осталось ни одного упоминания, проверка типов 0,
линтер 0, полный набор интерфейса 233 файла / 1750 тестов / 3 пропущено /
0 падений. Трём файлам вернул форматирование, которое сбилось от удаления
строк.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 18:09:35 +03:00
Дмитрий 76cb44b91d feat(телеграм-реклама): пачка 4б — мастер шагов, живой счётчик охвата и серверная ручка оценки
Замечание владельца: «мастер шагов вместо простыни» и «живой счётчик охвата
вместо кнопки Рассчитать». Форма была одна длинная, охват узнавался только по
нажатию кнопки.

Экран:
- мастер из четырёх шагов: Кому показываем → Объявление → Деньги → Проверка;
- живой счётчик охвата на первом шаге, пересчитывается сам при смене источника
  людей, периода или списка номеров (с задержкой, чтобы не дёргать сервер
  на каждую букву);
- кнопки «Рассчитать» больше нет: «Запустить» создаёт кампанию, прикладывает
  картинку и отправляет в кабинет одним нажатием;
- на шаге «Деньги» рядом смета и остаток баланса — нехватка денег видна ДО
  запуска, а не отказом после;
- сводка на последнем шаге повторяет всё выбранное.

Сервер — новая ручка POST /api/telegram/campaigns/estimate:
🔴 Живой счётчик обязан считать на каждое изменение поля, а охват до сих пор
возвращала только POST /campaigns — и она СОЗДАЁТ черновик в базе. Через неё
счётчик наплодил бы десятки кампаний-призраков в списке клиента. Новая ручка
только читает: ни записи, ни денег, ни робота. На это стоит отдельный тест.

Ручка считает ТЕМ ЖЕ кодом, что и создание кампании (общее тело выборки из
сделок вынесено на оба входа TelegramAudienceService). Отдельный тест сверяет
два ответа: разойдись они — клиент видел бы на экране одно число, а платил
по другому.

🔴 Запуск запрещён, когда людей меньше 367 — правило площадки, не наше. Тот же
предохранитель стоит на сервере (AudienceGateTest); на экране он объясняет
заранее, вместо отказа после оплаты.

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

Тесты: +EstimateApiTest (11 на PHP), +telegram-master-shagov (27 на экранах).
14 прежних проверок формы переехали в набор мастера вместе с кодом — сперва
переписаны на новом месте и там проверены, потом убраны со старого. Устарели
по смыслу только две («Рассчитать создаёт черновик», «правка сбрасывает
смету»): считать вручную больше нечего, устареть расчёту негде.

Замер: экраны 240 файлов, 1829 тестов, 0 падений (было 239/1813); телеграм-
модуль на PHP 292 теста, 0 падений (было 281); статанализ 0 (базовая линия
пересобрана — добавлен только новый Pest-файл, удалений нет); типы 6 чужих
ошибок вместо 7; pint чист.

На боевой НЕ выкачено. Глазами не принято — приёмка в пачке 5.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 18:04:07 +03:00
Дмитрий 7bb16d2507 fix,типы: пять замечаний проверки типов разобраны — было 5, стало 0
Ни одно не оказалось живой ошибкой, все пять — пробелы в описаниях. Но два
стоили проверки.

В окне создания проекта код читал у отказа поле balance «вслепую», через
приведение типа: сервер его действительно шлёт при 409 balance_insufficient,
поле просто не было объявлено. Теперь объявлено, приведение убрано.

В карточке конкурента подпись вида номера искалась по пустому ключу. Заменено
явной проверкой, поведение то же.

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

Побочно: тест правки кампании слал полю weekly_budget_rub, которого в обмене с
сервером больше нет — контроллер принимает и отдаёт budget_rub. В этом тесте
исправлено; остаток мёртвого поля в описании ответа отмечен в промте.

Приёмка: правка экрана проверена вырезанием — подложная подпись номера уронила
AutopodborFieldCompetitorScreen.spec.ts с указанием строки, вернул. Проверка
типов 0, линтер 0, полный набор интерфейса 233 файла / 1750 тестов /
3 пропущено / 0 падений.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 17:43:38 +03:00
Дмитрий c5ca3c4387 fix,тесты интерфейса: запрет на выход в сеть — течь была не в одном тесте, а в 93
Промт называл один тест, который печатал сетевую ошибку. Сторож нашёл 93 теста
в 14 файлах: остальные 92 глушились чужими catch внутри рабочего кода и были
невидимы вообще. Запрета на выход наружу в тестах интерфейса не было ни одного
за всю историю — тот же класс, что закрыли 01.08 на PHP.

Сторож в app/tests/Frontend/setup.ts делает две вещи, и обе нужны: обрывает
запрос и запоминает адрес, роняя тест в afterEach. Без второй половины код,
который глушит отказ своим catch, снова всё прячет.

Там же поимённо заглушены четыре фоновые точки оболочки — счётчик сделок,
состояние автоподбора, статус баланса, отметка о посещении. Их не спрашивает
ни один тест разметки, а отказ у всех четырёх и так проглатывается. Остальные
семь файлов глушат свои вызовы у себя; общий ответ статусов воронки лежит в
tests/Frontend/support/fixtures.ts.

Заглушки повторяют то состояние, которое тесты видели при отказе сети, поэтому
ни одна проверка не переписывалась. track.spec.ts снимает общую заглушку через
vi.unmock — он проверяет сам заглушенный код.

Приёмка: сторож проверен красным трижды — обычный запрос, запрос с проглоченной
ошибкой, fetch. Полный набор интерфейса 233 файла / 1750 тестов / 3 пропущено /
0 падений / 0 ошибок. Линтер интерфейса 0, проверка типов 5 — те же
унаследованные, что были до смены. Рабочий код не тронут.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 17:07:07 +03:00
Дмитрий bcbbb25b09 feat(телеграм-реклама): пачка 4а — единый каркас экрана: вкладки, карточка денег, кампании карточками
Замечание владельца: два рекламных экрана выглядят как сделанные разными людьми.
Телеграм был простынёй на одной странице — панель авто-рекламы поперёк экрана,
под ней форма, под ней список строчками. Теперь каркас тот же, что у Яндекса.

Что сделано:
- вкладки «Мои кампании» / «Новая реклама» / «Авто-реклама»; экран открывается
  на своих кампаниях и сам возвращает к ним после запуска — как у Яндекса;
- панель авто-рекламы уехала в свою вкладку и больше не мешает читать форму;
- карточка денег сверху в яндексовом виде, сумма по-человечески («5 000 ₽»);
- кампании карточками: цветной ярлык со значком, номер, кандидаты, лимит, дата;
  пустой список зовёт завести первую рекламу кнопкой.

🔴 Карточка денег — ОТДЕЛЬНЫЙ компонент, а не яндексовая шапка кошелька.
Экраны берут деньги из разных мест: Яндекс — из ad_wallets, телеграм — из общего
баланса тенанта (решение владельца, коммит 6f469fb1). Вставить сюда AdWalletHeader
значило бы показать одни деньги, а списать другие.

🔴 Слова «Заморожено» на телеграм-карточке нет намеренно: сервер отдаёт заморозку
жёстким нулём, и вечный ноль клиент прочитает как «деньги не резервируются». Это
враньё того же сорта, что и зелёное «Крутится» при нуле показов (замечание З-6).
На это стоит отдельный тест.

Цвета ярлыков не переписаны, а сверяются с живым campaignStatusMeta Яндекса;
подпись tgStatusMeta берёт из той же таблицы TG_STATUS_LABELS — второй копии нет.

Тесты: +2 файла (telegram-karkas, telegram-balance-header), +26 тестов.
Поправлено 15 прежних — все про переезд формы во вкладку. Один из них
(«на Тексте объявления подсказки НЕТ») после переезда зеленел по ложной причине:
подсказки «нет» и у поля, которого нет вовсе — добавлена проверка, что поле есть.

Замер: 239 файлов экранов, 1813 тестов, 0 падений (было 237/1787); ESLint 0;
типы — те же 7 чужих ошибок, ни одной своей. PHP не трогали.

Пачка 4б (мастер шагов + живой счётчик охвата) НЕ делалась: у телеграма нет
серверной ручки оценки, охват возвращает только POST /campaigns, а она создаёт
черновик в базе. Нужна новая ручка — это работа с сервером.

На боевой НЕ выкачено. Глазами не принято — приёмка в пачке 5.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 17:05:02 +03:00
Дмитрий 929b0a744b fix(схема): задвоенный указатель на сделках сведён именем — на бою его не было
Задача звучала «снеси на боевой базе deals_deleted_at_index, оставь
deals_deleted_at_idx». Замер боевой базы (pg_index на rw-endpoint кластера
c9q2cvtjpq3hgq6l0r96) показал обратное: там указатель ОДИН, и зовут его как раз
deals_deleted_at_index — второго на бою нет и не было. Исполнение задачи как
записано снесло бы единственный указатель по deleted_at на самой горячей таблице
портала. Боевая база не тронута ни одной изменяющей командой.

Дубль жил в сборке, а не на бою: db/schema.sql заводил индекс БЕЗ имени
(PostgreSQL звал его deals_deleted_at_idx), а миграция 2026_06_17_120000 — такой
же с именем. schema.sql исполняется первой миграцией, поэтому каждая собранная с
нуля база (местная dev, все тестовые) получала два, и любое изменение сделки
писало в оба. Боевая собиралась иначе.

Починка двумя половинами. В каноне имя задано явно и совпадает с боевым —
миграция 2026_06_17_120000 идёт с IF NOT EXISTS и на собранной базе становится
пустой. Новая миграция 2026_08_02_100000 убирает лишний deals_deleted_at_idx из
уже собранных баз; на бою это пустая операция. Она сверяет не только имя, но и
определение: _idx выдано PostgreSQL автоматически и в чужой базе могло достаться
другому индексу.

Приёмка вырезанием — порознь по половинам, потому что первая попытка была
негодной: вырезал имя из канона, а сборка всё равно дала правильный ответ, её
вылечила вторая половина. Замер «канон + миграция 17.06» без новой миграции: на
сломанном каноне 2, на исправленном 1. Полная сборка с нуля 149/149 DONE — один
указатель. В тестовой базе шесть половинок партиций прицеплены к выжившему,
осиротевших ноль. Сверка канона с базой — все четыре счётчика ноль. Статанализ
ноль и проверен красным: подложка назвала именно новый файл. Полный набор PHP
после правки — 4122 теста, 4118 прошли, 4 пропущено, 0 падений.

Журнал схемы v9.34, версия канона v8.87. В промт смене 9 вписано, что прежняя
инструкция была вредной и исполнять её нельзя, плюс два правила: утверждение
промта о боевом состоянии — гипотеза до замера, и вырезание доказывает что-то
только если вырезаны все пути к результату.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 16:16:54 +03:00
Дмитрий 4eac8d8ff1 docs(реклама): промт следующей смене — пачка 4, единый каркас телеграм-экрана
Accessibility (Pa11y live) / a11y (push) Has been cancelled
Разведка сделана живьём, а не по памяти, и вскрыла две мины, из-за которых
задание владельца «сделать как у Яндекса» нельзя выполнить в лоб:

1. Деньги у экранов РАЗНЫЕ. Яндекс берёт из рекламного кошелька (ad_wallets,
   баланс/заморожено/свободно), телеграм — общий баланс тенанта, причём
   frozen_rub отдаётся жёстко '0.00'. Вставить AdWalletHeader в телеграм —
   значит показать клиенту чужие деньги. Нужен тот же вид при своих данных.

2. Живого счётчика охвата без сервера не будет. У телеграма всего восемь
   ручек, оценки среди них нет; охват возвращает только POST /campaigns,
   а она создаёт черновик в базе. Дёргать её на каждое изменение поля —
   засорить базу. Нужна новая серверная ручка оценки.

Отсюда совет владельцу: разбить пачку 4 надвое — 4а чистый фронт (вкладки,
карточка денег, единый вид карточек кампаний), 4б мастер шагов и живой
счётчик (требует бэкенда). Иначе смена кончится посередине.

Названа и цена: на нынешнюю разметку телеграм-экрана завязано 45 тестов.

Ориентиры даны с номерами строк, сверенными с живыми файлами; список меток
разметки проверен поимённо — одну (tg-headline) я написал по памяти неверно,
настоящая tg-ad-headline, поправлено до коммита.

Прежний промт помечен отработанным, с указателем на этот.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 16:14:54 +03:00
Дмитрий 6ae6457b34 feat(реклама): пачка 3 — подсказки «?» у 16 непонятных полей на обоих экранах
Accessibility (Pa11y live) / a11y (push) Has been cancelled
SAST — Semgrep / Semgrep SAST scan (push) Has been cancelled
Замечание владельца: поля, смысл которых человеку взяться неоткуда (категория ОРД,
порог 367, частота показов, «Заморожено»/«Свободно»), решают, сколько клиент заплатит,
а объяснения к ним нет. Клиент либо гадает, либо не трогает.

Подсказка стоит у 16 полей: телеграм-форма (дни сделок, лимит на объявление, ОРД,
картинка/видео), форма пересдачи (ОРД), панель авто-рекламы (порог, бюджет, дневной
лимит), мастер Яндекса (оба конца периода контактов, срок показа, частота — на шаге и
в сводке, цена за тысячу), кошелёк (заморожено, свободно).

Тексты собраны в ОДИН файл composables/podskazkiReklamy.ts — 13 подсказок. Поля ОРД и
«Частота показов» стоят в разметке дважды, и в пачке 2 мы уже разгребали ровно этот
класс беды: две копии подписей статусов разъехались, клиент читал на двух экранах
разное. Теперь физически не разъедутся. Про ОРД написано словами владельца из З-4.

На очевидные поля («Текст объявления», «Ссылка», «Кошелёк») подсказки нет намеренно —
шум, из-за которого перестают читать нужные; на это стоит отдельный тест.

Язык стерегут датчики: ни одной латинской буквы (значит ни CPM, ни имён из кода),
запрет жаргона, длина 40–200 знаков, два-три предложения.

В поле ввода подсказка вешается слотом append-inner, а не label: метка у Vuetify
плавающая, при щелчке уезжает вверх и знак вопроса уехал бы с ней.

Тесты: podskazki-reklamy.spec.ts (тексты, 8) + podskazki-na-ekranah.spec.ts
(привязка к полям, 8). Привязка проверяется честно — селектором «подсказка внутри
своего поля», а не «есть где-то на экране».

Проверено: 237 файлов экранов, 1787 тестов, 0 падений; ESLint 0; проверка типов — те
же 7 чужих ошибок, что и были. Глазами НЕ принято, на боевой НЕ выкачено.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 15:54:36 +03:00
Дмитрий a5daae0e41 docs: промт смене 9 — что ждёт разрешения, что осталось и семь новых грабель
Живой промт теперь 2026-08-02-PROMT-podgotovka-k-produ-8.md. Прежний помечен
в шапке как отработанный, чтобы следующая смена не взяла не тот файл.

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

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

Отдельно записано, что полный набор тестов PHP в смене 8 не гоняли — рабочий
код на PHP не трогали, и число для сравнения надо снять заново.

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

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 15:29:20 +03:00
Дмитрий 06a56e1932 docs(реклама): промт следующей смене — пачка 3, подсказки «?» на обоих экранах
Accessibility (Pa11y live) / a11y (push) Has been cancelled
Новый промт 2026-08-04-PROMT-podskazki-na-ekranah.md: компонент HelpHint готов и
уже живёт в отделе продаж, на рекламных экранах не подключён ни разу. Поля, у
которых человеку смысл взяться неоткуда, названы поимённо со строками — телеграм-
экран, панель авто-рекламы, мастер Яндекса, кошелёк; строки сверены с живыми
файлами. Отдельно правило «не вешать вопрос на очевидное» — иначе перестают
читать и нужные.

Прежний промт (пачка 2) помечен отработанным с указателем на новый.

В замечаниях: вопрос про числа показов телеграм-кампании переведён из открытого в
отложенный — владелец 02.08: «показывает, надо только запустить и начнёт
показывать — не обращай внимание пока». Помечено «по своей инициативе не трогать»,
чтобы следующая смена за этим не гналась.

Новые ловушки из смены: причёсывалка переписывает ВЕСЬ файл, если он не был
причёсан до тебя (телеграм-экран дал 296 строк вместо 24 — датчик `git diff -w`);
итоги группировки не модели, нужен `->toBase()`.

Код не тронут. Пачки 1 и 2 по-прежнему на боевой НЕ выкачены.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 15:18:42 +03:00
Дмитрий 7e059e194b docs: в промт смене 8 вписан её итог — решения владельца и пять новых грабель
Владелец закрыл все три развилки: описание базы остаётся в двух файлах;
задвоенный указатель на сделках сносить ОТДЕЛЬНЫМ окном и с отдельного
разрешения — ещё не сделано; пять правил доступа без NULLIF не трогаем.

Работы Б и разметка и Г и линтер интерфейса помечены закрытыми, с числами.
Отдельно исправлено число из прежнего промта: «8156 замечаний в 626 файлах»
было чужим охватом — туда попали вендоренные плагины и кэши. Наш охват —
140 файлов, и в них было 237 замечаний.

Пять новых грабель в общие правила:
- автоправка сторожа умеет НАВРЕДИТЬ, читать её diff по строкам с содержанием;
- git stash push с путями и последующий pop правки не вернул, доставать поимённо;
- replace_all бьёт по всем совпадениям, включая третье, о котором не знал;
- число из промта — не замер, спрашивать «что именно проверяет наш сторож»;
- строгий тип сперва УХУДШАЕТ счётчик, доводить до конца и мерить до и после.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 15:12:40 +03:00
Дмитрий 0af5c5696f chore+fix: разметка документации и «неизвестный тип» в интерфейсе убраны в ноль
РАЗМЕТКА ДОКУМЕНТАЦИИ. Проверка сторожа давала 237 замечаний в 21 файле — стало
0 во всех файлах под учётом git. Пустые строки вокруг списков и заголовков
поправлены автоматом в 12 файлах, два файла пришлось делать руками, потому что
автоправка там навредила:
- в описании починки инцидента номера шагов 1…7 — это ИМЕНА, на них ссылается
  сам текст «из шага 2»; автоправка перенумеровала их в 1,1,1,1,2 и сломала
  ссылки. Вывел ровно этот файл из-под одного правила, объяснение — в самом
  файле, остальные правила действуют.
- в протоколе архитектуры автоправка склеила «R-* / DR-*» в «R-*/ DR-*».
  Вместо этого обозначения взяты в обратные кавычки — смысл сохранён.
Семь файлов не тронуты: они вне учёта git — два в папке второй смены и старые
черновики в корне.

ИНТЕРФЕЙС. «Неизвестный тип» убран целиком: было 123 замечания, стало 0.
- Навигация автоподбора описана ОДИН раз — app/resources/js/views/autopodbor/nav.ts.
  Раньше девять экранов повторяли описание с «неизвестным типом», а ещё три
  описывали его каждый по-своему и не полностью.
- Чтение ошибки от сервера собрано в один помощник — app/resources/js/api/errors.ts,
  с проверками. Он намеренно читает ФОРМУ ответа, а не полагается на axios:
  тесты экранов подсовывают ошибку простым объектом той же формы, и экран обязан
  вести себя в тестах так же, как в бою.
- В тестах вместо «неизвестного типа» — именованный доступ exposed<T> и полные
  заготовки: app/tests/Frontend/support/. Каждый тест теперь объявляет ровно те
  поля, до которых дотягивается, и опечатка в имени снова становится ошибкой,
  а не молчаливым undefined. Заодно сняты 14 построчных отключений правила.
- Разведены задвоенные имена в шаблонах: в таблице сделок в одном шаблоне жили
  ДВЕ разные функции isSelected — одна берёт номер сделки, другая строку таблицы.
- Убрана мёртвая функция dirLabel и три неиспользуемые переменные.

ПРОВЕРКА ТИПОВ: было 7 ошибок, стало 5. Две унаследованные закрылись попутно —
более строгие заготовки вскрыли нехватку обязательного поля elements. Оставшиеся
пять были до меня и не в моих строках.

ПРИЁМКА. Разметка проверена вырезанием: подложил поломку обратно — сторож её
поймал, вернул — снова чисто. Полный набор тестов интерфейса: 233 файла,
1750 тестов, 3 пропущено, 0 падений. Рабочий код на PHP не тронут.

NB: LEFTHOOK_EXCLUDE=cspell — тем же приёмом и по той же причине, что и в записи
e0223de4, которой эти шесть файлов исследования заводились в репозиторий: сырой
внешний материал с чужой терминологией. Замерено: до моих правок сторож
орфографии находил в них ровно столько же слов, сколько после — я не добавил
ни одного. Разметка в этих файлах теперь проходит начисто, отключена ТОЛЬКО
орфография и только на эту запись; остальные семнадцать сторожей отработали.
Решение владельца, спрошено отдельно.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 15:07:24 +03:00
Дмитрий a2496dfe61 feat(реклама): честные статусы кампаний — экран больше не зовёт «Крутится» рекламу с нулём показов
Accessibility (Pa11y live) / a11y (push) Has been cancelled
SAST — Semgrep / Semgrep SAST scan (push) Has been cancelled
Замечание владельца З-6: «почему статус крутится, когда она отклонена?»
Живой замер кампании #6 на бою: статус running, зелёное «Крутится», показов
доставлено НОЛЬ, потрачено 0 ₽, заморожено 3 333,36 ₽ — третьи сутки.

Главное, что вскрылось: экран физически не мог показать правду. Список кампаний
отдавал estimated_impressions («сколько обещали») и не отдавал delivered_impressions
(«сколько было»), хотя колонка есть. Чисел принятых/отклонённых объявлений тоже не
было. Поэтому чинили с сервера, а не с подписей.

- сервер отдаёт факты: delivered_impressions + счётчики объявлений (всего/принято/
  отклонено), ОДНИМ запросом на весь список — на N+1 поставлен отдельный датчик;
  те же счётчики доезжают и в отчёт по кампании;
- считаются только включённые в показ: снятое галочкой в Яндекс не уезжает и в
  знаменателе «2 из 15» ему не место;
- ярлык по фактам: running при нуле показов → «Принято, показов пока нет»
  нейтральным цветом; пошли показы → зелёное «Крутится»; часть отклонена →
  приписка «часть объявлений отклонена — 2 из 15»; все → красное «Отклонено»;
- правило перехода статусов НЕ тронуто: на rejected висит возврат заморозки
  (AdWalletService::release, «ВЫХОД 2»). Чинили то, что видит человек;
- подписи собраны в один файл (composables/campaignStatusMeta.ts). Их было ДВЕ
  копии — в списке и в отчёте — и они уже разъехались; разъехавшиеся копии и есть
  та разница между экранами, на которую жалуется владелец;
- телеграм-экран: подписи вынесены отдельно и приведены к тем же словам
  («На модерации в МТС» ↔ «На модерации в Яндексе», «Отклонено» на обоих);
  голое «Запущена» → «Запущена в кабинете МТС».

Граница честности: у телеграм-модуля нет счётчика показов ВООБЩЕ — actual_cost_rub
в таблице есть, но её не пишет ни одна строка кода. Поэтому там нельзя сказать ни
«крутится», ни «показов пока нет»: это была бы выдумка того же сорта. Записано
открытым вопросом владельцу в файле замечаний.

Проверено: 435 тестов рекламы Яндекса, 281 телеграма, 1771 тест экранов
(235 файлов, 3 пропущено), статанализ 0, формат чист. Новых тестов 18.
Глазами НЕ принято (приёмка — пачка 5), на боевой НЕ выкачено.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 14:53:57 +03:00
Дмитрий 9d3dc73608 docs(реклама): промт следующей смене — пачка 2, честные статусы кампаний
Accessibility (Pa11y live) / a11y (push) Has been cancelled
Отдельный файл вместо правки прежнего: тот отработан, на нём оставлен указатель.

Главное, что нашёл при подготовке промта и чего в замечаниях не было: экран
физически НЕ МОЖЕТ показать правду о показах. AdvertisingCampaignController:57
отдаёт списку кампаний estimated_impressions — сколько обещали, — а поля
delivered_impressions в ответе нет вовсе, хотя в таблице ad_campaigns оно есть.
Числа принятых и отклонённых объявлений тоже не уходят. Значит пачка 2
начинается с сервера, а не с подписей на кнопках.

Отдельно записано, что правило в SyncCampaignModerationJob менять НЕЛЬЗЯ: на
статусе rejected висит возврат заморозки клиенту. Чинить надо то, что видит
человек, а не машину статусов.

Ярлыки живут в трёх местах — CampaignList.vue, CampaignReportDialog.vue и
STATUS_LABELS телеграм-экрана; все три названы поимённо, иначе получится ровно
та разница между экранами, на которую владелец и жалуется.

Из ловушек добавлена своя ошибка этой смены: тесты фронтенда лежат в
app/tests/Frontend, а я искал их в resources и объявил, что тестов Vue нет
вовсе. Их 232 файла.

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