docs робот креативов: промт перезапуска v14 — все восемь дыр закрыты, дальше хвосты

Р1-Р8 приёмочного листа починены и проверены вырезанием. Портал 274/274,
робот 41/41. Дальше по листу v12 — хвосты П1-П8, Д1-Д6, Р-х1-Р-х6, потом мелочи.
This commit is contained in:
Дмитрий
2026-07-27 20:45:39 +03:00
parent 542b7d2d22
commit dcfd9fe297
@@ -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<CampaignStatus, …>`
требует все ключи, иначе `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».