diff --git a/docs/superpowers/2026-07-27-PROMPT-restart-robot-kreativov-v14.md b/docs/superpowers/2026-07-27-PROMPT-restart-robot-kreativov-v14.md new file mode 100644 index 00000000..5c26cbe8 --- /dev/null +++ b/docs/superpowers/2026-07-27-PROMPT-restart-robot-kreativov-v14.md @@ -0,0 +1,189 @@ +# Промт перезапуска v14 — реклама «за показы», все восемь дыр закрыты, дальше хвосты + +**Дата:** 27.07.2026. **Не заменяет** v12 (`2026-07-27-PROMPT-restart-robot-kreativov-v12.md`) — +там лежит **приёмочный лист целиком**: §6 дыры (закрыты), **§7 хвосты — твоя работа**, +§8 мелочи, §9 «не переделывать», §13 открытые вопросы владельца. Этот файл — только +«где ты сейчас» и что делать дальше. Предыдущий v13 устарел. + +--- + +## 0. 🔴 ПЕРВОЕ ДЕЙСТВИЕ — прочитать два файла целиком + +Не пересказывай их по памяти. Открой и прочитай: + +1. `docs/superpowers/2026-07-27-PROMPT-restart-robot-kreativov-v12.md` — приёмочный лист. + **Главное там сейчас — §7 «Важные хвосты» и §8 «Мелочи».** +2. `docs/superpowers/2026-07-27-PROGRESS-pochinka-v12.md` — что уже починено и как проверено. + +Ты в git worktree `.claude/worktrees/reklama-pokazy`, ветка `feat/reklama-yandex-pokazy`. +Laravel-приложение — в подпапке `app/`. Робот — в `bots/yandex-creatives/`. + +Сверь `git log --oneline -4`. Должно быть так: + +```text +542b7d2d fix реклама за показы: очередь робота больше не встаёт колом, доклад об успехе не врёт, тихий ноль ловится +43be8c39 docs робот креативов: промт перезапуска v13 — Р1-Р5 закрыты, дальше Р6 +b8f75b2a fix реклама за показы: файл робота только по своему заданию, замок на баннерах, защита от двойного запуска +d26716ed fix реклама за показы: срок показа закрывает кампанию, отчёт робота только по заданию в работе, гонка выдачи заданий +``` + +Никуда не пушили. Незакоммичено только три устаревших промта (`chast4-v3`, `chast4-v4`, +`robot-kreativov-v8`) — **не читай, не коммить, удалять только с разрешения владельца**. + +Зелёная отметка на 27.07.2026, прогнано в одиночку: + +- портал: `cd app && DB_DATABASE=liderra_testing_reklama php artisan test --filter=Advertising` + → **274/274, 957 проверок, ~45 с**; +- робот: `cd bots/yandex-creatives && node --test` → **41/41**; +- `cd app && npm run type-check` → 6 ошибок, **все чужие**, ровно те же и без наших правок + (проверено `git stash` в прошлой сессии). Не чинить в этой ветке. +- вызовов `AdWalletService->release(` в коде — **четыре**, пятого не появилось. Проверяй это + число после каждой денежной правки; прочие `->release()` в репозитории — снятие замков + `Cache::lock`, не деньги. + +--- + +## 1. Что закрыто — не переделывать + +| # | Дыра | Коммит | +|---|---|---| +| Р1 | Деньги клиента замерзали навсегда при недокруте сметы | `d26716ed` | +| Р2 | Отчёт робота принимался по любому заданию в любом состоянии | `d26716ed` | +| Р3 | Два задания в работе одновременно + выдача файла «какому-нибудь» | `d26716ed` + `b8f75b2a` | +| Р4 | Ручки баннеров не заперты после заведения кампании в Яндексе | `b8f75b2a` | +| Р5 | У «запустить» не было защиты от повторного нажатия | `b8f75b2a` | +| Р6 | Очередь робота встаёт колом навсегда | `542b7d2d` | +| Р7 | Робот докладывал «сбой» об успешной работе | `542b7d2d` | +| Р8 | Бесконечная заливка мусора в живой кабинет | `542b7d2d` | + +**Все восемь — с тестами, каждая защита проверена вырезанием.** Подробности по каждой — +в PROGRESS-файле. + +Что появилось нового и о чём надо знать, чтобы не сломать: + +- колонка `ad_campaigns.shows_until` + запись **v9.07** в `db/CHANGELOG_schema.md`; +- частичный уникальный индекс `uq_creative_job_single_taken` + запись **v9.08**. + 🪤 Из-за него в базе может быть **не больше одного** задания со статусом `taken` — + тест, создающий два, упадёт на нарушении уникальности, и это правильно; +- статус `AdCampaign::STATUS_LAUNCHING` (`launching`) — промежуточный, держится секунды. + Добавлен и во фронт: тип `CampaignStatus` в `app/resources/js/api/advertising.ts` и **обе** + карты `STATUS_META` (`CampaignList.vue`, `CampaignReportDialog.vue`) — `Record` + требует все ключи, иначе `vue-tsc` падает; +- адрес файла робота — `GET /api/creative-robot/jobs/{jobId}/banners/{bannerId}/file`; +- команда `creative-jobs:reap` (`app/app/Console/Commands/ReapStuckCreativeJobs.php`) — + сторож зависших заданий, в расписании каждые 10 минут. Порог 30 минут, 3 попытки. + 🔴 Ходит через `pgsql_admin`: на дефолтной роли без tenant-контекста RLS отдал бы ноль строк; +- у робота новый ключ `REPORT_RETRY_DELAY_MS` (по умолчанию 3000) — пауза между повторами + доклада «готово». В `.env.example` он уже есть. + +--- + +## 2. Твоя работа — хвосты по листу v12 §7 + +**Порядок внутри списка свободный, каждый — с тестом.** Полный текст каждого хвоста — +в v12 §7, здесь только карта, чтобы понимать масштаб. + +**Портал П1–П8:** П1 обрыв Яндекса даёт клиенту голый 500 · П2 повторная постановка после +сбоя берёт свежий слепок и падает по кругу, плодя дубли · П3 слепок снимается при постановке, +а не при выдаче · П4 номер креатива берётся на веру, размер не сверяется · П5 нет уникального +индекса на слот баннера (**миграция** → `rls-reviewer` + CHANGELOG) · П6 нет постраничного +обхода креативов + роботу отдаются все баннеры, а не только без номера · П7 замок на правку +не покрывает окно между сегментом и кампанией · П8 файл всегда отдаётся как `.jpg`. + +**Деньги Д1–Д6:** Д1 не пишется `client_cpm_rub` — списание может уехать по новой цене · +Д2 нет `lockForUpdate` в суточном списании · Д3 ошибка Директа глотается, а заморозка +снимается безусловно · **Д4 — открытый вопрос владельца**, не решай сам (см. §4) · Д5 рубильник +Директа обходится служебным каналом робота · Д6 сравнение `=== false` вместо `! config(…)`. + +**Робот Р-х1–Р-х6:** Р-х1 `.env` резолвится от каталога запуска · Р-х2 два процесса не +поделят профиль браузера, нужен файл-замок · Р-х3 письмо врёт «ничего не менял», когда файлы +уже залиты · **Р-х4 тест-пустышка** про рабочую папку — написать настоящий · Р-х5 расширение +файла и имя по `banner_id` (парно с П8) · Р-х6 `Number(env.X ?? '800')` — пустая строка даёт 0, +`SMTP_PORT` может дать `NaN`. + +🔎 Половина Р-х6 уже закрыта побочно: ключ `REPORT_RETRY_DELAY_MS` добавлен сразу в правильной +форме, но `HUMAN_DELAY_MS` и `SMTP_PORT` — с прежней миной. + +Дальше — **мелочи v12 §8**, потом задачи 16 и 17 (см. §5). + +--- + +## 3. Как работать + +1. **Каждая правка: сначала тест, который краснеет, потом код.** Убедись, что красный — + по правильной причине, а не из-за опечатки. +2. **Защиту проверять вырезанием**: временно сломай боевой код, убедись, что тест краснеет, + верни обратно. +3. После каждой правки — **полный прогон обоих наборов**. +4. **Отчёты проверяющих проверяй сам по коду.** +5. **Денежный диф читать глазами.** Выходов снятия заморозки ровно **четыре** (таблица + в v12 §5). Пятого не создавать. +6. Миграция → обязательно `rls-reviewer` и запись в `db/CHANGELOG_schema.md`. +7. **За собой убирать.** В конце `git status --porcelain` — только три старых промта. + +### 🪤 Грабли, пойманные в этих сессиях + +- 🪤 **Копируй файл в scratchpad перед мутацией и возвращай оттуда.** `git checkout -- <файл>` + откатывает файл к коммиту, то есть сносит и саму правку, а не только мутацию. +- 🪤 **Тест может быть ложно зелёным на незнакомом адресе**: неизвестный URL перехватывает + страница сайта и отвечает 200. Голого `assertOk()` мало — проверять содержимое. +- 🪤 **Тест на защиту может быть пустышкой**, если он сам создаёт то состояние, которое должна + создавать защита. Вырезание защиты — единственный честный способ это увидеть. Так вскрылось + дважды; третий известный случай ждёт починки — Р-х4. +- 🪤 **`pgsql_admin` в тестах виден**: `tests/Concerns/SharesAdminPdo.php` подключён глобально + к `Feature` (`tests/Pest.php`), обе connection делят один PDO. Поэтому команду можно честно + прибить к `pgsql_admin`. **Обратная сторона:** мутация «сменить соединение на дефолтное» + в тестах НЕ покраснеет — это чисто боевая защита, держится на знании про RLS. +- 🪤 **Тесты гонять по одному процессу за раз.** База только `liderra_testing_reklama`. + Никогда не `liderra` (боевая) и не `liderra_testing` (общая, её ломает параллельная сессия). +- 🪤 `->after('колонка')` в миграции на PostgreSQL — **no-op**, колонка встаёт в конец таблицы. +- 🪤 `composer pint` без путей проходит по всему складу и затирает чужие незакоммиченные + правки — только `cd app && ./vendor/bin/pint <файл> <файл>`. +- 🪤 **`.git` в worktree — ФАЙЛ, не папка.** Текст коммита класть в `$(git rev-parse --git-dir)/…`. +- 🪤 Новые файлы `git commit <пути>` не берёт — нужен предварительный `git add` тех же путей. + Каталог `/моя/` в `.gitignore` — там нужен `git add -f`. +- 🪤 Larastan и lefthook в worktree не работают; коммит с `LEFTHOOK_EXCLUDE=larastan`. + «Can't find lefthook in PATH» — ожидаемо, коммит проходит. +- 🪤 Хуки в worktree немые — gitleaks вручную по индексу: + `"…/Документация/bin/gitleaks.exe" protect --staged` (бинарь лежит в **корневом** репо, + в worktree только `.gitkeep`). +- 🪤 Bash помнит текущий каталог между вызовами — сверяться через `pwd`, пути абсолютные. +- 🪤 `tinker` без `DB_DATABASE` уходит в dev-базу. +- 🪤 `AdminAdvertisingSpendTest` иногда плавает от мусора в тестовой базе — чужой, не чинить. +- 🪤 `app/app/Listeners/PauseCampaignsOnAdStop.php` git иногда помечает изменённым, а содержимое + побайтово совпадает с коммитом. Причуда Windows, не трогать. + +--- + +## 4. 🔴 Границы, которые нельзя переступать + +- Прод `liderra.ru` — **боевой, живые клиенты и деньги**. Любой выкат, правка боевой БД, + включение рубильников — только с явного «go» владельца. БД по умолчанию только чтение. +- Рубильник `YANDEX_DIRECT_ENABLED` — **ВЫКЛ**, `base_url` — песочница. Реальных кампаний + в боевом кабинете **не создавать** до отдельного «go». +- 🔴 **Вход в живой рекламный кабинет Яндекса — только вместе с владельцем.** +- Наценка, `yandex_cost_rub`, `ad_margin_percent` — **никогда** в клиентском JSON. +- **GitHub origin заблокирован**, 403. Пуш **только в gitea**. +- Коммиты — **по эскейпу владельца** через AskUserQuestion, метка опции = точная git-команда. + Сообщение **без круглых скобок**, `LEFTHOOK_EXCLUDE=larastan`, только явные пути. +- 🔴 `db/03_service_bypass_policies.sql` **перезапустить после выката новых таблиц**, иначе + служебные роли увидят ноль (висит на таблице `ad_creative_jobs`, запись v9.06). +- **Открытые вопросы владельца не закрывать самому** — v12 §13. В том числе **Д4** (как писать + проводку при нехватке денег) и выбор «прихожей». +- Владелец — **не программист**. Отчитываться простым русским, без программистских слов. +- **Останавливаться на логичных точках**, а не лететь без остановки. + +--- + +## 5. Дальше — задачи 16 и 17 (после хвостов) + +**16 — установка на боевом.** Только с «go», сначала `prod-deploy-validator`. +**17 — живая сверка.** Только вместе с владельцем. + +Подробности — v12 §14. 🔑 На первом живом прогоне добираем то, чего мы ни разу не видели: +сколько ждать после «Создать», как выглядит отказ «файл не подошёл» и настоящая ли кнопка +«Создать» (v12 §8 про `isDisabled`). + +--- + +Отчитывайся владельцу простым языком. На коммит и на всё необратимое — спрашивай «go».