Commit Graph

3843 Commits

Author SHA1 Message Date
Дмитрий c6dca0d442 docs выкат: в промт вписана мина, которой в нём не было - полный выкат откатывает чужое
Первая редакция промта вела следующую смену прямо в аварию: шаг «выкат файлов»
без единой проверки, насколько ветка отстала от основной. На 01.08 отставание
было 7 коммитов, и это блок менеджеров на боевом. Залей папку целиком - и
воронка продаж с корзиной и фильтрами исчезла бы с боевого сайта.

Вписано правило замера: правое число в git rev-list --left-right --count
обязано быть 0 перед выкатом, иначе сперва сведение и повторный прогон.

Отмечено, что основная продолжает двигаться, и сведение надо повторять
непосредственно перед выкатом, а не заранее.

Дописаны две ловушки. Первая: столкновение номеров журнала схемы git НЕ
показывает как конфликт, молча склеивает две записи с одним номером - дан
готовый датчик на дубли. Вторая: фронтовый набор был красным пять дней, потому
что его не гоняли целиком, - велено гонять vitest целиком, а не по файлу.

Пункт про журнал схемы переписан с «дописать v9.31» на «сделано, номер v9.32».

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 14:58:09 +03:00
Дмитрий 4efd3d3329 merge: подтянул воронку продаж с основной - иначе выкат откатил бы блок менеджеров
Владелец спросил, не потрём ли мы соседнюю смену. Проверил - потёрли бы.
Наша ветка отстала от основной на 7 коммитов, и это ровно сегодняшняя работа
по воронке продаж: корзина, журнал движений карточки, фильтр по датам, дробь
у Отказа. Выкат по плану был ПОЛНЫЙ заливкой файлов - боевой сайт получил бы
портал без всего этого. Соседи выкатывали точечно, шестью файлами, поэтому
у них и не рвануло.

СТОЛКНОВЕНИЕ НОМЕРОВ ЖУРНАЛА СХЕМЫ, ТРЕТИЙ РАЗ ЗА СУТКИ. Час назад я записал
v9.31 за заголовок объявления. В это же время соседи записали v9.31 за корзину
воронки и влили в основную. Обе записи настоящие. Git конфликта НЕ ПОКАЗАЛ -
просто склеил две записи с одинаковым номером в один файл, молча. Наша
подвинута на v9.32, боевая осталась на своём номере.

Пересобран список исключений статанализа: после сведения он врал счётчиком
90 против 95 и не знал новых файлов соседей. Проверено, что пересборка ничего
настоящего не спрятала - все 16 замечаний были одного вида, ложная тревога
PendingCalls, плюс одно про форму данных внутри задания робота, тоже в тесте.

ПОЧИНЕН ВРУЩИЙ СТОРОЖ ВИТРИНЫ РЕКЛАМНЫХ КАНАЛОВ. Тест утверждал, что настоящий
экран есть только у Яндекса, а Телеграм получил свой ещё 27.07 - сторож пять
дней держал фронтовый набор красным, и этого никто не видел, потому что фронт
целиком не гоняли. Список настоящих каналов ведётся руками намеренно: смысл
сторожа - поймать случайно прописанный маршрут у канала-заглушки.

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

Замеры после сведения:
- портал 4091 тест, 4087 прошло, 0 падений, 0 ошибок
- фронт 232 файла, 1739 тестов, 0 падений
- статанализ 0 замечаний
- робот 130 из 130

На бой ничего не выкачено. Боевой сайт не тронут.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 14:54:04 +03:00
Дмитрий 1bd33c09cf fix: возврат чужой работы, которую откатило моё сведение
SAST — Semgrep / Semgrep SAST scan (push) Has been cancelled
Accessibility (Pa11y live) / a11y (push) Has been cancelled
Перед слиянием с main я откатил 4 файла к своей старой версии, чтобы
сдвинуть с места застрявшее слияние — и слияние закрепило этот откат.
Пострадала починка от 29.07: чтение связок поставщика из pivot (без неё
5 из 9 работающих проектов показывали жёлтое «Готовим к запуску» при
живом заказе) и два набора тестов вебхука/сверки CSV.

Файлы возвращены к состоянию main 7361d1ae3. На боевой откат НЕ уезжал —
выкат был точечный, только 6 файлов воронки продаж; проверено на живом
сервере: починка там на месте.
2026-08-01 14:21:21 +03:00
Дмитрий a93cc12086 merge: свёл заголовок объявления с веткой робота - обе половины телеграма в одной ветке
Ветки разошлись: у соседней не было трёх правок тестов от 01.08, у нашей не
было заголовка объявления. Ни одна не содержала другую. Сведены в нашу.

Конфликт был ровно один и в автогенерённом файле docs/observer/STATUS.md -
время последнего обновления и список процессов. Взята наша сторона, хук всё
равно перегенерирует. В коде столкновений не было ни одного, в списке
исключений статанализа тоже.

Дописана запись журнала схемы v9.31 на их миграцию ad_headline - в исходном
коммите 32df3329 записи не было вовсе. Номер следующий свободный после v9.30.

Замеры после сведения:
- полный прогон 4069 тестов, 4065 прошло, 0 падений, 0 ошибок
- статанализ 0 замечаний
- тесты робота 130 из 130
- фронтовый тест экрана 23 из 23
- стиль php по затронутым файлам чисто

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

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 14:18:19 +03:00
Дмитрий bf03e6204e docs выкат: промт следующей смене на выкат телеграм-рекламы на бой
Владелец решил катить. Промт написан под это, а не под хвосты.

Главное, что вскрылось при подготовке: работа разошлась по ДВУМ веткам, и ни
одна не содержит другую. У соседней ветки tg-zagolovok нет наших трёх правок
тестов от 01.08, у нас нет их заголовка объявления. Обе от основной не
отстают. Поэтому первый шаг смены - свести ветки, иначе выкатим половину.

Выкат намеренно разрезан надвое. Портал можно катить сразу: переключатель
остаётся в старом положении, поведение не меняется, новые таблицы и канал
уезжают спящими. Робота включать некуда, пока не решено, на какой машине он
живёт - МТС не пускает адреса дата-центров, это упирается в обращение 31074.

В промте прописаны обязательные пред-полётные: журнал схемы для их миграции
записью v9.31 - её нет вообще, а это то самое правило, из-за нарушения
которого ночью столкнулись номера; пересборка списка исключений статанализа
после сведения; проверка их нового теста на добавленный запрет выхода
в интернет; ПЕРЕзапуск service_bypass_policies, иначе канал робота увидит
ноль заданий молча.

Приёмка - глазами в кабинете МТС, а не по журналу портала. Добавлены две
находки для владельца, ничего не тронуто, и одиннадцать ловушек.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 13:46:56 +03:00
Дмитрий 607b192a57 docs выкат: промт следующей смене на выкат телеграм-рекламы на бой
Владелец решил катить. Промт написан под это, а не под хвосты.

Главное, что вскрылось при подготовке: работа разошлась по ДВУМ веткам, и ни
одна не содержит другую. У соседней ветки tg-zagolovok нет наших трёх правок
тестов от 01.08, у нас нет их заголовка объявления. Обе от основной не
отстают. Поэтому первый шаг смены - свести ветки, иначе выкатим половину.

Выкат намеренно разрезан надвое. Портал можно катить сразу: переключатель
остаётся в старом положении, поведение не меняется, новые таблицы и канал
уезжают спящими. Робота включать некуда, пока не решено, на какой машине он
живёт - МТС не пускает адреса дата-центров, это упирается в обращение 31074.

В промте прописаны обязательные пред-полётные: журнал схемы для их миграции
записью v9.31 - её нет вообще, а это то самое правило, из-за нарушения
которого ночью столкнулись номера; пересборка списка исключений статанализа
после сведения; проверка их нового теста на добавленный запрет выхода
в интернет; ПЕРЕзапуск service_bypass_policies, иначе канал робота увидит
ноль заданий молча.

Приёмка - глазами в кабинете МТС, а не по журналу портала. Добавлены две
находки для владельца, ничего не тронуто, и одиннадцать ловушек.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 13:46:23 +03:00
Дмитрий 1acbaf9383 Merge remote-tracking branch 'gitea/main' into feat/prospects-manual-testing-kp
Accessibility (Pa11y live) / a11y (push) Has been cancelled
SAST — Semgrep / Semgrep SAST scan (push) Has been cancelled
# Conflicts:
#	docs/observer/STATUS.md
2026-08-01 13:43:02 +03:00
Дмитрий 325241c21d docs тесты: промт приведён в соответствие - хвосты Н1 и Х4 закрыты
Промт следующей смене врал в самом громком месте: хвост Н1 был записан как
срочный - "похоже, протух боевой ключ Яндекс Директа". Это оказалось ложной
тревогой, и написал её я сам, не проверив трёх фактов, опровергающих её
за пять минут. Ключ в тесте подставной, локально боевого ключа нет вовсе,
а в Яндекс тест ходил потому, что подбирал чужое задание из базы.

Раздел Н1 переписан: пометка "закрыт, тревога ложная", разбор настоящей
причины и урок про слово "срочно" в промте. Раздел Х4 помечен закрытым -
тест внешних сервисов больше не зависит от сети.

В Х2а вписан готовый датчик поиска файла, который гадит в базу, и ловушка
про то, что искать уборку словом нельзя. В Х2б у "чужого писателя" появилось
имя - параллельная смена в папке tg-zagolovok, она ходит в общую базу.

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

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

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 13:39:09 +03:00
Дмитрий ab381954df docs тесты: промт приведён в соответствие - хвосты Н1 и Х4 закрыты
Промт следующей смене врал в самом громком месте: хвост Н1 был записан как
срочный - "похоже, протух боевой ключ Яндекс Директа". Это оказалось ложной
тревогой, и написал её я сам, не проверив трёх фактов, опровергающих её
за пять минут. Ключ в тесте подставной, локально боевого ключа нет вовсе,
а в Яндекс тест ходил потому, что подбирал чужое задание из базы.

Раздел Н1 переписан: пометка "закрыт, тревога ложная", разбор настоящей
причины и урок про слово "срочно" в промте. Раздел Х4 помечен закрытым -
тест внешних сервисов больше не зависит от сети.

В Х2а вписан готовый датчик поиска файла, который гадит в базу, и ловушка
про то, что искать уборку словом нельзя. В Х2б у "чужого писателя" появилось
имя - параллельная смена в папке tg-zagolovok, она ходит в общую базу.

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

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

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 13:38:34 +03:00
Дмитрий 9ee4e9a79f feat(воронка продаж): корзина, дробь у «Отказа», фильтр по датам — экраны
- 12-я колонка «Корзина», результат «В корзину» просит только причину;
- в шапке «Отказа» дробь «4/2» с подсказкой «из них 2 после ручного
  тестирования»; при нуле дробь не рисуется — «69/0» это шум;
- у «Выслано КП» поле даты подписано «если договорились» и необязательно;
- орган фильтра по датам на ОБОИХ экранах, два режима + период.

Приёмка глазами пройдена живьём по всем семи пунктам, включая «Воронку отдела»
(снимки в docs/superpowers/screens/2026-08-01-korzina-filtry/priemka/).
Браузер поймал то, чего не видели тесты: при смене режима оставался прежний
период, и «что менялось за завтра» давало пустую доску — теперь период
возвращается к «Сегодня», на это заведён отдельный тест.

Прогон: сервер 1245/1245, фронт 1567/1567. Журнал схемы — v9.31.
2026-08-01 13:29:10 +03:00
Дмитрий d9f22d1353 fix тесты: убрана грязь между тестами и закрыт выход в интернет из прогона
Полный набор стал зелёным целиком: 4063 теста, 0 падений и 0 ошибок против
36 непроходящих до правок. Рабочий код не тронут - изменены только тесты.

Корень у пяти правок один: тест зелёный, а после себя оставляет мусор в базе,
и падают соседи. Три файла рекламы писали набело, без отката. Их записи -
задание роботу и проекты без источника лидов - переживали тест, и дальше
в том же прогоне их подбирали чужие проверки. Отсюда 16 падений в файле
про робота креативов и 13 в файлах про ночной слепок.

Тестам запрещён выход в интернет - Http::preventStrayRequests в TestCase.
До этого прогон физически ходил в живой кабинет Яндекс Директа, а поломка
маскировалась под "Invalid OAuth token", хотя ключ в тесте подставной.
Сторож принят вырезанием: со снятой правкой та же поломка называет себя
честно, с адресом запроса.

Он же вскрыл, что ProjectRuleNotificationTest слал живой запрос на удаление
проекта в кабинет поставщика crm.bp-gr.ru, оставаясь зелёным - ответ кабинета
тест не проверяет. Поставлена заглушка.

ExternalServiceDownAlertTest избавлен от зависимости от сети - закрыт хвост,
тянувшийся с 30.07: падал в общем прогоне, проходил в одиночку.

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

Проверено: полный прогон 4063/4059 зелёный, статанализ 0, код-стиль моих
файлов чисто. На бой ничего не выкачено.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 13:29:10 +03:00
Дмитрий 7aa54773c1 merge: свёл заголовок объявления с веткой робота — воронка продаж и опрос в одной ветке
Влил fix/robot-yandex-zamok (26 коммитов: сведение с основной, стадии воронки
«Тестирование ручное» и «Выслано КП», чтение вердикта и пересдача по опросу)
в ветку заголовка объявления.

Конфликт был один — docs/observer/STATUS.md, машинный файл наблюдателя
со столбиком часов процессов. Взята своя, более свежая версия; файл всё равно
перезаписывается хуком.

Замер после слияния (своя тестовая база liderra_testing_zag, прогон в тишине):
портал 4069 тестов, 4029 прошло, 16 упало; робот 130/130.
До слияния было 4063/4018/17. Тестов больше, падений меньше — стык чистый.
Падают шесть давних классов, не связанных с этой работой: CreativeRobotEndpoint
(11 — лезет в живой Директ и получает «недействительный ключ»), CreativeJobService,
InAppNotification, PhoneRegionSmoke, ProjectExtensions, SalesOverview.

Проверено отдельно: таблица заданий роботу не ограничивает список режимов
(обычная строка), поэтому новые «read-status» и «resubmit» миграции не требуют.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 12:48:02 +03:00
Дмитрий 016416502a feat(воронка продаж): корзина, КП без обязательной даты, фильтр по датам — сервер
- результат «В корзину»: одна причина, дата созвона стирается, платящего не выбросить;
- реклама на корзине встаёт СРАЗУ — явной веткой, а не случайно через unknown_stage;
- у «Выслано КП» дата созвона стала необязательной: бывает «пришлите на почту,
  если интересно — перезвоню». Врущий старый тест на 422 без даты поправлен;
- фильтр доски date_mode=todo|changed + период (добавлен вид «завтра»).
  «Надо сделать» — созвон в периоде ИЛИ просрочен, кроме отказа и корзины.
  «Менялось» — есть движение стадии ИЛИ запись разговора за период.

Прогон: 1245/1245 (отдел продаж + все юнит-тесты).
2026-08-01 12:41:24 +03:00
Дмитрий 53ebcd9fe0 feat(воронка продаж): журнал движений карточки + место под корзину
Стадию меняют два разных места — результат разговора менеджера и автопереезд
по деньгам. Джоба не оставляла следа вообще, поэтому «что менялось за день»
было не из чего построить. Запись движения перенесена в событие модели: один
шов на всех, включая любой будущий третий источник.

- sales_prospect_moves — журнал всех движений (append-only, с GRANT'ами ролям);
- prev_stage на карточке — откуда приехала (кормит счётчик «69/1» у «Отказа»);
- stage += 'trash' — место под колонку «Корзина».

Сторож проверен вырезанием: без записи движения краснеют 4 теста, в том числе
тест джобы и тест API.
2026-08-01 12:05:04 +03:00
Дмитрий 32df332901 fix(телеграм-реклама): заголовок объявления для рекламы сайта — кампания больше не встаёт
Приёмка глазами вскрыла: кабинет МТС требует «Заголовок объявления» (до 40 знаков),
когда в объявлении ссылка на САЙТ, а не на телеграм-канал. Робот про это поле не знал,
«Продолжить» молча не срабатывало, кампания вставала на шаге «Объявление» — в бою уже
ПОСЛЕ списания денег. Проверено живьём: 2234454 (сайт — встала) против 2234462 (канал —
дошла до подтверждения) и 2234490 (сайт с заголовком — дошла).

Портал спрашивает заголовок заранее, на создании черновика: обязателен только для
не-телеграмной ссылки (App\Support\TelegramLink), колонка ad_headline varchar(40),
поле на экране появляется по той же развилке. Робот заполняет его в кабинете.

Три ловушки, добытые живыми прогонами (описаны в коде):
- поле дорисовывается в ОТВЕТ на ссылку, с задержкой — надо ждать, а не спрашивать;
- под описание подходит несколько элементов — нужен .first();
- серая надпись внутри поля НЕ placeholder, а нарисованная подпись: поиск по атрибуту
  давал ноль совпадений при видимом на снимке поле. Опознаём по видимой надписи.

Тесты: робот 130/130, ClientTg 250/250, экран 23/23. Полный прогон бэкенда — те же
13 падающих классов до и после правки (ни одного в телеграм-части). В baseline
статанализа добавлен известный ложный класс Pest для нового файла тестов.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 11:42:46 +03:00
Дмитрий 2248cb7b79 docs(воронка продаж): промт для следующей сессии по корзине и фильтрам
Готовый текст, который владелец копирует в новую сессию: четыре куска работы,
порядок (вопросы → спека → план → код), мины и грабли окружения.
2026-08-01 11:41:41 +03:00
Дмитрий e1374fd967 docs(воронка продаж): зафиксирован запрос владельца — корзина, фильтры по датам, счётчик через дробь, КП без даты
Просьба записана дословно, три скрина описаны словами, заведена папка
docs/superpowers/screens/2026-08-01-korzina-filtry/ под сами картинки.
Собраны находки по коду (счётчик колонки, фильтры доски, отсутствие
истории стадий) и четыре вопроса владельцу с рекомендациями.
2026-08-01 11:40:46 +03:00
Дмитрий 164880f44a docs телеграм-робот: промт следующей смене после сведения веток
Прежний промт помечен устаревшим - в нём закрыт хвост Х1, изменилось состояние
ветки и у хвоста про тестовую базу нашёлся более грубый корень. Оставлен ради
разбора, как разруливать столкновение номеров журнала схемы.

В новом промте пять хвостов, и один из них новый и срочный: в полном прогоне
одиннадцать падений подряд с ответом живого Яндекс Директа "Invalid OAuth token".
Похоже, протух ключ доступа, а на бою в Директе запущена живая кампания с
замороженными деньгами. Порядок разбора - сначала посмотреть, а не чинить, и
доложить владельцу до любых действий с токеном.

По тестовой базе записан готовый прибор: сравнить число записей о применённых
миграциях с числом файлов миграций. Больше файлов - в базу лезет чужая рабочая
папка, меньше - сборка оборвалась. Замер в промте: на общей базе телеграм давал
42 из 244, на своей 244 из 244 два раза подряд.

Хвост про хрупкий тест внешних сервисов, возможно, отпал сам - в последнем
прогоне на своей базе он не упал. В промте написано, как это проверить и как
закрыть запись честно.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 03:41:49 +03:00
Дмитрий bba56f6a1c docs телеграм-робот: промт следующей смене после сведения веток
Прежний промт помечен устаревшим - в нём закрыт хвост Х1, изменилось состояние
ветки и у хвоста про тестовую базу нашёлся более грубый корень. Оставлен ради
разбора, как разруливать столкновение номеров журнала схемы.

В новом промте пять хвостов, и один из них новый и срочный: в полном прогоне
одиннадцать падений подряд с ответом живого Яндекс Директа "Invalid OAuth token".
Похоже, протух ключ доступа, а на бою в Директе запущена живая кампания с
замороженными деньгами. Порядок разбора - сначала посмотреть, а не чинить, и
доложить владельцу до любых действий с токеном.

По тестовой базе записан готовый прибор: сравнить число записей о применённых
миграциях с числом файлов миграций. Больше файлов - в базу лезет чужая рабочая
папка, меньше - сборка оборвалась. Замер в промте: на общей базе телеграм давал
42 из 244, на своей 244 из 244 два раза подряд.

Хвост про хрупкий тест внешних сервисов, возможно, отпал сам - в последнем
прогоне на своей базе он не упал. В промте написано, как это проверить и как
закрыть запись честно.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 03:39:14 +03:00
Дмитрий 72db586a12 merge: свёл ветку телеграм-робота с основной, разрулил столкновение номеров журнала схемы
Основная ветка ушла вперёд на 23 коммита - приехала чужая работа по воронке
продаж, вебхуку поставщика и сверке CSV. Конфликтов было два.

1. Журнал схемы db/CHANGELOG_schema.md. В обеих ветках лежала запись v9.28
   от 31.07 про разное: у нас очередь заданий роботу, у них стадии воронки
   "Тестирование ручное" и "Выслано КП". Обе записи настоящие, поэтому ни одна
   не выброшена: боевая v9.28 осталась на своём номере, наши две подвинуты на
   v9.29 очередь заданий и v9.30 грант админ-роли. Поправлены ссылки на номер
   в шапках двух миграций, перекрёстные ссылки внутри самих записей и врезка
   "Перенумерация" вверху журнала - там теперь описан и этот случай.
2. docs/observer/STATUS.md - файл авто-генерируемый, взята версия основной
   ветки, хук перепишет его сам.

Заголовок db/schema.sql не трогали: там своя нумерация v8.85, ни одна из
веток её не двигала.

Плюс одна настоящая находка статанализа, приехавшая с чужой работой: у метода
SupplierPortalClient::fetchDeliveredLeads в описании возвращаемого набора не
было поля tag, хотя код его уже возвращает и чужой же тест его ждёт. Дописал
одно поле в описание, логику не трогал. Список исключений статанализа пересобран
- разъехались счётчики ложного класса Pest от новых строк в чужих тестах.

Проверено на ОТДЕЛЬНОЙ тестовой базе liderra_testing_tgmerge:
  телеграм на портале  244 из 244
  робот                124 из 124
  статанализ           0 замечаний
  код-стиль            чисто
  полный прогон        4063 теста, 4018 прошло, 17 упало

До сведения было 4039 тестов и 20 падений - тестов стало больше, падений
меньше. Ни одно падение не касается телеграма или журнала схемы: 13 из 17 в
рекламном модуле Яндекса, из них 11 - живой отказ авторизации Яндекс Директа,
остальные счётные, от накопленных за прогон данных.

Отдельно вскрылось при проверке: общая тестовая база liderra_testing испорчена -
в ней 180 записей о применённых миграциях при 146 файлах, то есть в неё пишет
не только эта рабочая папка. Из-за этого сборка базы срывалась на первом же
шаге. Отдельная база всё вылечила. Это хвост Х2б, лечение в репозиторий не
вносил - решение владельца.

На бой ничего не выкачено, переключатель TG_ROBOT_TRANSPORT остаётся в process.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 03:23:36 +03:00
Дмитрий 24882ac16e docs телеграм-робот: промт следующей смене — добить хвосты
Работа по роботу закончена и запушена, тестовая база вылечена наполовину,
на бой ничего не выкачено. В промте пять хвостов по убыванию важности.

Главный и срочный: в обеих ветках лежит запись v9.28 журнала схемы от 31.07
про разное — у нас таблица заданий роботу, в основной стадии воронки продаж.
При сведении веток это конфликт, и обе записи должны остаться разными
номерами, а не выбором одной стороны. Ветка отстала на 23 коммита.

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

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

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

Что сделано по плану docs/superpowers/plans/2026-07-31-tg-poll-read-status-resubmit.md:

- два новых режима задания: чтение вердикта модерации и пересдача;
- применение вердикта вынуто из джоба в TelegramModerationVerdictApplier,
  применение итога пересдачи - в TelegramResubmitResultApplier; оба переноса
  построчные, денежная логика не менялась ни в одном символе;
- у обоих джобов появилась развилка по каналу: на опросе задание ставится,
  робот не запускается;
- приёмщик отчёта различает режимы - иначе отчёт о чтении вердикта применился
  бы как отчёт о запуске и сдвинул кампанию не туда;
- номера телефонов больше не выдаются режимам, которым они не нужны: чтению
  вердикта и пересдаче аудитория не требуется, она у кампании уже есть;
- у робота развилка по режиму вынесена в отдельный src/poll-plan.js, чтобы
  её можно было проверять без браузера и без сети;
- сторож прав на бою: роль портала обязана иметь право ставить задания.

Новых миграций и новых прав НЕ понадобилось: все три места ставят задание под
подключением по умолчанию, то есть под ролью портала, у которой права уже есть.
Схема БД не менялась, запись в CHANGELOG не требуется.

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

Проверено:
  телеграм на портале  244 из 244 (было 219, старые тесты в том числе)
  робот                124 из 124 (было 120)
  статанализ           0 настоящих замечаний
  полный прогон        4039 тестов, 3995 прошло, 20 упало

Из 20 падений 19 - те же давние, что были до работы. Двадцатое -
ExternalServiceDownAlertTest, в одиночку проходит 3 из 3 и вместе с телеграм-
тестами тоже; падает только в полном прогоне от накопленных данных. Это
известная слабость: у большинства файлов нет изоляции между тестами.

Заодно сборка тестовой БД переведена с migrate:fresh на связку
db:wipe --drop-types + migrate: первая спотыкалась на призрачном типе
legal_entities, вторая на тех же состояниях отрабатывала без отказов.

На бой не выкачено, переключатель TG_ROBOT_TRANSPORT остаётся в process.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 19:03:45 +03:00
Дмитрий ceb88ab102 fix тесты: тестовая база перестала рваться посреди прогона
Корень оказался в Laravel: в конце каждого теста он проверяет, осталось ли
соединение в своей черновой транзакции, и если тест её закрыл сам - сбрасывает
признак "база собрана". Следующий тест пересобирает базу целиком прямо посреди
прогона, снося её под всеми остальными. В проекте полно кода со своими
транзакциями, поэтому срабатывало примерно раз на восемь прогонов.

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

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

Замер до и после, полный набор портала:
  без правки           3175 прошло, 837 упало
  с правкой, прогон 1  3971 прошло, 19 упало
  с правкой, прогон 2  3971 прошло, 19 упало, списки совпали дословно

Оставшиеся 19 - давние и не от базы: 11 падают на неверном токене Яндекса,
одна на типе исключения, семь счётных от накопления данных между тестами.

Плюс план перевода чтения вердикта и пересдачи на опрос - отдельным файлом,
код по нему ещё не писался.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 16:40:30 +03:00
Дмитрий 7361d1ae33 Merge branch 'feat/prospects-manual-testing-kp' into deploy/prospects-to-main
Accessibility (Pa11y live) / a11y (push) Has been cancelled
SAST — Semgrep / Semgrep SAST scan (push) Has been cancelled
2026-07-31 16:40:10 +03:00
Дмитрий 9165cbb088 fix(воронка продаж): досохранена правка модели — новые поля в fillable
Правка потерялась при замере статанализа: файл уходил в стэш и вернулся
несохранённым, а коммит Task 1 прошёл без него. Поймано сверкой боевого
с собранной папкой перед выкатом, а не тестами — тесты идут по рабочей
папке и правку видели.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 16:39:23 +03:00
Дмитрий a57a70268d merge: воронка продаж — стадии «Тестирование ручное» и «Выслано КП» в main
Accessibility (Pa11y live) / a11y (push) Has been cancelled
SAST — Semgrep / Semgrep SAST scan (push) Has been cancelled
Конфликты только в документах: журнал «мозга» взят из main, запись v9.28
переставлена поверх боевого журнала схемы. Код не пересекался.

Сторож слоёв проверен отдельно в рабочей папке: 0 нарушений.
Сторож секретов gitleaks отработал по-настоящему.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 16:28:34 +03:00
Дмитрий e918bab8c2 docs(воронка продаж): итог работы и грабли в файл передачи
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 16:11:56 +03:00
Дмитрий d54ceed4ed docs(схема): запись v9.28 о стадиях Тестирование ручное и Выслано КП
В спеке §3 уточнено по факту кода: нормализатор отдаёт номер с плюсом,
а хранится он без плюса — плюс срезаем.

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

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 16:11:09 +03:00
Дмитрий 975075384a feat(воронка продаж): напоминание, куда отправляли тест и КП
Серая строка только для чтения в блоке результата — менеджеру перед звонком
не нужно лезть за этим в историю разговоров.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 15:54:29 +03:00
Дмитрий f4ed24e3cc feat(воронка продаж): два новых результата разговора в карточке
Выбор канала + поле значения, подпись поля меняется по выбору. Дата созвона
обязательна у обоих. Платящему клиенту результаты не предлагаются.
«Зарегистрировался» остаётся доступен с новых стадий — иначе из них не выйти.

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

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 15:50:53 +03:00
Дмитрий 79697217aa fix(прогрев): новые стадии воронки не выключают рекламу молча
До правки decide на незнакомой стадии возвращал unknown_stage — реклама на
такие фирмы встала бы без единого сообщения. Теперь обе новые стадии идут
по правилу переговоров: крутим до созвона, потом ещё срок просрочки.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 11:15:42 +03:00
Дмитрий 569010318b feat(воронка продаж): новые результаты запрещены платящему клиенту
Сторож проверен вырезанием: с убранной защитой тест краснеет, с возвращённой
зеленеет. Зелёный тест, которого не видели красным, ничего не охраняет.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 11:13:44 +03:00
Дмитрий 589f418e61 feat(воронка продаж): результат разговора «Выслано КП» на сервере
Канал из пяти: почта, ватсап, телеграм, макс, другое. Адрес/ник сохраняем
как ввёл менеджер — телеграм-ник и почта к телефонному виду не приводятся.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 11:10:26 +03:00
Дмитрий 16bb555054 feat(воронка продаж): результат разговора «Ручное тестирование» на сервере
Телефон приводится к виду 79… без плюса — как контакты карточки и как
принимают Яндекс Аудитории. Неразобранный номер отказывает, карточку не двигает.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 11:07:50 +03:00
Дмитрий 7439d1980c feat(воронка продаж): две новые колонки на доске — Тестирование ручное и Выслано КП
Список стадий правится в двух местах сразу: PROSPECT_STAGES на фронте и
STAGES в контроллере. Расхождение дало бы пустую колонку.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 11:03:44 +03:00
Дмитрий 977d54bc53 feat(воронка продаж): база под стадии Тестирование ручное и Выслано КП
Стадии manual_testing и kp_sent в CHECK + 4 nullable-колонки под их поля.
Откат и повторный накат проверены на своей тестовой базе измерением колонок.

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

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 10:45:10 +03:00
Дмитрий 30a62570b3 docs(пилот): анти-затирание для правки вебхука/сверки от 31.07
Accessibility (Pa11y live) / a11y (push) Has been cancelled
На 31.07 в gitea 8 живых веток, и ни в одной нет отсрочки добора и журнала вебхука
(отставание от main 6..3393 коммитов). Выкат бэкенда из любой из них молча вернёт
старые файлы. В запись добавлены: предупреждение, требование влить main перед
выкатом и две команды проверки после выката. Тот же класс потери, что 09.07 и 29.07.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 10:26:38 +03:00
Дмитрий 17de8eb08a docs(схема): подтянул журнал схемы и словарь орфографии из main
Ветка отставала от main на 161 коммит; из файлов моей задачи устарел
только журнал схемы. Беру канонический вариант из main, чтобы номер новой
версии не столкнулся с занятыми (верхняя в main — v9.27). Словарь cspell
подтянут тем же движением — без него сторож орфографии не пропускает
свежие строки журнала.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 10:18:29 +03:00
Дмитрий c67ed26691 docs(вебмастер): доложена схема-карта, на которую ссылался протокол
Accessibility (Pa11y live) / a11y (push) Has been cancelled
SAST — Semgrep / Semgrep SAST scan (push) Has been cancelled
Документ ПРОТОКОЛ-создание-вебмастера.md (коммит e0223de4) ссылался на
схему ПРОТОКОЛ-вебмастер-КАРТА.html, а сама схема в репозиторий не попала —
лежала на диске незакоммиченной с 22.06.2026. Из-за этого сторож ссылок
краснел на чистом main и блокировал любой пуш: 2 битые ссылки из 2242.

Содержимое схемы не менялось, кроме косметики по правилу проекта:
белый цвет записан полностью (#ffffff вместо #fff), 8 мест, вид тот же.
2026-07-31 10:17:48 +03:00
Дмитрий f3d9812e6c fix реклама Яндекса: принятые объявления сами уходят в показ — модерация приняла, это ещё не показ
У объявления два независимых признака: вердикт модерации (Status) и идёт ли
показ (State). Созданное программой объявление рождается выключенным, и
включение кампании его не поднимает — команды поднять сами объявления в коде
не было вообще.

Живая приёмка 31.07.2026, кампания 713175197: принята и включена, Яндекс на
уровне кампании пишет «Идут показы», а внутри все 15 объявлений выключены.
Показов ноль при замороженных у клиента 3333.36 руб.

Опросчик теперь чинит по СОСТОЯНИЮ из ответа Яндекса, а не по переходу
статуса: собирает объявления со статусом «принято» и выключенным показом и
зовёт ads.resume. Так самовосстанавливается и уже запущенная кампания — по
переходу она осталась бы выключенной навсегда, потому что «принято» у нас
записано давно. Состояние Яндекс присылал в каждом ответе и раньше, опросчик
его просто выбрасывал.

Проверено вырезанием: без починки новый тест краснеет. Реклама 349 из 349.
2026-07-31 10:17:44 +03:00
Дмитрий 9df7846fd6 fix реклама: минимум площадки считается по длине периода, а созданные объявления сами уходят на модерацию
Живой запуск кампании на бою 30.07.2026 вскрыл две поломки, которых вчерашняя
починка не видела. Обе — из класса молчаливых: портал рапортует успех, а в жизни
ничего не происходит.

ПЕРВАЯ. Минимум Директа НЕ постоянный.

Вчера мы приняли живой отказ «Budget for this period cannot be less than 600 rub.»
за постоянный порог и зашили 600 ₽ в настройку. Сегодня та же кампания на неделю
получила отказ «cannot be less than 2400 rub.» — проверка её пропустила, и клиент
снова увидел английский текст.

Две точки дали правило: минимум = 300 ₽ за каждый календарный день периода,
считая оба края.
   период 30.07-31.07, 2 дня → 600 ₽  = 2 × 300
   период 30.07-06.08, 8 дней → 2400 ₽ = 8 × 300

Теперь порог считается от периода показа, а ставка за день вынесена в настройку
YANDEX_DIRECT_MIN_SPEND_RUB_PER_DAY. Период вычисляется ДО денег — иначе считать
минимум не от чего.

ВТОРАЯ. Создать объявления — не значит запустить рекламу.

Запуск проходил успешно, портал ставил статус «на модерации», а в кабинете лежали
кампания, группа и 15 объявлений в состоянии DRAFT и OFF. Яндекс кладёт всё
созданное черновиком и проверку сам не начинает. Реклама не показалась бы никогда,
и узнать об этом можно было только глазами в кабинете: наш журнал говорил, что всё
хорошо.

Добавлены два вызова, которых не было вовсе:
   ads.moderate     — отдать созданные объявления на проверку
   campaigns.resume — включить показ

Порядок обязателен. Пока кампания черновик, включить её нельзя — Директ отвечает
«кампания является черновиком и не может быть остановлена». Сначала модерация,
она переводит кампанию в MODERATION, и только потом включение.

Отбор объявлений строго по Ids. На CampaignIds Директ отвечает «отсутствует
обязательный параметр Ids» — именно так отправка молча не сработала бы.

На модерацию уходят объявления, созданные ИМЕННО этим заходом: повторная отправка
уже проверяемого — отказ по позиции, он порвал бы возобновляемый запуск. Включение
показа безобидно при любом повторе: на включённой кампании Яндекс отвечает
предупреждением, а не отказом.

Заодно клиент Директа перестал молчать про отказы внутри ответа: раньше он смотрел
только AddResults, UpdateResults и DeleteResults, теперь ещё ModerateResults и
ResumeResults. Отказ по позиции в этих двух проходил бы насквозь незамеченным.

ПРОВЕРЕНО ЖИВЬЁМ. Кампания № 6 на бою: в кабинете 713175197, группа 5778555449,
15 объявлений, аудитория подключена, статус MODERATION, показ включён. На счету
Яндекса 8775 ₽. У клиента заморожено 3333.36 ₽ за 27778 показов до 06.08.

Тесты: 346 зелёных по рекламе, 43 по клиенту Директа, статанализ ноль замечаний.
Четыре новых теста — минимум по длине периода на боевом случае, отправка на
модерацию, порядок модерация-до-включения, отсутствие повторной отправки.
Два старых теста закрепляли частный случай в два дня — им проставлен явный срок
показа, иначе они молча описывали бы неправду.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 10:17:41 +03:00
Дмитрий 075fdfef6f docs(воронка продаж): спека и план двух новых стадий — Тестирование ручное и Выслано КП 2026-07-31 10:17:13 +03:00
Дмитрий 6ddd2b6717 docs(воронка продаж): спека и план двух новых стадий — Тестирование ручное и Выслано КП
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 10:17:03 +03:00
Дмитрий 3cb4e08390 docs(пилот): запись 31.07 — гонка вебхука со сверкой и месяц невидимых отказов
Accessibility (Pa11y live) / a11y (push) Has been cancelled
Снимок боевого пополнен разбором инцидента 31.07.2026: тревога «вебхук потерял 7»
оказалась гонкой (поставщик кладёт строку в журнал раньше, чем шлёт вебхук, лаг
2-8 минут, сверка ходит раз в 30 минут); побочно вскрыто, что журнала вебхука не
было вообще — писал в таблицу, снесённую 24.05, и 70 отказов 404 за 10 дней были
невидимы. Что теперь на бою, чем откатывать, и почему пять сделок без региона
починить нечем.

Телефон-ловушка второй учётки в тексте замаскирован — gitleaks поймал по правилу
ru-phone-unmasked, правило сработало верно.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 10:11:57 +03:00
Дмитрий 78dc8ae94e docs телеграм-робот: промт следующей смене — связка на опросе, что дальше
Переделка сделана и запушена, на бой не выкачено. Развилка описана: поселить
робота и выкатить, перевести на опрос чтение вердикта и пересдачу, вылечить
тестовую базу. Ловушки смены выписаны, включая молчаливую установку
зависимостей и правило замерять сторожа до починки своего текста.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 10:11:17 +03:00
Дмитрий 9c44decc3a docs телеграм-робот: промт следующей смене — связка на опросе, что дальше
Переделка сделана и запушена, на бой не выкачено. Развилка описана: поселить
робота и выкатить, перевести на опрос чтение вердикта и пересдачу, вылечить
тестовую базу. Ловушки смены выписаны, включая молчаливую установку
зависимостей и правило замерять сторожа до починки своего текста.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 10:10:44 +03:00
Дмитрий ae3905d725 test телеграм-робот: сторож на латинский токен + перенос защиты в общий вход
Защита от русских букв в токене переехала из bin/poll.js в portalClient — теперь
она стоит на ЛЮБОМ входе, а не только у прохода опроса. Прикрыта двумя тестами
и принята вырезанием: без проверки тесты падают.

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

Тесты робота: было 118, стало 120.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 10:02:02 +03:00
Дмитрий 77711835a0 fix(поставщик): добор забирает ТЕГ из журнала — региону возвращена опора
SAST — Semgrep / Semgrep SAST scan (push) Has been cancelled
Accessibility (Pa11y live) / a11y (push) Has been cancelled
Продолжение разбора 31.07.2026. У пяти сегодняшних добранных сделок (223, 225-228)
пусты регион и город. Проверил три источника: ДаData по этим номерам молчит
(qc=0, виртуальные операторы МиАТел/МТТ/Скартел/ВымпелКом), Россвязь не совпала,
тег поставщика у них — «РФ», то есть вся страна. Восстановить их регион НЕЧЕМ,
задним числом не чиню и не выдумываю.

Но причина на будущее устранима: в журнале отданного ЕСТЬ колонка «Тег», и там
бывает настоящий регион (у одного из семи сегодняшних — «Свердловская область»).
Мы её просто не читали, поэтому у добранного лида в карточке было только
vid+phone+project, и когда ДаData молчит, резолверу нечем подстраховаться —
RegionTagResolver работает именно по тегу.

- parseDeliveredRows: тег берётся из ячейки сразу после td.crm-domain-column.
  Разметка списана с ЖИВОГО кабинета, не придумана. Прочерк/пусто → tag=null,
  поведение как раньше (парсер этого журнала уже дважды ломал прод — 09.07 и 16.07,
  поэтому только добавление поля, ни одна существующая ветка не тронута).
- CsvReconcileJob: тег кладётся в raw_payload добранного лида.

Тесты (20/20 в файле, 233/233 поставщик+вебхук): разбор живой разметки, «РФ» и
прочерк, тег доезжает в карточку и — главное — с тегом резолвер даёт регион
(source=tag), а стоит тег вырезать, тот же лид снова «регион неизвестен».
Larastan 0, Pint чисто.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 09:28:35 +03:00
Дмитрий 071a70eab3 docs телеграм-робот: памятка установки робота на машину
Сама установка НЕ выполнена — она трогает живую машину и боевой портал,
нужно явное разрешение владельца. Куда селить робота, зависит от исхода
разговора с поставщиком прокси.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 09:24:54 +03:00
Дмитрий 9100e1fdda feat телеграм-робот: проход опроса — сам спрашивает работу у портала
Своей петли нет: запускается таймером, зависший проход не мешает следующему.
Номера кладутся во временный файл и убираются при любом исходе.

Проверено живьём против поднятого портала: верный токен — «Работы нет.» и код 0,
чужой токен — понятная ошибка 401.

Заодно закрыта мина: токен уходит в заголовок HTTP, куда пускают только латиницу.
С русскими буквами робот падал нечитаемым сбоем Node ещё до обращения к порталу —
теперь говорит по-человечески. Наступил на неё сам, проверяя план дословно.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 09:22:26 +03:00