docs телеграм-робот: промт следующей смене после сведения веток

Прежний промт помечен устаревшим - в нём закрыт хвост Х1, изменилось состояние
ветки и у хвоста про тестовую базу нашёлся более грубый корень. Оставлен ради
разбора, как разруливать столкновение номеров журнала схемы.

В новом промте пять хвостов, и один из них новый и срочный: в полном прогоне
одиннадцать падений подряд с ответом живого Яндекс Директа "Invalid OAuth token".
Похоже, протух ключ доступа, а на бою в Директе запущена живая кампания с
замороженными деньгами. Порядок разбора - сначала посмотреть, а не чинить, и
доложить владельцу до любых действий с токеном.

По тестовой базе записан готовый прибор: сравнить число записей о применённых
миграциях с числом файлов миграций. Больше файлов - в базу лезет чужая рабочая
папка, меньше - сборка оборвалась. Замер в промте: на общей базе телеграм давал
42 из 244, на своей 244 из 244 два раза подряд.

Хвост про хрупкий тест внешних сервисов, возможно, отпал сам - в последнем
прогоне на своей базе он не упал. В промте написано, как это проверить и как
закрыть запись честно.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Дмитрий
2026-08-01 03:39:14 +03:00
parent 72db586a12
commit bba56f6a1c
3 changed files with 258 additions and 12 deletions
+6 -12
View File
@@ -1,6 +1,6 @@
# Brain Status (auto-generated)
Last updated: 2026-07-31T07:17:46.606Z
Last updated: 2026-08-01T00:24:12.107Z
| Контролёр | Состояние | Детали |
|---|---|---|
@@ -15,7 +15,7 @@ Last updated: 2026-07-31T07:17:46.606Z
⚠️ **ПОСТ ПУСТОЙ** — не зарегистрированы: enforce-floor.mjs, enforce-supreme-gate.mjs, enforce-normative-content-rules.mjs, enforce-read-path-deny.mjs, enforce-mcp-classification.mjs, enforce-judge-gate.mjs, enforce-snapshot.mjs, enforce-floor-escape-consume.mjs, enforce-skill-journaler.mjs, enforce-verify-gate.mjs, enforce-criterion-gate.mjs, enforce-coverage-verify.mjs, enforce-todowrite-skill-verifier.mjs (оборона НЕ подтверждена; SE-B/Δ8)
Судья М4: **live-block** (inert $0 / shadow / floor-only / live-block)
Судья М4: **inert** (inert $0 / shadow / floor-only / live-block)
| Машина / страж | Хук | Зарегистрирован |
|---|---|---|
@@ -112,9 +112,9 @@ Episodes since last run: 542 / threshold: 10
| PID | Имя | CPU-время | Возраст |
|---|---|---|---|
| 3544 | MsMpEng | 16.61ч | NaNч |
| 23936 | Code | 4.76ч | 0.0ч |
| 4 | System | 2.51ч | NaNч |
| 3544 | MsMpEng | 24.19ч | 1328736.4ч |
| 23936 | Code | 8.07ч | 0.0ч |
| 4 | System | 3.50ч | 0.0ч |
⚠️ Проверь, не «осиротевшие» ли это процессы от завершённых Claude-сессий.
@@ -130,13 +130,7 @@ Episodes since last run: 542 / threshold: 10
## Целостность журналов действий
🔴 Битые цепочки (3 из 120):
| session | broken at seq |
|---|---|
| `03437265-6d58-4622-aeed-c0eeac0f2c32` | 1 |
| `54594686-843c-4ea8-bcd3-5ae6a7244e30` | 14 |
| `9c02276d-dabb-40e4-9c04-44c18d47485a` | 14 |
Ключ подписанта не provisioned — проверка цепи недоступна (ключ — owner-шаг A3).
## Алерт-индикаторы
@@ -1,5 +1,9 @@
# Промт следующей смене — добить хвосты по телеграм-роботу и тестовой базе
> ⛔ **УСТАРЕЛ 01.08.2026 — читай `2026-08-02-PROMT-hvosty-posle-svedeniya.md`.**
> Хвост Х1 закрыт, состояние ветки изменилось, у хвоста Х2 найден более грубый корень.
> Этот файл оставлен ради разбора «как разруливать столкновение номеров журнала схемы».
Дата постановки: 31.07.2026, поздний вечер. Всё ниже проверено живыми командами.
**Коротко:** работа по телеграм-роботу закончена и запушена — на опрос переведены все
@@ -0,0 +1,248 @@
# Промт следующей смене — хвосты после сведения веток
Дата постановки: 01.08.2026, ночь. Всё ниже проверено живыми командами.
Заменяет `2026-08-01-PROMT-dobit-hvosty.md` — там хвост Х1 уже закрыт, читать тот файл
незачем, кроме как ради разбора «как разруливать столкновение номеров».
**Коротко:** телеграм-робот доделан, ветка сведена с основной, всё запушено. На бой ничего
не выкачено. Осталось пять хвостов, и **один из них новый и срочный** — похоже, протух
ключ доступа к Яндекс Директу.
---
## Где ты работаешь
Рабочая папка `.claude/worktrees/telegram-merge`, ветка `fix/robot-yandex-zamok`.
Хранилище — **только gitea**, GitHub мёртв.
- верхушка `72db586a`, всё запушено;
- ветка **впереди на 24** коммита и **не отстаёт** от `gitea/main` (сведено 01.08).
Папка снаряжена: свои `composer install` и `npm ci`, свои сторожа. В **главной** папке
репозитория сторожа мертвы — см. хвост Х3.
🔴 **Тестовую базу бери свою.** Общая `liderra_testing` испорчена (подробности в Х2).
Все прогоны ниже делай так:
```bash
cd .claude/worktrees/telegram-merge/app
DB_DATABASE=liderra_testing_tgmerge php artisan test
```
База `liderra_testing_tgmerge` уже создана и собрана. Если развалится — лечится так:
```bash
cd .claude/worktrees/telegram-merge/app
DB_DATABASE=liderra_testing_tgmerge APP_ENV=testing php artisan db:wipe --drop-views --drop-types --force
DB_DATABASE=liderra_testing_tgmerge APP_ENV=testing php artisan migrate --force
```
## Что уже сделано — не переделывать
| Что | Где |
|---|---|
| Канал робота, очередь заданий, аренда | `TgRobotController`, `TelegramRobotQueue`, `RobotJob` |
| Применение итога запуска / вердикта / пересдачи | три применяльщика `Telegram*ResultApplier` и `TelegramModerationVerdictApplier` |
| Развилки по каналу у обоих джобов | `PollTelegramModerationJob`, `ResubmitTelegramCampaignJob` |
| Развилка режимов у робота | `bots/mts-telegram-ads/src/poll-plan.js` |
| Сборка тестовой БД один раз в начале прогона | `app/tests/TestCase.php` |
| Сторож против пересборки посреди прогона | `app/tests/Feature/Database/TestDbRebuildGuardTest.php` |
| Сведение с основной + перенумерация журнала схемы | коммит `72db586a` |
Переключатель `TG_ROBOT_TRANSPORT`: `process` (умолчание, старый путь) | `poll`.
Сейчас `process` — поведение не изменилось. Откат = вернуть `process`.
**Чем проверено (на своей базе):** телеграм 244/244, робот 124/124, статанализ 0,
код-стиль чисто, полный прогон **4063 теста / 4018 прошло / 17 упало**. До сведения было
4039 тестов и 20 падений — тестов больше, падений меньше.
---
## Хвост Н1 🔴 НОВЫЙ и срочный — похоже, протух ключ доступа к Яндекс Директу
В полном прогоне **11 падений подряд** в `tests/Feature/Advertising/CreativeRobotEndpointTest.php`,
и все с одним ответом живого Яндекса:
```
Yandex Direct v5/creatives/get error: {"error_code":53,"error_detail":"Invalid OAuth token",
"error_string":"Authorization error"}
```
То есть тест реально ходит в Директ и получает отказ авторизации. Токен ставили 25.07.2026.
**Почему это срочно:** на бою в Директе 30.07 запущена живая кампания `713175197` с
замороженными 3333.36 ₽. Если ключ протух — реклама могла встать, а мы об этом узнаём
из упавшего теста, а не из тревоги.
Что сделать по порядку:
1. **Сначала посмотреть, а не чинить.** Проверить, тот же ли токен в `app/.env` на бою и
локально, и когда он выдан. Доступ к бою — **только с разрешения владельца**, база по
умолчанию только чтение.
2. Спросить у самого Директа состояние кампании `713175197` (показ идёт или встал).
3. Доложить владельцу **до** любых действий с токеном — это его учётка и его деньги.
🪤 Не чинить это правкой теста и не глушить его. Тест — единственное место, которое сейчас
кричит правду.
---
## Хвост Х2 — тестовая база: две разные болезни, не путать
**Что уже вылечено** (не переделывай): база пересобиралась посреди прогона и сносила себя
под уже отработавшими тестами. Корень был в самом Laravel — подробности в шапке
`app/tests/TestCase.php`. Лечение: собираем базу сами один раз в начале и держим флаг
взведённым на каждом тесте.
### Х2б — общая база на все рабочие папки. 🔑 Доказано замером
В `liderra_testing` оказалось **180 записей о применённых миграциях при 146 файлах** на
диске. Записей БОЛЬШЕ, чем файлов — прямое доказательство, что в базу пишет ещё кто-то
(другая рабочая папка со своим набором миграций).
Следствие: `db:wipe` рапортует успех, но остаётся призрачный составной тип `legal_entities`,
и **первая же миграция** падает на `pg_type_typname_nsp_index`. Схема остаётся полусобранной,
прогон рассыпается сотнями «таблицы не существует» — при полностью исправном коде.
Замер: на общей базе телеграм давал 42/244 и 48/244, на своей — **244/244 два раза подряд**,
и база после прогона оставалась целой.
🔑 **Готовый датчик:** сравнить число записей в таблице `migrations` с числом файлов
миграций. Больше файлов — в базу лезет чужой; меньше — сборка оборвалась. И то и другое
значит «верить прогону нельзя».
```bash
cd .claude/worktrees/telegram-merge/app
DB_DATABASE=<база> APP_ENV=testing php artisan tinker --execute="echo DB::table('migrations')->count().' записей / '.count(glob(database_path('migrations').'/*.php')).' файлов'.PHP_EOL;"
```
**Что осталось сделать:** закрепить это в репозитории — своя база на рабочую папку.
🔴 Это **решение владельца**, потому что меняет общий конфиг. Готовое предложение:
в `app/phpunit.xml` строку `<env name="DB_DATABASE" value="liderra_testing"/>` оставить как
есть (она без `force="true"`, поэтому переменная окружения её перебивает), а порядок
«перед прогоном задай свою базу» записать в `ЭТАЛОН.md`. Так ничего не ломается у тех,
кто запускает по-старому.
### Х2а — нет изоляции между тестами
В `app/tests/Pest.php` строка `->use(RefreshDatabase::class)` закомментирована с 08.05.2026;
изоляцию заказывают 81 файл из 570. Остальные 489 пишут в базу и ничего за собой не убирают.
Отсюда оставшиеся счётные падения («ждали 1, получили 2»).
🔴 Включать изоляцию всем — **решение владельца**: её и выключили из-за тестов, которые
ходят в базу по нескольким подключениям сразу (для них есть приёмы `SharesAdminPdo` /
`SharesSupplierPdo`). Прежде чем предлагать — **замерь цену**: включи, прогони полный набор
на своей базе, посчитай, сколько тестов покраснеет. Без этого числа разговора нет.
🪤 Не путать две болезни: изоляция даёт счётные падения, чужой писатель — «таблицы не существует».
---
## Хвост Х3 — сторожа мертвы в главной папке репозитория
В `c:\моя\проекты\портал crm\Документация\node_modules` лежит **3793 файла вместо 66 619**.
Проверка разметки и орфографии там падает с `MODULE_NOT_FOUND` — правки в главной папке
уходят **непроверенными**, и никто этого не видит.
```bash
cd "c:/моя/проекты/портал crm/Документация" && npm ci
```
Проверка, что помогло: `find node_modules -type f | wc -l` должно дать около 66 600, и
`node node_modules/markdownlint-cli2/markdownlint-cli2-bin.mjs README.md` должен выдавать
замечания, а не `MODULE_NOT_FOUND`.
🪤 Установка умеет молча положить десятую часть файлов и отрапортовать «added N packages».
Сравнивать надо **объём папки**, а не текст отчёта.
---
## Хвост Х4 — хрупкий тест внешних сервисов (возможно, уже отпал)
`tests/Feature/External/ExternalServiceDownAlertTest.php` раньше падал в полном прогоне и
проходил в одиночку. **В последнем полном прогоне на своей базе он НЕ упал.** Похоже, его
валила испорченная общая база, а не порядок тестов.
Что сделать: прогнать полный набор на своей базе ещё раз и посмотреть, появится ли он в
списке падений. Появится — делать тест самодостаточным. Не появится — вычеркнуть хвост,
записав, что причиной была база.
---
## Хвост Х5 — мелочь, но врущая
В `tests/Feature/ClientTg/TgRobotDoneTest.php` служебный токен задан **русскими буквами**
(`'секрет'`). В заголовок HTTP русские буквы не проходят физически — настоящий робот такой
токен отправить не может. Тест закрепляет невозможное. Заменить на латиницу, как в новых
тестах (`'tg-secret-1'`).
---
## Что НЕ трогать без явного «да» владельца
**Выкат на бой и включение опроса.** Упирается не в код: нужно решение, **где будет жить
робот** — МТС не пускает адреса дата-центров. Зависит от разговора с поставщиком прокси
(обращение **31074**). Порядок выката — `docs/superpowers/2026-07-31-tg-robot-ustanovka.md`.
Ключевое оттуда:
1. Полный прогон + агент `prod-deploy-validator` — вердикт ГОДЕН/НЕГОДЕН.
2. Миграции ролью `crm_migrator`, сперва `--pretend`.
3. 🔴 **ПЕРЕзапустить `db/03_service_bypass_policies.sql`.** Файл таблично-агностичен,
перечислять новую таблицу не надо — нужен именно **прогон**. Не прогонишь → канал
робота увидит ноль заданий **молча**.
4. `TG_ROBOT_TOKEN` в `.env` портала (**только латиница**), `config:clear` от `www-data`.
5. `TG_ROBOT_TRANSPORT=poll`**последним шагом**, когда робот уже стоит и отвечает.
6. Приёмка — **глазами в кабинете МТС**. Журнал говорит, что портал ПОДУМАЛ, а не что было.
## Красные линии
**Прод — живые клиенты и деньги.** Любой выкат, миграция на бою, движение денег — только
по явному «да» владельца. База по умолчанию **только чтение**.
🔴 Номера телефонов — ПДн. В задание робота их класть нельзя. Отдельный запрос, только пока
задание в работе, без кеша, и только режимам запуска. Это сделано — не «улучшать».
🔴 Три применяльщика итога трогают деньги. Правки — только отдельным коммитом и только
после того, как оба пути зелёные.
**Пуш только в gitea.** Коммит — по эскейпу, сообщение **без круглых скобок**, в файле,
явные пути.
## Ловушки
- 🪤 **Число записей в `migrations` ≠ число файлов** — прогон недостоверен, см. Х2б.
- 🪤 **`<env>` в `phpunit.xml` без `force="true"` НЕ перебивает** уже заданную переменную
окружения. Поэтому `DB_DATABASE=... php artisan test` работает как надо.
- 🪤 **`Artisan::call` исключений не бросает** — возвращает код возврата. Проверять надо и
код, и факт.
- 🪤 **`db:wipe` рапортует «Dropped all tables successfully» и при этом оставляет призрачный
тип**, если базу параллельно правит кто-то ещё. Проверять фактом, а не текстом отчёта.
- 🪤 **Джоб в тесте нельзя звать напрямую с подставным роботом** — статанализ ругается на
тип. Принято `Job::dispatchSync()` + `app()->instance(Runner::class, $mock)`.
- 🪤 **Тест опросчика вердикта не увидит кампанию без `SharesSupplierPdo`.**
- 🪤 **Перед перегенерацией списка исключений статанализа пересчитай настоящие:**
`composer stan 2>&1 | grep -o '"message":"[^"]*"' | grep -vc 'PendingCalls'`. Должно быть 0.
- 🪤 **cspell может упасть на словах из ЧУЖОГО коммита** — не твоя опечатка. Смотри, чьи
строки, и добавляй в `cspell-words.txt`, если это имя собственное.
- 🪤 **`composer pint` правит и чужие файлы.** Перед коммитом откатить всё, что не твоё.
- 🪤 **`docs/observer/STATUS.md` не пре-стейджить** — его перегенерирует хук после коммита.
- 🪤 **Фабрики у `ClientTg\Campaign` нет** — кампания создаётся руками.
- 🪤 **`grep 'pending'` по выводу `migrate:status` ловит имена колонок** (`add_pending_count`)
и врёт про «есть неприменённые». Считать по факту, а не по подстроке.
- 🪤 **`python -c` через bash съедает кавычки** — для разбора логов класть скрипт в файл.
- 🪤 **`/tmp` в аргументе bash превращается в windows-путь, а внутри python-строки — нет.**
- 🪤 `pkill -f` по ssh **убивает собственную сессию**. Бить по номеру процесса.
- 🪤 Локально Node 24, на рендер-сервере Node 20: `node --test test/` работает только на 20.
Локально запускать `npm test`.
- 🪤 Полный прогон идёт **~14 минут**. Запускать фоном, не ждать в цикле со сном.
## Как разговаривать с владельцем
Он **не программист** — простым русским, короткими абзацами, без «эндпоинтов» и «деплоя».
«Готово» — только если правда проверил; упало — сказать честно. Он ценит замер вместо
догадки: утверждаешь «сломалось не от меня» — покажи прогон до и после, а не рассуждение.
## Чем кончается смена
Либо «хвосты закрыты, вот замеры», либо внятное «упёрлись вот в это, решает владелец».
Код — с тестами, обычным пушем в gitea.