docs(реклама показы): промт перезапуска v5 и снимок состояния после закрытия заморозки

Риск «заморозка не снимается» в снимке помечен закрытым — коммит dd894a52.
Промт v5 самодостаточен: где что лежит, что сделано, что осталось по приоритету,
грабли окружения.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Дмитрий
2026-07-27 09:48:24 +03:00
parent dd894a5224
commit a384fc43d9
2 changed files with 136 additions and 6 deletions
@@ -42,12 +42,19 @@
## 🔴 Открытые вопросы / риски (решить ДО реального списания на бою)
- **Заморозка не снимается при завершении/остановке.** `AdWalletService::release()` не зовётся
НИГДЕ в приложении (пред-существующая дыра модели кошелька, не из Части 4). Теперь, когда кампании
доходят до `completed` (фикс `paid_impressions`), «зависшая» заморозка стала заметной: `frozen_rub`
только растёт, свободный баланс клиента занижается. **Нужно решение владельца:** снимать HOLD при
`completed`/стопе/удалении (позвать `release`), где именно и по какой семантике. Отдельная небольшая
задача по модели кошелька.
- ~~**Заморозка не снимается при завершении/остановке.**~~ ✅ **ЗАКРЫТО 27.07.2026, коммит `dd894a52`.**
Разбор показал, что дыра была ШИРЕ и БЛОКИРУЮЩЕЙ: `charge()` уменьшал `balance_rub`, но не трогал
`frozen_rub` — одни и те же рубли считались дважды, свободный остаток (`balance frozen`) уходил в
минус, а `AdWalletGate::isSolvent()` проверяется СРАЗУ после списания в `ChargeCampaignSpendJob`
клиент объявлялся неплатёжеспособным после ПЕРВОГО суточного списания и `AdStopAll` глушил все его
кампании. Кампания умерла бы после первого дня показов даже при полном кошельке.
Исправлено: заморозка тает вместе со списанием + снимается на всех **4 выходах**
`completed` (`CampaignImpressionCharger`), `rejected` (`SyncCampaignModerationJob`),
`stopped_no_funds` (`PauseCampaignsOnAdStop`), `paused` (контроллер); `resume` морозит
неоткрученный остаток сметы ДО обращения к Директу и отдаёт 409 при нехватке денег.
Решение владельца по паузе: на паузе деньги свободны, при возобновлении морозим снова.
Удаление кампании выходом НЕ является — `destroy` разрешён только для черновика.
Тесты рекламного модуля 156/156. Промт продолжения — `2026-07-27-PROMPT-restart-reklama-pokazy-v5.md`.
- **Отчёт показов Директа** — контракт (TSV/async/кэш по ReportName) подтверждён только по документации,
живьём проверяется в Задаче 9.
- **Один адаптивный креатив** оформляется вручную в веб-конструкторе Яндекса; номер вписывает оператор
@@ -0,0 +1,123 @@
# ПРОМТ перезапуска сессии — реклама «за показы», после закрытия заморозки (27.07.2026)
Скопировать содержимое блока ниже целиком в новую сессию.
---
```
Ты — Claude Code в проекте Лидерры. 🔴 БОЕВОЙ ПРОД liderra.ru / lk.liderra.ru — ЖИВЫЕ
КЛИЕНТЫ И ДЕНЬГИ. Любой выкат, правка боевой БД, включение рубильников — ТОЛЬКО с
явного разрешения владельца. БД по умолчанию только чтение.
🔴 ОБЯЗАТЕЛЬНО инвокируй подходящие скилы ПЕРВЫМИ (Pravila §12 hard-rule — skill
вызывается ДО работы, §9 «Отступления» к нему не применяется). Не «на глаз» без скилов.
Разговор с владельцем — простым русским, без программистских слов: он не программист.
════════ СНАЧАЛА ПРОЧИТАЙ ════════
1. 📌 ГЛАВНОЕ — состояние стройки: docs/superpowers/2026-07-27-HANDOFF-reklama-pokazy-chast4-yadro-STATE.md
2. Дизайн и решения по клиентскому Яндексу:
docs/superpowers/specs/2026-07-24-yandex-audience-dlya-klientov-РЕШЕНИЯ.md
docs/superpowers/specs/2026-07-24-yandex-audience-dlya-klientov-design.md
3. Хвосты прошлого слоя (реклама за клики, уже на бою):
docs/superpowers/findings/2026-07-25-reklama-modul-zamechaniya-zhivoy-test.md
docs/superpowers/plans/2026-07-25-yandex-reklama-ux-audit-fixes.md
4. Контракт медиа-API Директа: docs/superpowers/findings/2026-07-26-direct-media-api-contract.md
(раздел «⚠️ Проверить при go-live»)
5. CLAUDE.md §0/§1/§3/§5 + docs/Plugin_stack_rules_v1.md (Vue 3 + Vuetify 3, Forest-палитра).
6. Память: project-reklama-modul-vykat-2026-07-25, feedback-worktree-laravel-windows,
feedback-brauzer-lovit-to-chego-ne-vidyat-testy, feedback_environment.
════════ ГДЕ ЧТО ЛЕЖИТ ════════
• Рабочая папка стройки: .claude/worktrees/reklama-pokazy, ветка feat/reklama-yandex-pokazy,
HEAD = dd894a52, ЗАПУШЕНА в gitea, незапушенных коммитов 0.
• main = a487c660 = gitea/main = боевой сервер БАЙТ В БАЙТ (598 файлов бэкенда сверены 27.07).
• 🔴 GitHub origin ЗАБЛОКИРОВАН (account suspended, 403) — пуш ТОЛЬКО в gitea.
• 🪤 Основная рабочая папка стоит на чужой ветке fix/warming-existing-manager-dedup
(8 незапушенных коммитов по телеграм-боту МТС + 255 новых файлов) — НЕ ТРОГАТЬ.
• 🟠 Ещё 6 веток нигде не сохранены, только на этой машине: feat/client-sms-broadcast 15,
fix/warming-existing-manager-dedup 8, feat/aleksandra-knowledge-base 86,
feat/external-services-online-monitoring 47, feat/extsvc-followup-2 60,
backup/before-pdn-clean-20260719 172. Предложить владельцу выгрузить в gitea.
════════ СОСТОЯНИЕ НА 27.07.2026 ════════
СЛОЙ 1-2 — реклама «за клики» + UX-правки: ВЫКАЧЕНЫ И ЖИВЫ на lk.liderra.ru.
Сделано 19 из 21 задачи UX-аудита. Не сделаны: T16 различимые имена черновиков,
T21 шаг 3 живая оплата картой (внешняя блокировка — go-live ЮKassa не завершён).
СЛОЙ 3 — реклама «за показы» (CPM): ГОТОВА в ветке, НЕ влита в main, НЕ на проде,
рубильник Директа ВЫКЛ. 23 коммита. Тесты рекламного модуля 156/156.
ЧТО ЗАКРЫТО В СЕССИИ 27.07 (коммит dd894a52):
Заморозка рекламного кошелька. Была дыра: freeze ставился один раз при запуске на
всю смету, а release не вызывался НИГДЕ. Главное следствие было блокирующим —
charge уменьшал balance_rub, но не трогал frozen_rub, одни и те же рубли считались
дважды, свободный остаток (balance − frozen) уходил в минус, а isSolvent проверяется
сразу после списания в ChargeCampaignSpendJob → клиент объявлялся неплатёжеспособным
после ПЕРВОГО суточного списания и AdStopAll глушил все кампании. Кампания умерла бы
после первого дня показов при полном кошельке.
Исправлено: заморозка тает вместе со списанием + снимается на всех 4 выходах —
completed (CampaignImpressionCharger), rejected (SyncCampaignModerationJob),
stopped_no_funds (PauseCampaignsOnAdStop), paused (контроллер), а resume морозит
неоткрученный остаток сметы ДО обращения к Директу и отдаёт 409 при нехватке денег.
Решение владельца по паузе: на паузе деньги свободны, при возобновлении морозим снова.
Удаление кампании выходом НЕ является — destroy разрешён только для черновика.
════════ ЧТО ОСТАЛОСЬ (по приоритету) ════════
🔴 БЛОКИРУЮЩЕЕ (без этого реклама не работает):
1. Рубильник Директа. В боевом .env есть YANDEX_DIRECT_TOKEN, но НЕТ
YANDEX_DIRECT_ENABLED (default false) и НЕТ YANDEX_DIRECT_BASE_URL
(default = ПЕСОЧНИЦА api-sandbox.direct.yandex.com). Доступ к API одобрен 26.07.
Переключить на https://api.direct.yandex.com + ENABLED=true, затем
php artisan config:cache ПОД www-data (квирк #107 — иначе портал ляжет).
2. Задача 9 — контролируемый первый живой запуск: одна кампания, крошечный сегмент,
минимальный SpendLimit; 200 OK на каждом методе; тип CPM_BANNER_CAMPAIGN; id
проставлены; НЕМЕДЛЕННО suspendCampaign до начала открутки. Сверить реальные имена
enum/полей с контрактом (файл п.4) и поправить код при расхождении.
⚠️ ТОЛЬКО по отдельному «go» владельца.
3. Отчёт показов getCampaignImpressions вживую: формат TSV vs async/поллинг; что
уникальный ReportName даёт СВЕЖИЕ данные, а не кэш; что тело — одно число показов.
От этого зависит корректность списаний.
🟠 КРУПНОЕ:
4. Влить показы в main и выкатить по ранбуку
docs/superpowers/runbooks/2026-06-18-gitea-prod-deploy-pipeline.md.
⚠️ При выкате: на боевом ad_settings сейчас колонка markup_percent = 30.00, а ветка
использует ad_margin_percent = 40% — проверить миграцию наценки, это ДЕНЬГИ.
⚠️ При новых RLS-таблицах ПЕРЕзапустить db/03_service_bypass_policies.sql, иначе
джобы и админ-экран увидят тихий ноль (грабля srv_bypass).
5. РОБОТ (Часть C) — робот-креативщик вместо ручного оформления макета в конструкторе
Яндекса, по образцу docs/superpowers/specs/2026-07-26-mts-telegram-ads-bot-design.md.
Сначала дизайн-спека → одобрение владельца → стройка.
6. ВЫБОР (Часть A) — выбор клиенту в мастере: Яндекс сам нарежет 15 размеров из одной
картинки ИЛИ клиент грузит свои 15; плюс объяснение, что делает ИИ Яндекса.
🟡 ХВОСТЫ СЛОЯ 2 (уже на бою):
7. T16 — различимые имена черновиков (сейчас все «Реклама от 25.07.2026»).
8. T21 шаг 3 — живая оплата картой, только с владельцем; без успешного платежа
«работает» НЕ заявлять.
9. Админ-экран «Реклама — расход и маржа» живьём не пройден, нужен админ-доступ.
10. Pa11y по авторизованным рекламным экранам (сейчас конфиг бьёт только по публичным
localhost-страницам).
⚪ УБОРКА:
11. AdvertisingCampaignEndpointTest идёт БЕЗ изоляции (нет RefreshDatabase/
DatabaseTransactions) и копит строки в liderra_testing → при полном прогоне ложно
краснеют соседи. Изолировать.
12. Larastan в этом worktree не запускается (junction-vendor рушит bootstrap) — гонять
composer stan на обычном чекауте перед вливанием.
════════ ГРАБЛИ ОКРУЖЕНИЯ ════════
• Перед прогоном тестов: DB_DATABASE=liderra_testing php artisan migrate:fresh --force
• Прогон: vendor/bin/pest tests/Feature/Advertising (156/156 зелёные на 27.07).
Полный vendor/bin/pest — 3570 тестов, 3548 проходят; 22 падения НАКОПИТЕЛЬНЫЕ
(изолированно на чистой БД все зелёные), к рекламе отношения не имеют.
• Коммит: LEFTHOOK_EXCLUDE=larastan, сообщение БЕЗ круглых скобок, по эскейпу владельца,
явными путями (каталог /моя/ в .gitignore — git add пропускает молча).
В worktree «Can't find lefthook in PATH» — безобидно.
• 🪤 В рабочей папке лежит ЧУЖАЯ незакоммиченная правка
app/tests/Feature/Advertising/CampaignImpressionChargerTest.php — НЕ коммитить как своё.
════════ С ЧЕГО НАЧАТЬ ════════
Прочитать п.1–4, показать владельцу короткий статус простым языком и спросить, что
берём: go-live Директа (пп.1–3, нужен его «go»), выкат показов (п.4), РОБОТ (п.5),
ВЫБОР (п.6) или хвосты. Ничего не выкатывать и рубильники не включать без разрешения.
```