docs: промт смене переписан под состояние после пуша — развилка владельца первым делом

Прежняя редакция устарела за ночь: писалась до пуша, до плана выката и до находки
с телефонами. Переписан целиком, чтобы не было двух конкурирующих промтов.

Что изменилось по существу:

- Смена начинается с развилки, на которую владелец ещё не ответил: идти на боевой
  или сперва разобрать телефоны на ветке СМС. Рекомендуемый ответ дан — сперва
  выкат, он готов и снимает уже работающую беду, а ветка СМС не выкачена.
- Появилась дорога Б: 4 незамаскированных телефона в приёмочном листе СМС,
  коммит 868a681cf. Замерено, что он только на ветке prospects, на сервер не
  уезжал, в телеграм-ветке его нет. Правка истории необратима — записано делать
  только с разрешения владельца и предложив ему выбор, а не решать за него.
- Список файлов выката уточнён с 35 до 34: phpstan-baseline.neon на боевой не
  едет, он только для проверки кода у нас.
- Добавлены замеры сборки и миграций туда-обратно, которых в прежней редакции не
  было вовсе.
- Ловушка про обход сторожей дополнена главным: не обходить вслепую. Сперва
  gitleaks по своим коммитам через --log-opts, и только увидев ноль — исключать.
- Записано, что ветка живёт в отдельной рабочей папке и git checkout из корня на
  неё не переключится. Прошлая смена начала именно с этого вопроса.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Дмитрий
2026-08-02 23:51:33 +03:00
parent 04eb3c7f6e
commit 7e6e5c4f95
@@ -1,14 +1,16 @@
# Промт следующей смене — выкат телеграм-рекламы, четыре находки закрыты
# Промт следующей смене — выкат телеграм-рекламы готов, ждёт слова владельца
Поставлено 02.08.2026 ночью. Всё ниже — живые замеры этой смены, а не память.
Поставлено 02.08.2026 ночью, **после** пуша и подготовки выката. Всё ниже — живые замеры
этой смены, а не память. Переписан целиком: прежняя редакция этого файла устарела за ночь.
**Главный документ работы — [2026-08-02-ZAMECHANIYA-edinyy-vid-reklamnyh-ekranov.md](2026-08-02-ZAMECHANIYA-edinyy-vid-reklamnyh-ekranov.md).**
Там замечания владельца дословно, разбор всех пачек и находок; находки 1, 3, 4, 5 и «срок
показа» помечены закрытыми с номерами коммитов.
**Ветка `fix/tg-zagolovok-obyavleniya` = `5a41d1b37`, отправлена в gitea, неотправленного 0.**
Прежний промт [2026-08-07-PROMT-resheniya-vladelca-i-vykat.md](2026-08-07-PROMT-resheniya-vladelca-i-vykat.md)
**отработан**: владелец решил по всем четырём находкам, всё сделано и закоммичено. Осталась
только его вторая часть — **выкат**.
Опорные документы:
- **[2026-08-08-PLAN-vykata-telegram-reklamy.md](2026-08-08-PLAN-vykata-telegram-reklamy.md)** —
поимённый список 34 файлов, порядок шагов, откат, приёмка глазами. **Главное для выката.**
- [2026-08-02-ZAMECHANIYA-edinyy-vid-reklamnyh-ekranov.md](2026-08-02-ZAMECHANIYA-edinyy-vid-reklamnyh-ekranov.md) —
замечания владельца дословно, разбор всех пачек; находки помечены закрытыми.
---
@@ -27,149 +29,122 @@
---
## Что решил владелец 02.08 и что сделано
## 🔴 Смена начинается с развилки — спросить владельца ПЕРВЫМ ДЕЛОМ
| Находка | Решение владельца | Коммит |
|---|---|---|
| Значки, 23 имени вне рекламы | «Починить сейчас, заодно» | `b264e7e9` |
| Подпись «Выберите, кому показывать» | заодно | `b264e7e9` |
| Срок показа не написан нигде | «Дописать» | `b264e7e9` |
| «Моя база номеров» ведёт в никуда | **«Сделать загрузку базы сейчас»** | `00cc072e` |
| Цена не зависит от медиа | **«Развести цену»** | `3864f6f1` |
Ему задали вопрос в конце прошлой смены, ответа ещё нет:
🟢 **Запрет на выкат снят:** он держался на «Моей базе», она заработала.
> **Идти на боевой — или сперва разобрать телефоны на ветке СМС?**
### `3864f6f1` — цена показа по виду медиа
**Рекомендуемый ответ: сперва выкат.** Он готов, проверен и снимает уже работающую беду
(«Моя база» на бою ведёт в никуда). Телефоны на ветке СМС никуда не денутся и на бой не
влияют — та ветка не выкачена.
Работа была написана прошлой сменой, но **не проверена и не закоммичена** — лежала в рабочей
папке 17 изменёнными файлами. Эта смена её проверила, дописала недостающее и закрыла.
Не начинать ни то ни другое, пока владелец не ответил.
Замер вскрыл, что прежний тариф стоял на **двух неверных посылках**:
---
1. **Скидки за объём у МТС НЕТ** — прайс кабинета, стр. 13: 0,48 ₽ за показ плоско.
Ступенчатую скидку давали мы, а нам её не давал никто: на объёме от 50 000 наценка 1.40
превращалась в **5 %**.
## Дорога А — выкат на боевой
**Всё готово, вести по [плану выката](2026-08-08-PLAN-vykata-telegram-reklamy.md).** Здесь —
только то, что нельзя пропустить.
1. 🔴 **Пересчитать список файлов заново** — ветка могла уйти вперёд:
`git diff --name-only ad7a31271..HEAD -- app/ | grep -v '^app/tests/' | grep -v phpstan-baseline`
2. 🔴 **До заливки проверить право на счётчик.** В `client_tg_contacts` до 02.08 не вставляла
ни одна строка кода — право боем ни разу не проверялось:
`select has_sequence_privilege('crm_app_user', 'client_tg_contacts_id_seq', 'USAGE');`
Ответ `f``GRANT USAGE, SELECT ON SEQUENCE client_tg_contacts_id_seq TO crm_app_user;`
3. 🔴 **Полный overlay ЗАПРЕЩЁН** — поимённым списком.
4. 🔴 `config:cache` / `optimize`**от `www-data`, НЕ от root**. От root портал ложился на 18 минут.
5. 🔴 Две миграции: **v9.64** и **v9.65**. Обе проверены прогоном туда и обратно.
6. Экраны собирать **у нас**, на боевой везти готовый `public/build`.
**Приёмка глазами после выката — шесть пунктов в плане.** Тестовых учёток на бою нет с 01.07,
доступ у владельца.
## Дорога Б — телефоны на ветке СМС
🔴 **Сторож секретов находит 4 незамаскированных телефона** в
`docs/superpowers/2026-07-27-PRIEMKA-client-sms-fixes.md`, коммит `868a681cf` от 01.08.
Замерено этой сменой:
- коммит **только на ветке `feat/prospects-manual-testing-kp`**, в телеграм-ветке его нет;
- **на сервер не уезжал** — удалённой копии этой ветки нет;
- это **ПДн в коммите**, прямое нарушение §5 п.4 CLAUDE.md;
- пока он в истории, пуш той ветки будет упираться в сторожа.
Правка истории — необратимая операция, **делать только с явного разрешения владельца** и
предложив ему выбор (переписать историю ветки / замаскировать и жить с исключением в
`.gitleaks.toml` / что-то ещё). Не решать за него.
---
## Что сделано прошлой сменой — пять коммитов, все запушены
| Коммит | Что |
|---|---|
| `3864f6f1` | цена показа по виду медиа вместо ступеней по объёму, запись схемы **v9.65** |
| `b264e7e9` | срок показа в сводке, подпись про номера, все 23 значка |
| `00cc072e` | **«Моя база номеров» заработала** — снимает запрет на выкат |
| `48fed405` | находки помечены закрытыми, промт |
| `5a41d1b3` | план выката |
### Чего стоит не забыть про эти работы
🔑 **Тариф стоял на двух неверных посылках, обе вскрыты замером:**
1. **Скидки за объём у МТС НЕТ** — прайс кабинета, стр. 13: 0,48 ₽ за показ плоско. Скидку
давали мы, а нам её не давал никто: на объёме от 50 000 наценка 1.40 превращалась в **5 %**.
2. **Кабинет показывает CPM БЕЗ НДС** — колонка списка так и названа. Счёт кампании
`2231134` сошёлся: 420 × 400 ₽/1000 × 1,2 = 201,60 ₽. Мы ИП на УСН, НДС не возмещается.
Тариф стал плоским по видам медиа: себестоимость с НДС **0.480 / 0.720 / 0.816 ₽** за показ.
Миграция `client_tg_cena_po_media`, запись схемы **v9.65**. Экран админки переделан: три
фиксированные строки, рядом цена за тысячу для сверки с кабинетом.
Себестоимость с НДС теперь плоская: **0.480 / 0.720 / 0.816 ₽** за показ.
**Что дописала эта смена:** тест экрана тарифов остался на старом договоре и падал —
переписан под новый и **проверен вырезанием** (сломал передачу `media_kind` — красный;
сломал расчёт за тысячу — красный).
### `00cc072e` — «Моя база номеров»
Читающая половина была готова с самого начала (`TelegramAudienceService::fromBase` умеет
нормализацию, дубли и стоп-лист) — не хватало только записи. Добавлены `TelegramBazaService`,
три ручки `GET/POST/DELETE /api/telegram/contacts` и блок заливки в шаге «Кому показываем».
- заливка **заменяет** базу целиком, а не добавляет — написано на экране до нажатия;
- файл без единого годного номера отклоняется, старая база остаётся цела;
- потолок 200 000 номеров и 10 МБ; обрыв по потолку не молчит;
- наружу отдаются только счётчики — номера это ПДн, экрану они не нужны.
🔑 **«Моя база» вышла меньше, чем казалось:** читающая половина была готова с самого начала
(`TelegramAudienceService::fromBase` умеет нормализацию, дубли, стоп-лист) — не хватало
только записи. Заливка **заменяет** базу целиком, кривой файл отклоняется и старую базу не
трогает, наружу отдаются только счётчики.
---
## Работа смены: выкат на боевой — только с отдельного «да» владельца
### 🔴 Список выката пересчитан — файлов 35, а не 24
Прежний промт называл **24 файла**. Эта смена посчитала командой от последнего известного
выката (`ad7a31271`, 02.08 11:29 МСК, «заголовок объявления на боевой»):
```bash
git diff --name-only ad7a31271..HEAD -- app/ | grep -v '^app/tests/'
```
Получилось **35 файлов приложения** и **ДВЕ миграции**, а не одна:
- `2026_08_02_000001_add_headline_and_media_to_client_tg_auto_rule.php` (запись **v9.64**)
- `2026_08_02_000002_client_tg_cena_po_media.php` (запись **v9.65**)
🪤 Расхождение с прежним списком не разбиралось — возможно, там считали иначе. **Перед
выкатом список пересчитать командой заново**, а не брать ни один из двух на веру: между
этим промтом и выкатом ветка может уйти вперёд.
🪤 В списке есть файлы, которых эта задача не касалась (`AdvertisingCampaignController.php`,
`Mp4Probe.php`, экраны Яндекса) — они приехали пачками 1–3 и слиянием. Это не ошибка списка,
но глазами их стоит просмотреть отдельно.
### Порядок и красные линии выката
- 🔴 **полный overlay ЗАПРЕЩЁН** — катить поимённым списком;
- 🔴 `config:cache` / `optimize`**от `www-data`, НЕ от root**. От root портал ложился на 18 минут;
- 🔴 заливку скрипта и его запуск **не соединять** одной командой — виснет на две минуты;
- 🔴 вывод писать в файл; `echo` из tinker не доезжает. **Нет файла ⇒ скрипт УПАЛ**;
- 🪤 `sudo -u postgres psql` на боевой машине — **СТАРАЯ копия**. Живая база — Managed PG.
### 🔴 Одна вещь, которую надо проверить на бою перед заливкой
**Право `USAGE` на счётчике `client_tg_contacts_id_seq` для роли `crm_app_user`.**
В эту таблицу до сегодня не вставляла ни одна строка кода — значит право ни разу не
проверялось боем. Миграция таблицы (27.07) выдала `SELECT, INSERT, DELETE` **на таблицу**, но
на счётчик не выдала ничего.
Замер этой смены в тестовой базе: счётчик без права — **но ровно так же выглядит и
`client_tg_campaigns_id_seq`**, в который портал на бою пишет. То есть новых прав работа
скорее всего не требует (на бою счётчики закрывает общий `db/02_grants.sql`), но проверка
дешёвая, а пропущенный grant на бою даёт отказ, которого на dev не видно — на этом уже
обжигались.
Проверить так:
```sql
select has_sequence_privilege('crm_app_user', 'client_tg_contacts_id_seq', 'USAGE');
```
Ответ `f` ⇒ выдать:
```sql
GRANT USAGE, SELECT ON SEQUENCE client_tg_contacts_id_seq TO crm_app_user;
```
### Чем кончается смена
Либо «выкатили, вот что смотрел глазами на бою» — и тогда обновить `ПИЛОТ.md` и написать
промт следующей смене. Либо честное «сделал столько, дальше упёрся вот во что».
---
## Замеры этой смены — все командой, не по памяти
## Замеры прошлой смены — все командой
- Телеграм-модуль: `cd app && DB_DATABASE=liderra_testing_zag php artisan test --filter=ClientTg`
**325 тестов, 0 падений** (было 292: +12 базы номеров, +21 цены по медиа).
**325 тестов, 0 падений** (было 292).
- Экраны: `cd app && npx vitest run`**244 файла, 1855 тестов, 0 падений**, 3 пропущено.
- Статанализ: `cd app && composer stan`**0**. Формат: `composer pint -- --dirty` — чисто.
- Проверка типов: `npm run type-check`**6 ошибок, все в чужих файлах**, столько же было до
работы. Смотреть, не добавилось ли **своего** файла.
- Статанализ `composer stan`**0**. Формат `composer pint -- --dirty` — чисто.
- Проверка типов `npm run type-check`**6 ошибок, все в чужих файлах**, столько же было до.
- Сборка `npm run build`**прошла за 4,39 с**.
- Миграции: `migrate:fresh` — все с нуля; `migrate:rollback --step=2` + `migrate` — туда и
обратно, тариф после круга правильный.
🪤 Гонять из папки `app`, иначе «Could not open input file».
🪤 Один «Errors: 1» в прогоне экранов приходит из `AutopodborManualCompetitorError.spec.ts`
оборванный сетевой запрос в чужом наборе, был до нас. В одиночку этот файл проходит.
🪤 Один «Errors: 1» в прогоне экранов из `AutopodborManualCompetitorError.spec.ts`,
оборванный сетевой запрос в чужом наборе, был до нас. В одиночку файл проходит.
---
## Что осталось открытым
1. 🔴 **Числа 0.720 и 0.816 — вывод, а не замер.** Правило «кабинет пишет CPM без НДС»
подтверждено на кампании без медиа (счёт `2231134`), а счёт кампании **с картинкой**
живьём не снимали: шаг «Стоимость» недостижим без загрузки живых номеров. Замерите —
правится одной строкой в миграции и строкой в `client_tg_tariffs`.
2. 🟡 **Пузырь чата поддержки перекрывает кнопку «Дальше»** внизу мастера (координаты:
пузырь 838894×793849, кнопка 785873×792–828). Виджет общий для всего портала —
решение владельца не спрашивали, находка 2 осталась открытой.
3. 🔴 **Стена площадки: минимум 367 номеров**, у самого крупного клиента 112. Теперь клиент
может залить **свою базу** и порог перешагнуть — а значит пойдут живые деньги. Раньше
этой дороги не было вовсе.
1. 🔴 **Числа 0.720 и 0.816 — вывод, а не замер.** Правило «кабинет пишет без НДС»
подтверждено на кампании **без** медиа. Счёт кампании **с картинкой** живьём не снимали:
шаг «Стоимость» недостижим без загрузки живых номеров. Ошибка тут стоит денег на каждой
медийной кампании. Замерите — правится одной строкой в миграции и строкой в таблице.
2. 🔴 **Порог 367 номеров теперь перешагиваем.** У самого крупного клиента 112 номеров из
сделок, но после выката он может **залить свою базу** — и пойдут живые деньги там, где
раньше дороги не было вовсе.
3. 🟡 **Пузырь чата поддержки перекрывает кнопку «Дальше»** внизу мастера (пузырь
838894×793849, кнопка 785873×792–828). Виджет общий для всего портала. Единственная
находка приёмки, по которой владельца **не спрашивали**.
4. **Чтение вердикта модерации (`read-status`) не проверено ни разу.** Вскроется на первом
живом запуске.
живом запуске: не прочитает робот ответ МТС — кампания провисит 48 часов и уйдёт в «нужен
разбор», деньги при этом не вернутся.
5. **Токен робота засветился в выводе команды** 02.08 (в файлы и коммиты не попал).
Перевыпустить, когда будет удобно.
6. 🟡 **Битая ссылка в `CLAUDE.md`** на `лендинг/TZ_landing_v1_0.md` — файла нет ни в этой
ветке, ни в main. Сломано до нас, роняет сторожа ссылок на каждом пуше.
## ⏸ Отложено владельцем — НЕ поднимать
@@ -180,60 +155,75 @@ GRANT USAGE, SELECT ON SEQUENCE client_tg_contacts_id_seq TO crm_app_user;
## Ловушки
### Главная ловушка этой работы
### Главные
🔴 **Зелёные тесты не увидели ни одной из пяти находок.** Все вскрыл взгляд на живой экран.
Прежде чем говорить «работает», **открой и посмотри**.
🪤 **Незакоммиченная чужая работа выглядит как отсутствие работы.** Эта смена начала с
вопроса «куда делась работа» — а 17 файлов цены по медиа лежали в рабочей папке
`.claude/worktrees/tg-zagolovok`, не в основной. **Ветка стоит в отдельной рабочей папке**
`git checkout` из корня репозитория на неё не переключится, скажет «already used by worktree».
🪤 **Ветка живёт в ОТДЕЛЬНОЙ рабочей папке** `.claude/worktrees/tg-zagolovok`. `git checkout`
из корня репозитория на неё не переключится — скажет «already used by worktree». Прошлая
смена начала с вопроса «куда делась вся работа», а работа была там.
🪤 **Тест, который сам собирает состояние, охраняет пустоту.** «Моя база номеров» была
покрыта зелёными тестами — каждый сам вписывал контакты через `Contact::create`. До состояния
«контакты появились» не доходил ни один, потому что дойти было нечем. Проверяя источник
данных, спрашивай: **а кто в него пишет?**
🪤 **Незакоммиченная чужая работа выглядит как её отсутствие.** 17 файлов цены по медиа
лежали в рабочей папке непроверенными. Начиная смену — **смотреть `git status` в worktree**,
а не только в корне.
### Правка кода
🪤 **Тест, который сам собирает состояние, охраняет пустоту.** «Моя база» была покрыта
зелёными тестами — каждый сам вписывал контакты. До состояния «контакты появились» не
доходил ни один, потому что дойти было нечем. Проверяя источник данных, спрашивай: **кто в
него пишет?**
- 🪤 **Значки: имени нет в карте — молча вопрос в кружке.** Сторож
`tests/Frontend/znachki-est-v-karte.spec.ts` теперь без единой поблажки: список
«известного долга» пуст. Новое имя `mdi-*` обязано быть в `lucideMap`
(`resources/js/plugins/vuetify.ts`). **Букв в Lucide нет**`mdi-alpha-*` подбирать по
смыслу, искать точную букву бесполезно.
- 🪤 **После НОВОГО тестового файла на PHP статанализ краснеет** — шум `$this` в Pest,
гасится `app/phpstan-baseline.neon`. Пересобрать `--generate-baseline`, потом **глазами**
сверить diff: только добавления, удалений быть не должно (счётчик `count:` — исключение).
- 🪤 **Имён колонок наизусть не помнить.** Кампании Яндекса — `ad_campaigns`, объявления —
`ad_campaign_banners`, правило авто-телеграма — `client_tg_auto_rule` (единственное
число!), рекламный кошелёк — `ad_wallets`, контакты телеграма — `client_tg_contacts`.
- 🪤 **Статус модерации объявления пишется ПРОПИСНЫМИ** (`ACCEPTED` / `REJECTED`).
### Пуш
### Гит и сторожа
🪤 **Штатно падают два сторожа, оба не на нашей работе:** `gitleaks-full-history` (4 телефона
на ветке СМС, см. дорогу Б) и `lychee-links` (битая ссылка в CLAUDE.md). Отключать на **один**
пуш: `LEFTHOOK_EXCLUDE=gitleaks-full-history,lychee-links git push …`
- 🪤 **Номер записи в `db/CHANGELOG_schema.md` брать как максимум по ВСЕМ веткам плюс один.**
Последняя занятая — **v9.65**.
- 🪤 **Коммитить явными путями.** Машинный `docs/observer/STATUS.md` переписывается хуком
после каждого коммита и молча уезжает в следующий. Снимать `git restore --staged`.
- 🪤 **Штатно падают два сторожа, оба не на нашей работе:** `gitleaks-full-history` и
`lychee-links`. Отключать на **один** пуш:
`LEFTHOOK_EXCLUDE=gitleaks-full-history,lychee-links git push …`.
- 🪤 Сообщение коммита держать в **файле** и звать `git commit -F <файл>` — командная строка
должна быть без скобок.
🔴 **Но не обходить вслепую.** Прошлая смена сперва проверила, что находки чужие и старые:
`gitleaks detect --log-opts="<первый>^..<последний>"` по своим коммитам дал ноль. Обход без
такой проверки — это протаскивание секрета мимо сторожа.
### Опорные точки — добавились новые
🪤 **Коммитить явными путями.** Машинный `docs/observer/STATUS.md` переписывается хуком после
каждого коммита и молча уезжает в следующий. Снимать `git restore --staged`. Хук ещё и
дописывает СВОЙ коммит поверх — не пугаться, что указатель на сервере на один впереди.
К прежнему списку (`tg-tabs`, `tg-wizard`, `tg-reach`, `tg-svodka`, `tg-launch`, `tg-media`,
`help-hint` и остальные) прибавились:
🪤 Сообщение коммита держать в **файле** и звать `git commit -F <файл>` — командная строка
без скобок.
### Код
🪤 **Значки: имени нет в карте — молча вопрос в кружке.** Сторож
`tests/Frontend/znachki-est-v-karte.spec.ts` теперь **без единой поблажки**, список долга
пуст. Новое имя `mdi-*` обязано быть в `lucideMap` (`resources/js/plugins/vuetify.ts`).
**Букв в Lucide нет**`mdi-alpha-*` подбирать по смыслу, точную букву искать бесполезно.
🪤 **После НОВОГО тестового файла на PHP статанализ краснеет** — шум `$this` в Pest, гасится
`app/phpstan-baseline.neon` через `--generate-baseline`. Потом **глазами** сверить diff:
только добавления; единственное допустимое удаление — строка `count:`.
🪤 **Имён колонок наизусть не помнить.** Кампании Яндекса — `ad_campaigns`, объявления —
`ad_campaign_banners`, правило авто-телеграма — `client_tg_auto_rule` (единственное число!),
рекламный кошелёк — `ad_wallets`, контакты телеграма — `client_tg_contacts`.
🪤 **Статус модерации объявления пишется ПРОПИСНЫМИ** (`ACCEPTED` / `REJECTED`).
🪤 **Номер записи в `db/CHANGELOG_schema.md`** — максимум по ВСЕМ веткам плюс один.
Последняя занятая — **v9.65**.
### Опорные точки экранов — добавились новые
`tg-svodka-srok`, `tg-baza`, `tg-baza-count`, `tg-baza-file`, `tg-baza-clear`,
`tg-baza-itog`, `tg-baza-error`, `tg-tariff-kind-*`, `tg-tariff-price-*`,
`tg-tariffs-save-error`.
🔴 **`tg-tariff-add` и `tg-tariff-remove-*` больше НЕ существуют** — ступени тарифа убраны,
строк ровно три и они фиксированы.
🔴 **`tg-tariff-add`, `tg-tariff-remove-*`, `tg-calc`, `tg-estimate` больше НЕ существуют.**
---
## Как проверять свою работу — правило этого проекта
**Зелёный тест не доказывает ничего, пока не видел его КРАСНЫМ.** Прошлая смена проверяла
вырезанием: ломала передачу `media_kind` — красный; ломала расчёт за тысячу — красный;
опустошала список поблажек в стороже значков — красный ровно на 23 имени с адресами файлов.
Работа идёт **пачками**: после каждой владелец делает компакт. Не хвататься за всё сразу.