По спеке 2026-07-02-jivo-ai-support-bot-design: webhook + FTS-поиск
по инструкции + YandexGPT Lite + эскалация + журнал + тест скорости.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Дизайн согласован владельцем 02.07.2026: свой бот через Jivo Bot API,
YandexGPT Lite, база знаний = docs/help/ в репо, кнопка «Показать»
(экскурсии), скорость 2-5 сек, v1 только общие вопросы.
Словарь: +jivo/дживо/gigachat.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Серверный 3-й слой защиты (SupplierProjectName::strip) в DealController,
V1/DealsController, DealExportController — паритет с фронтом stripChannelPrefix.
На входе префикс и так срезается (RouteSupplierLeadJob/CsvLeadsParser), это
belt-and-suspenders на случай будущих правок парсера. Тесты: API+CSV не палят B<N>_.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Старая линия main сохранена: тег main-legacy-20260709 + off-site бэкап. Дерево этого
коммита побайтово равно боевому серверу (495/495 бэкенд-файлов сверены).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Задача 5: RunAutopodborStudyJob::handle() оборачивает существующее тело в
try/finally — после ЛЮБОГО завершения прогона (done/empty/failed, включая
проброшенное исключение) распорядитель AutopodborStudyScheduler::tick()
берёт следующего кандидата с освободившейся дорожки шага 2.
Задача 6: artisan autopodbor:study-tick — крон-страховка раз в минуту на
случай, если дорожка освободилась не через завершение джобы. Без
withoutOverlapping() — требует таблицу cache_locks, которой нет в схеме
(см. комментарий в routes/console.php); overlap уже защищён внутренним
Cache::lock в AutopodborStudyScheduler::tick().
Переиспользует BalancePreflightService.evaluate() для проверки, что после
списания за пакет ещё хватает баланса на дневной лимит активных проектов.
Бинарный поиск максимально доступного количества единиц пакета (maxAffordable)
и расчёт недостающей суммы (topupRub) при отказе.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Боевой в batch-режиме: пауза проекта не доходила до поставщика — заказ висел активным
(галочка вкл, лимит>0), поставщик мог лить лиды и списывать за остановленный проект
(подтв. живой базой: omega, 57 заказов, 0 команд после паузы 08.07).
Поставщик фиксирует завтрашнюю заливку снимком в 21:00, поэтому гасить надо в 18:00-прогоне
ДО снимка (мгновенно бессмысленно — сегодня уже зафиксировано). Добавлен проход
deactivateOrphanedOrders в SyncSupplierProjectsJob: заказам источников, которых нет в
активных группах слепка за завтра, шлётся updateProject(status=paused) с ненулевым лимитом,
локально помечается inactive_since. online-режим не тронут (там пауза уже мгновенна).
Активность берётся из слепка (слепок-инвариант), не из live is_active.
Тесты (TDD RED->GREEN): batch pause + стык с delete-хвостом. 19/19 supplier+delete,
238/238 supplier+projects, pint чисто, PHPStan без новых замечаний.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Блок 6: если за окно (10 мин) в supplier_manual_sync_queue упала пачка
(>= threshold-manual-queue, дефолт 10) — вероятное падение кабинета поставщика.
Инцидент дедупится 60 мин, письмо шлётся каждый прогон на kdv1@bk.ru + ops@liderra.ru
(громко при реальной аварии, но одним сводным письмом). Флаг sendMail в createIncident.
phpstan-baseline: +4 TestCall::artisan() (5→9) от новых тестов.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
(cherry picked from commit 74d4d9f21d)
Убраны письма manual_required и failover_to_form (по проекту, без троттла) —
источник потопа ~116 писем на ops@ 07.07. Строка в ручной очереди остаётся
(запись видна в админке). Зависимость Mailer удалена из канала и DI-привязки.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
(cherry picked from commit 72818f5a80)
Отметка в ПИЛОТ.md обоих моих выкатов: (1) SyncSupplierProjectsJob deactivateOrphanedOrders
(пауза гасит заказ у поставщика в 18:00 до снимка 21:00; бэкап .bak-20260709-023707);
(2) AuditChainConfig global (verify-chains EXIT 0, 78 партиций; бэкап .bak-20260709-060558).
NB про параллельный CSV-reconcile money-фикс (91c5a119) — уже в gitea main, дрейфа нет.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Причина фантомных сделок kdv1 (09.07.2026): CsvReconcileJob брал отчёт «Запрос
номеров» = ПУЛ собранных номеров (phones_cnt), а не ОТДАННОЕ (crms_cnt), и лепил
из пула сделки клиенту + выедал дневной лимит. Пример: пул 157, отдано 15,
у клиента 15 фантомов, а 15 реально отданных выбило лимитом.
Фикс: SupplierPortalClient::fetchDeliveredLeads() читает журнал «Мои сделки»
(index-visit, по vid) — только реально отгруженное. CsvReconcileJob сверяет по vid,
добор несёт настоящий vid (idx_supplier_leads_vid_unique -> идемпотентно с webhook).
Пул не трогается -> фантомы невозможны, лимит не забивается мусором.
Проверено: 12 новых тестов + 294 supplier зелёные, Pint/Larastan чисто. Выкачено и
проверено на проде (reconcile читает отданное, 0 фантомов за прогон).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Боевой в batch-режиме: пауза проекта не доходила до поставщика — заказ висел активным
(галочка вкл, лимит>0), поставщик мог лить лиды и списывать за остановленный проект
(подтв. живой базой: omega, 57 заказов, 0 команд после паузы 08.07).
Поставщик фиксирует завтрашнюю заливку снимком в 21:00, поэтому гасить надо в 18:00-прогоне
ДО снимка (мгновенно бессмысленно — сегодня уже зафиксировано). Добавлен проход
deactivateOrphanedOrders в SyncSupplierProjectsJob: заказам источников, которых нет в
активных группах слепка за завтра, шлётся updateProject(status=paused) с ненулевым лимитом,
локально помечается inactive_since. online-режим не тронут (там пауза уже мгновенна).
Активность берётся из слепка (слепок-инвариант), не из live is_active.
Тесты (TDD RED->GREEN): batch pause + стык с delete-хвостом. 19/19 supplier+delete,
238/238 supplier+projects, pint чисто, PHPStan без новых замечаний.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Портал выкатывается полной заменой public/build; сборка из устаревшей ветки
молча сносила чужую работу (инцидент 08.07: Метрика снесла колонки «Сделок»).
deploy-build.sh перед подменой сверяет маркеры критичных фич (по активным
чанкам manifest.json, не по orphan-осколкам) — теряет живую фичу → ОТКАЗ.
Осознанный откат — FORCE=1, только проверка — DRY_RUN=1. Установлен на боевом.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Широкая таблица (7 колонок) не влезала в узкую колонку планшета рядом с
rail-меню — правые колонки «Комментарий»/«Поставлен» обрезались на 600–1279px.
ResponsiveTable теперь показывает карточки на телефоне И планшете, таблицу —
только на десктопе (>=1280). Тесты табличного слоя зафиксированы на десктоп-ширину.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Имя конкурента хранится как «Бренд, описание» — в колонке «Конкурент»
показываем часть до первой запятой, полное имя — в title при наведении.
Применено к десктопной таблице и мобильной карточке. +тест на обрезку.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Cherry-pick b35d3888 поверх боевой ветки feat/portal-mobile-adaptive для
выката ТОЛЬКО правки тарифов на прод. Логика денег не менялась.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
START_BALANCE_RUB в RegistrationService: '300.00' → '1000.00' — новый тенант
получает 1000 ₽ на баланс при подтверждении почты. Обновлён докблок
RegistrationController и тест RegistrationTest (баланс 1000, 13 тестов зелёные).
Автоподборные 300 ₽ (цена «Собрать конкурентов») НЕ тронуты — это другая сумма.
Миграций нет (константа применяется при регистрации). В UI/лендинге сумма
подарка не отображается — фронт не менялся.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Влитый из автоподбора deep-link «Сделки → Автоподбор» читал route.query
в onMounted без защиты. В приложении роутер всегда есть, но при монтировании
в юнит-тесте без роутера route === undefined → unhandled error (2 ошибки,
vitest exit 1). Добавил route?.query ?? {} — поведение в бою не меняется,
полный прогон снова зелёный (1184 ✅, exit 0). Правка нужна для чистого
CI-гейта выката мобильной ветки.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
После вливания переделки сделок (колонки Конкурент/Источник со ссылками
в автоподбор) эти поля были только в таблице на компьютере — в карточке
телефона их не было. Добавил в мобильную карточку:
- «Конкурент» — имя конкурента зелёной ссылкой, ведёт в автоподбор
(/autopodbor?competitor=ID), рядом город;
- «Источник» — отдельной строкой домен/идентификатор ссылкой в автоподбор
(+ «· +N» если источников несколько) и канал (Сайт/Звонки/СМС).
Для сделок без источника автоподбора карточка выглядит как раньше
(проект · канал · город). @click.stop — ссылки не открывают деталь сделки.
Проверено на 390px: конкурент+источник видны, вбок не едет, переход
по ссылке в автоподбор работает; тесты DealsTable/mapper/ResponsiveTable
зелёные (23).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Прошёл все экраны портала на 390px, нашёл и починил переполнения:
1. NewProjectDialog: кнопки дней недели (ПН…ВС) в окне редактирования
проекта не влезали — СБ и ВС уезжали за правый край и были не видны.
На телефоне делим ряд поровну между 7 кнопками.
2. DaysBulkDialog (массовое «Дни сбора»): та же беда с двумя рядами дней —
тот же приём (flex 1/1/0 на телефоне).
3. ApiTab: пары кнопок «Копировать+Перегенерировать» и
«Сохранить+Тестовый webhook» не помещались в строку — длинная вторая
кнопка обрезалась. Разрешаем перенос на телефоне (flex-wrap).
4. NotificationsTab: таблица «События × каналы» была шире экрана —
колонка Email уезжала, а названия событий крошились по одному слову.
На телефоне ужимаем колонки-галочки и уменьшаем отступы, событию
достаётся почти вся ширина; заголовки каналов мельче.
Все правки — только в phone-media (<600px), десктоп не затронут.
Проверено замерами DOM + скриншотами на 390px; тесты затронутых
компонентов зелёные.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
На узком экране заголовок «Проекты» и кнопка «Создать проект» стояли
вплотную в одну строку. На телефоне (<600px) кнопка теперь под заголовком
во всю ширину-строку, слева, с воздухом между ними; заголовок прижат влево
через align-self (перебивает utility .align-center с !important). Десктоп
не затронут — правка только в phone-media.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Карточка источника (.ld-srccard) — flex-ряд: иконка | данные | блок кнопок
(.ld-srcctl, flex-shrink:0, max-width:320) справа. На телефоне кнопки
(Создать проект/Приостановить/Настройки проекта/В источники/Возобновить)
уезжали за край карточки. На телефоне (media <600px) карточка переносит
блок кнопок на строку ниже во всю ширину. Затрагивает обе под-вкладки
(Источники в работе + Предложения). Проверено сканом: 0 заездов. Тесты 71/71.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>