From 01ca3087aa4edb4947bf006394f8bb24cd51fade Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9?= Date: Tue, 28 Jul 2026 07:28:49 +0300 Subject: [PATCH] =?UTF-8?q?docs=20=D1=80=D0=BE=D0=B1=D0=BE=D1=82=20=D0=BA?= =?UTF-8?q?=D1=80=D0=B5=D0=B0=D1=82=D0=B8=D0=B2=D0=BE=D0=B2:=20=D0=BF?= =?UTF-8?q?=D1=80=D0=BE=D0=BC=D1=82=20=D0=BF=D0=B5=D1=80=D0=B5=D0=B7=D0=B0?= =?UTF-8?q?=D0=BF=D1=83=D1=81=D0=BA=D0=B0=20v16=20=D0=B8=20=D1=80=D0=B5?= =?UTF-8?q?=D1=88=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B2=D0=BB=D0=B0=D0=B4=D0=B5?= =?UTF-8?q?=D0=BB=D1=8C=D1=86=D0=B0=20=E2=80=94=20=D1=86=D0=B5=D0=BD=D1=83?= =?UTF-8?q?=20=D0=BE=D0=BF=D1=80=D0=B5=D0=B4=D0=B5=D0=BB=D1=8F=D0=B5=D1=82?= =?UTF-8?q?=20=D0=BA=D0=BB=D0=B8=D0=B5=D0=BD=D1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Приёмочный лист v12 пройден целиком: восемь критичных дыр, хвосты портала и денег, хвосты робота и мелочи §8. Осталось только то, чего нельзя сделать без владельца и без живого кабинета — задачи 16 и 17. Владелец подтвердил 28.07.2026: цену за 1000 показов назначает клиент. Пункт листа про client_cpm_rub и budget_rub закрыт как решение, а не как дыра — поле в мастере остаётся, ручки продолжают принимать цену. Записано и в журнале починки, и в промте, чтобы будущий разбор не переоткрыл вопрос заново. Портал 300/300, робот 60/60, мест снятия заморозки денег четыре. --- .../2026-07-27-PROGRESS-pochinka-v12.md | 9 +- ...7-28-PROMPT-restart-robot-kreativov-v16.md | 203 ++++++++++++++++++ 2 files changed, 210 insertions(+), 2 deletions(-) create mode 100644 docs/superpowers/2026-07-28-PROMPT-restart-robot-kreativov-v16.md diff --git a/docs/superpowers/2026-07-27-PROGRESS-pochinka-v12.md b/docs/superpowers/2026-07-27-PROGRESS-pochinka-v12.md index 3c389a2f..bbe25710 100644 --- a/docs/superpowers/2026-07-27-PROGRESS-pochinka-v12.md +++ b/docs/superpowers/2026-07-27-PROGRESS-pochinka-v12.md @@ -520,7 +520,7 @@ v9.07. Чинить не в этой ветке. `Width`/`Height` без `?? 0` — во всех трёх местах `YandexDirectClient` уже с защитой (правки П4/П6). -## 🔴 НЕ трогал — вопрос владельцу +## ✅ РЕШЕНО ВЛАДЕЛЬЦЕМ 28.07.2026 — цену определяет клиент `client_cpm_rub` и `budget_rub` приходят от клиента. В листе v12 это записано как «лучше не отдавать клиенту», но в мастере кампании стоит **видимое поле «Ваша цена за 1000 показов, ₽»** @@ -529,7 +529,12 @@ v9.07. Чинить не в этой ветке. То есть это не недосмотр, а принятое продуктовое решение: клиент назначает свою ставку. `budget_rub` — счётная величина от сервера, которую клиент видит в списке кампаний. -Убрать их — значит менять продукт и ломать мастер. **Решает владелец.** +Убрать их — значит менять продукт и ломать мастер. + +🔴 **Владелец подтвердил 28.07.2026: «клиент сам определяет цену».** Пункт листа v12 §8 +про `client_cpm_rub` и `budget_rub` **закрыт — правку не делать никогда.** Поле цены +в мастере остаётся, ручки `store`/`update` продолжают принимать `client_cpm_rub`. +Если в будущем разборе кто-то снова назовёт это дырой — это НЕ дыра, а решение владельца. ## Зелёная отметка после мелочей diff --git a/docs/superpowers/2026-07-28-PROMPT-restart-robot-kreativov-v16.md b/docs/superpowers/2026-07-28-PROMPT-restart-robot-kreativov-v16.md new file mode 100644 index 00000000..1cff6db0 --- /dev/null +++ b/docs/superpowers/2026-07-28-PROMPT-restart-robot-kreativov-v16.md @@ -0,0 +1,203 @@ +# Промт перезапуска v16 — реклама «за показы», лист пройден целиком, дальше боевой сервер + +**Дата:** 28.07.2026. **Заменяет v15.** Приёмочный лист v12 закрыт весь: восемь критичных дыр, +хвосты портала и денег, хвосты робота и мелочи §8. Осталось только то, что нельзя сделать +без владельца и без живого кабинета. + +--- + +## 0. 🔴 ПЕРВОЕ ДЕЙСТВИЕ — прочитать два файла целиком + +Не пересказывай их по памяти. Открой и прочитай: + +1. `docs/superpowers/2026-07-27-PROGRESS-pochinka-v12.md` — что починено, как проверено + и почему решено именно так. **Последний раздел — про мелочи 28.07 и решение владельца.** +2. `docs/superpowers/2026-07-27-PROMPT-restart-robot-kreativov-v12.md` — приёмочный лист. + Из него живыми остались только **§13 «Открытое — решает владелец»** и **§14 задачи 16–17**. + Разделы §6, §7, §8 закрыты целиком — читать их как историю, не как работу. + +Ты в git worktree `.claude/worktrees/reklama-pokazy`, ветка `feat/reklama-yandex-pokazy`. +Laravel-приложение — в подпапке `app/`. Робот — в `bots/yandex-creatives/`. + +Сверь `git log --oneline -4`. Должно быть так: + +```text +af12b1ce fix реклама за показы: предел веса картинки проверен по-настоящему, обход модерации не срывается целиком, робот не несёт токен на чужой адрес +0f82a2d9 docs робот креативов: промт перезапуска v15 — приёмочный лист пройден, дальше мелочи +9cc3f5e9 fix реклама за показы: пауза не врёт про остановку, цена запуска остаётся на кампании, робот не дерётся сам с собой +0548fc0d fix реклама за показы: слепок креативов при выдаче, сверка размера перед объявлением, один баннер на слот +``` + +Никуда не пушили, на боевой не выкатывали. Незакоммичено только три устаревших промта +(`chast4-v3`, `chast4-v4`, `robot-kreativov-v8`) — **не читай, не коммить, удалять только +с разрешения владельца**. + +Зелёная отметка на 28.07.2026, прогнано в одиночку: + +- портал: `cd app && DB_DATABASE=liderra_testing_reklama php artisan test --filter=Advertising` + → **300/300, 1029 проверок, ~48 с**; +- робот: `cd bots/yandex-creatives && node --test` → **60/60**; +- вызовов `AdWalletService->release(` в коде — **четыре**, пятого не появилось. Проверяй это + число после каждой денежной правки; прочие `->release()` — снятие замков `Cache::lock`. +- `npm run type-check` не гонялся (фронт не трогали). До этой ветки там было 6 чужих ошибок — + не чинить здесь. + +--- + +## 1. Что закрыто — не переделывать + +Весь приёмочный лист: **Р1–Р8** (`d26716ed`, `b8f75b2a`, `542b7d2d`), **портал П1–П8** +(`0548fc0d`), **деньги Д1–Д6 кроме Д4** и **робот Р-х1–Р-х6** (`9cc3f5e9`), **мелочи §8** +(`af12b1ce`). Каждая правка с тестом, каждая защита проверена вырезанием. Подробности +и обоснования — в PROGRESS-файле. + +🔴 **Осознанно НЕ меняется — не «чини»:** + +- **Цену за 1000 показов назначает КЛИЕНТ.** Владелец подтвердил 28.07.2026: «клиент сам + определяет цену». Поле «Ваша цена за 1000 показов, ₽» в мастере кампании остаётся, ручки + `store`/`update` продолжают принимать `client_cpm_rub`, `budget_rub` клиент видит в списке. + Если очередной разбор назовёт это дырой — **это не дыра, это решение владельца**; +- у `resume()` неудача Директа не откатывает статус: заморозка ставится ДО обращения + к Яндексу, и отказ потребовал бы **пятого** места разморозки. Их ровно четыре; +- миграция уникального слота дубли не чистит — падает громко. Молча удалять картинки клиента + нельзя, а на боевом таблицы ещё нет вовсе; +- слепок креативов снимается в `takeNext()`, внутри транзакции — да, сетевой вызов в открытой + транзакции, обосновано в комментарии; +- `CreativeJobService::bannersToUpload()` — единый источник «какие баннеры везём»: и для выдачи + роботу, и для сопоставления при отчёте. Разведёшь их — опознание сломается; +- тест про порядок посредников служебного канала намеренно **ничего не гарантирует про + поведение** (наблюдаемой разницы нет) — он держит порядок от обратной правки, так и написано + в самом тесте. + +**Что появилось в схеме:** `ad_campaigns.shows_until` (v9.07), частичный индекс +`uq_creative_job_single_taken` (v9.08), уникальный индекс `uq_ad_campaign_banner_slot` (v9.09). +🪤 Из-за них в базе не может быть двух заданий `taken` и двух баннеров одного размера +в кампании — тест, который их создаёт, упадёт на нарушении уникальности, и это правильно. + +--- + +## 2. Твоя работа + +### 2.1. Задачи 16 и 17 — только с «go» владельца + +Подробности — v12 §14. + +**16 — установка на боевом.** Сначала `prod-deploy-validator`. Поставить робота, +`npm install` + браузеры, заполнить `.env` включая «прихожую», завести вход глазами через +удалённый рабочий стол внутри виртуального экрана, повесить расписание на `run:once` +и `keepalive`. Наружу порт не открывать. + +🔴 При выкате: **перезапустить `db/03_service_bypass_policies.sql`** — в ветке две новые +таблицы (`ad_campaign_banners`, `ad_creative_jobs`), иначе служебные роли увидят ноль +(тихий ноль, журнал при этом зелёный). В `.env` робота добавились `HUMAN_DELAY_MS`, +`REPORT_RETRY_DELAY_MS` и «прихожая». + +**17 — живая сверка. Только вместе с владельцем.** 🔑 На первом живом прогоне добираем то, +чего мы ни разу не видели: сколько ждать после «Создать», как выглядит отказ «файл +не подошёл» и настоящая ли кнопка «Создать» (единственное место разметки, помеченное +«не измерено живьём» — v12 §8 про `isDisabled`). + +### 2.2. Известные остатки — не срочные, но записаны + +- Проигравший гонку за слот баннера получит 500 от нарушения уникальности, а его файл + останется на диске сиротой. Честного теста без настоящей гонки не написать; размен + осознанный — 500 лучше двух объявлений за деньги клиента. Хочешь починить — обработать + `QueryException` 23505 в `uploadBanner()` перечитыванием строки. +- У `crm_app_user` табличный `UPDATE` на `ad_creative_jobs` без ограничения по колонкам. + Сейчас безвредно (клиентский код в `taken` не пишет), но защита держится на отсутствии + такого кода, а не на правах в БД. +- Мусор в кабинете при неудачных заходах робота вычищается только руками — открытый вопрос + по продукту (v12 §17). +- `CampaignBannerService` и `BannerGenerator` — мёртвый продуктовый код, удалять только + с разрешения владельца. +- 🔴 `db/schema.sql` не содержит ни одной таблицы рекламного модуля — накопленный дрейф. + Там же устарела шапка `db/CHANGELOG_schema.md`. Закрывать отдельным canon-sync, + **не в этой ветке**. + +### 2.3. Открытое — решает владелец, сам не закрывай + +- **Д4** — как писать проводку при нехватке денег: на фактически списанное или отдельной + строкой «недобор». Влияет на отчёт по марже. +- **Какую кампанию с группой назначить «прихожей»** роботу. Рекомендация — отдельная пустая + остановленная кампания, чтобы робот не подходил к работающей рекламе. Нужно к задаче 16. +- Остальное — v12 §13. + +--- + +## 3. Как работать + +1. **Каждая правка: сначала тест, который краснеет, потом код.** Убедись, что красный — + по правильной причине, а не из-за опечатки в самом тесте (так уже было дважды). +2. **Защиту проверять вырезанием**: временно сломай боевой код, убедись, что тест краснеет, + верни обратно. +3. После каждой правки — **полный прогон обоих наборов**. +4. **Отчёты проверяющих проверяй сам по коду.** `rls-reviewer` в этой ветке дал верный GO, + но заодно заявил, что теста на индекс нет — он искал по имени индекса, а тест ловит отказ + базы. Тест был, и красным до миграции он был. +5. **Денежный диф читать глазами.** Выходов снятия заморозки ровно **четыре**. Пятого + не создавать. +6. Миграция → обязательно `rls-reviewer` и запись в `db/CHANGELOG_schema.md`. +7. **За собой убирать.** В конце `git status --porcelain` — только три старых промта. + +### 🪤 Грабли, дорого доставшиеся + +- 🪤 **Копируй файл в scratchpad перед мутацией и возвращай оттуда.** `git checkout -- <файл>` + откатывает файл к коммиту, то есть сносит и саму правку. +- 🪤 **Тест на защиту может быть пустышкой** — в этой ветке нашлось **четыре** таких: захват + кампании, рабочая папка робота, слепок при выдаче, предел веса картинки. Общее у всех: тест + не отличал «защита работает» от «защиты нет». Если проверяешь предел, порог или запрет — + вырежи его и убедись, что тест покраснел. +- 🪤 **Тест может быть ложно зелёным на незнакомом адресе**: неизвестный URL перехватывает + страница сайта и отвечает 200. Голого `assertOk()` мало. +- 🪤 **`Http::fake([...])` НЕ ловит незаглушенные адреса** — они уходят в настоящую сеть. + Добавил вызов к Яндексу в код — добавь заглушку во ВСЕ фейки соответствующих тестов. +- 🪤 **`final`-классы Mockery не мокает.** Ронять зависимость в тестах — через + `$this->app->bind(Класс::class, fn () => throw new ...)`. +- 🪤 **`pgsql_admin` в тестах виден** (`tests/Concerns/SharesAdminPdo.php` подключён глобально). + Обратная сторона: мутация «сменить соединение на дефолтное» НЕ покраснеет. +- 🪤 **`URL` в JS переводит кириллическое имя хоста в punycode** — ожидание теста, написанное + кириллицей, не совпадёт с текстом ошибки. +- 🪤 **Тесты гонять по одному процессу за раз.** База только `liderra_testing_reklama`. + Никогда не `liderra` (боевая) и не `liderra_testing` (общая). +- 🪤 **В тестовой базе копится мусор от прежних прогонов** — из-за него падала миграция + уникального слота. Чистить можно ТОЛЬКО тестовую базу, миграцию строгой не ослаблять. +- 🪤 `composer pint` без путей затирает чужие незакоммиченные правки — только + `cd app && ./vendor/bin/pint <файл> <файл>`. И перечитывай результат: pint двигает докблоки + и убирает скобки у `new`. +- 🪤 `->after('колонка')` в миграции на PostgreSQL — **no-op**. +- 🪤 **`.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` (бинарь в **корневом** репо). +- 🪤 `nodemailer` и `playwright` локально НЕ установлены. Модуль, который их импортирует, + нельзя проверить тестом — выноси чистую часть отдельно (`human.js`, `smtp.js`). +- 🪤 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`, только явные пути. +- **Открытые вопросы владельца не закрывать самому** — §2.3 выше. +- **Не создавать пятый вызов `release()`.** +- Владелец — **не программист**. Отчитываться простым русским, без программистских слов. +- **Останавливаться на логичных точках**, а не лететь без остановки. + +--- + +Отчитывайся владельцу простым языком. На коммит и на всё необратимое — спрашивай «go».