Files
portal/docs/superpowers/specs/2026-06-21-acceptance-RUN-report.md
T
Дмитрий 1b7916ac69
Accessibility (Pa11y live) / a11y (push) Has been cancelled
docs: поправка по лидам + сверка приёмки раунд-2 + статусы долгов 23.06
Лиды не идут с 3 июня = намеренное решение владельца он сам выключил
проекты подтверждено 23.06 не дефект. Развёрнута вчерашняя поправка
ушедшая в крайность реальный инцидент. Новый файл-разбор
2026-06-23-LEADS-STOPPED-correction.md с доказательствами прода
read-only SSH плюс баннеры во всех связанных доках. Телефон-источник
замаскирован.

Приёмка раунд-2 сверена живьём все 5 закрыты M-1 M-2 apiv1-rate на проде
hash 21 schema v8.51 Раздел B тесты.

Долги докалки проверены 23.06 всё ещё открыты deptrac 1 живое нарушение
ProjectResource SupplierSnapshotGuard larastan env-квирк диагностируемость
молчаливого дропа no_snapshot_skipped всё ещё Log info.

Только docs прод и код не тронуты.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 17:51:55 +03:00

170 lines
21 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Результат приёмочного теста боевого liderra.ru — 22.06.2026
**Прогон:** 22.06.2026, ~05:4607:30 МСК, на боевом `liderra.ru`. Кодовая фраза стены: «роутер-наставник».
**Исполнитель:** сессия-исполнитель по `2026-06-21-acceptance-EXECUTOR-LAUNCH-prompt.md`.
**Метод:** провижининг тест-популяции (SQL + портал), контролируемые инъекции вебхука с прод-сервера (vid TEST-диапазон `9e12..9.99e12`), сверка в боевой БД (read-only), живой онбординг владельцем (реальная капча + код из письма), нагрузка с safety-стопом, полный teardown.
---
## 🟢 ВЕРДИКТ: **GO**
Деньги сходятся копейка-в-копейку, изоляция без утечек, аудит-цепочки зелёные после уборки, ядро (раздача→списание→сделка→изоляция) проходит сквозь на боевом, критдефектов нет. **Омега (tenant 2, «Компания 1») за весь прогон не тронута: 1 835 400.00 ₽ / 1013 сделок — до и после.** UX/мелкие находки GO не блокируют (список ниже).
---
## Сводка по пунктам
| Этап / пункт | Статус | Заметка |
|---|---|---|
| R1 провижининг (7 клиентов / 16 проектов / 14 rt у поставщика) | PASS | rt созданы у crm.bp-gr.ru (реальные external_id, sync ok) |
| R2 инъекционный стенд (секрет/allowlist/слепок/вебхук) | PASS | инъекция с прод-сервера 127.0.0.1, vid TEST-диапазон |
| R3 CAP=3 (раздача ≤3 разным) | PASS | московский лид → ровно 3 региона-82 |
| R3 идемпотентность (повтор vid) | PASS | already_processed, без новых сделок/списаний |
| R3 лимит под локом + перелив фаза-2 | PASS | лимит 10 не пробит; перелив на всяРФ-клиента |
| R3 M-DOUBLEPROJ (клиент с 2 проектами) | PASS | 12 лидов = 12 списаний (не 24); субдомен→корень |
| R3 каскад фаза-1/2/3 + подмена | PASS | точный→всяРФ→подмена; город = РЕАЛЬНЫЙ (Краснодарский край) при subject_code=82 |
| R3 DIRECT (bare project) | PASS | platform=DIRECT, матч по типу+идентификатору |
| R3 M1 тройная запись + копейки | PASS | lead_charges+balance_transactions+supplier_lead_costs согласованы |
| R3 M-TIER (тарифная ступень) | PASS | 101-й лид → T2 65₽ (живой тариф сменился сегодня) |
| R3 M-INSUFF (нехватка → откат+автопауза) | PASS | баланс 50₽: 0 сделок, 0 списаний, проект на автопаузе |
| R3 изоляция (портал 404 + RLS-роль + негатив) | PASS | C1 не видит C2 (404); crm_app_user контекст 17 → 0 чужих |
| R3b онбординг (живая капча + код из письма) | PASS | tenant 24: pending→active, баланс 0→300₽; реальная Yandex-капча решена владельцем |
| R3b гейт реквизитов | PASS | новый аккаунт без реквизитов → проект создать нельзя |
| R3b колокольчик + дайджест | PASS | 266 in-app; дайджест-письмо ~250 сделок на kdv1@bk.ru — **доставлено и подтверждено владельцем** |
| R3b импорт CSV (не списывает) | PASS | 2 сделки, 0 списаний, баланс не дрогнул; нулевая historical_import строка |
| R3b отчёты (build + изоляция) | PASS | deals_export done, привязан к tenant |
| R3b G6 публичный API (изоляция + 401) | PASS | по ключу только свои сделки; без/битый ключ → 401; rate-limit 120/мин есть |
| R3b G7-A поддержка | PASS | заявка создана (RLS-scoped) |
| R3b G7-B impersonation | SKIP (код-верифик.) | админ-сайд, зона M-1; вживую не гонялся |
| R4 нагрузка/ёмкость | PASS | ~258 лид/мин (≈37× к цели 7/мин); память ~1.35ГБ своб; деньги под нагрузкой точны |
| R5 F1 деньги (копейки) | PASS | у всех тест-клиентов баланс = старт − Σсписаний, точно |
| R5 F2 изоляция финал | PASS | 0 утечек |
| R5 F3 аудит (после teardown) | PASS | 79 цепочек intact, 0 mismatch |
| R5 teardown (rt→слепки→тенанты→Redis) | PASS | до 21:00; 0 TEST-остатков; омега цела |
| R3 all-pairs 27 (полная матрица) | PARTIAL | покрыты представители всех осей (S×R×B×Lim×D) реальными инъекциями; полный перебор 27 пере-слепков — не гонялся (выборочно) |
| Часть F (CsvReconcile, freeze-sweep, D-PAUSE, D-MERGE, T-* UI edge) | PARTIAL/SKIP | ядро инвариантов покрыто; отдельные edge-карточки не гонялись вживую |
---
## Деньги (F1)
- Каждый тест-клиент: `balance_rub == 100000 Σ(lead_charges)/100`**сходится точно** (`reconciles=t`).
- Пример: C1 — 16 списаний = 1110₽ → баланс 98890 (до импорта); под нагрузкой 200 лидов C1/C2/C6 12990/14000 — точно по ступеням.
- Тройная запись согласована; копейки (bcmath) не теряются; откат при нехватке — атомарный.
## Изоляция (F2)
- Портал: чужая сделка → 404; свой счётчик = БД.
- RLS-роль `crm_app_user` под контекстом 17: свои 12, чужих 0.
- Публичный API: по ключу только свой тенант.
- **0 утечек.**
## Аудит (F3)
- После teardown `audit:verify-chains`**зелёный** (79 intact, 0 mismatch), омега и глобальные цепочки целы.
## Ёмкость (слой D)
- Один воркер @ 2 ГБ / 2 vCPU: **~258 лид/мин ≈ 370k/сутки теоретически** против цели 10k/сутки → **~37× запас**.
- Память под нагрузкой стабильна (~1.35 ГБ свободно), swap не тронут, SAFETY-стоп не понадобился.
- Узкое место на этом масштабе не достигнуто. **Железа на старте хватает с большим запасом.**
- ⚠️ Замер представительный (250 лидов), не полный ramp до 3000 — но насыщение явно >> 10k/сут.
## Маржа (F4)
- Не считалась отдельно (тест-баланс; себестоимость поставщику в инъекции = справочная). Опускаю как информативную.
---
## Находки (НЕ блокеры GO)
| ID | Важность | Суть |
|---|---|---|
| **FN-RESET** | 🔴 клиент-видимый, чинить ДО продажи | **Сброс пароля полностью сломан.** `forgotPassword``Password::sendResetLink` → дефолтное Laravel-уведомление строит URL через `route('password.reset')`, которого НЕТ (SPA-роут сброса = `/reset/:token`, имя `reset-password`). Итог: `Route [password.reset] not defined` → клиент видит «Произошла ошибка», письмо со ссылкой не уходит никому. Побочно ломает анти-перебор (существующий email → ошибка, несуществующий → нейтральный ответ = enumeration-вектор). Найдено владельцем post-run на info@lkomega.ru. Фикс: `ResetPassword::createUrlUsing(...)` в `AppServiceProvider::boot()` → ссылка на `/reset/{token}?email=...`. Не money/isolation → формальный вердикт GO не меняет, но обязателен до передачи продажникам (клиент не восстановит пароль). **✅ ИСПРАВЛЕНО в коде** (подтверждено `app/app/Providers/AppServiceProvider.php:88-92`, проверка 22.06.2026), **✅ НА ПРОДЕ (проверено 22.06.2026)**. |
| **FN-AUDIT** | для разработчика | Во время прогона `verify-chains` краснеет на свежих app-строках (balance_transactions/activity_log/pd_processing_log/tenant_operations_log, текущий месяц) — по логике глобальной per-partition функции не должны; прогон записал transient-инциденты на проде. **Самоизлечивается после teardown** (хвост удалён → зелёный). Омега/глобальные (`auth_log`/`saas_admin`) никогда не задеты. Передать: почему свежие строки sharing-flow не верифицируются + чистка ложных инцидентов m06. |
| FN-1 | мелкая (supplier) | смс+слово создал у поставщика только B2, B3 группы не создан (по коду должно B2+B3). На деньги/изоляцию не влияет. |
| FN-2 | косметика | боевой дефолт `users.notification_preferences` содержит мёртвый ключ `reminder` (фича удалена) — расходится с `db/schema.sql:795`. **✅ ИСПРАВЛЕНО в коде** (миграция `app/database/migrations/2026_06_22_120000_drop_reminder_from_notif_prefs_default.php`, проверка 22.06.2026), **✅ НА ПРОДЕ (проверено 22.06.2026)**. |
| FN-3 | мелкая | онбординг-confirm не ставит `users.email_verified_at` (аккаунт всё равно активен, вход работает). **✅ ИСПРАВЛЕНО в коде** (подтверждено `app/app/Services/Auth/RegistrationService.php:115`, проверка 22.06.2026), **✅ НА ПРОДЕ (проверено 22.06.2026)**. |
| NB-DIRECT | к сведению | DIRECT-проекты синкаются к поставщику как site → дают 3 rt (R1 ожидал 0). На DIRECT-инъекцию не влияет. |
| M-1 / M-2 | из prep-review | M-1 (админ fail-open) и M-2 (капча) — по launch §2 выкачены/закрыты; M-2 подтверждён вживую (реальная капча решалась владельцем). M-1 — бизнес-риск к сведению владельцу (админ-зона на nginx). |
### Находки из логов боевого (22.06, read-only post-run)
| ID | Важность | Суть |
|---|---|---|
| **FN-SESSION** | 🔴 операционная, ЖИВАЯ | Заход к поставщику через Playwright падает **~121×/день каждый день** (14–21.06; 766 за 7 дней): `SupplierAuthException: PlaywrightBridge exit code 1`. Переустановка браузера 21.06 не вылечила. **Ломает `CsvReconcileJob`** (страховочную сверку, подбор пропущенных лидов) + проактивное обновление сессии. Вероятно связано с «лиды не идут» (страховка-сеть down). Разобрать запуск Playwright под www-data на проде (HOME/permissions/headless-shell версия). |
| **FN-LOGIN-ROUTE** | 🟡 тот же класс, что FN-RESET | `Route [login] not defined` (свежий, 21.06 + 08.06) — какой-то flow строит `route('login')`, которого в SPA нет. Чинить вместе с FN-RESET (общий шаблон: дефолтные Laravel-маршруты, которых SPA не объявляет). |
| **FN-FAILEDJOBS-PILE** | ✅ СДЕЛАНО 22.06.2026 | `failed_jobs` = 490к мёртвых строк `DomainException: B1 platform does not support SMS`**целиком 2829.05** (буря вашиденьги24, корень уже исправлен майским UPDATE 292/293→sms). **`queue:flush` выполнен на проде: было 494 191 → стало 0.** |
| FN-ENC | 🟢 минор | 4× `invalid byte sequence for encoding UTF8` — лид с битой кодировкой падал в очереди (исторически). |
| FN-RLS-CTX | 🟢 минор (истор.) | 2× `unrecognized configuration parameter "app.current_tenant_id"` (29.05) — путь запроса к `projects` без RLS-контекста. Старое, не воспроизводится свежо. |
> **Вывод по логам:** основной шум (collision 2001× / B1+SMS 490k / routing_failed_permanently 667×) — **исторический майский шторм** (вашиденьги24), корень починен, нужна только уборка `failed_jobs`. Единственная **живая** операционная проблема — **FN-SESSION** (Playwright-заход к поставщику валится 121×/день → CsvReconcile не работает). Почта/деньги/раздача сейчас здоровы.
## Поправка от 22.06.2026 (разбор FN-SESSION)
> Дописано после диагностики на проде (read-only). Запись выше сохранена как была;
> здесь — уточнения. Полный разбор: [docs/superpowers/runbooks/2026-06-22-FN-SESSION-diagnosis-and-ops-fix.md](../runbooks/2026-06-22-FN-SESSION-diagnosis-and-ops-fix.md).
- **FN-SESSION**: корень — отсутствовал исполняемый файл браузера в кэше www-data
(`chromium.launch` падал). Утверждение «переустановка 21.06 НЕ вылечила» — **неверно**:
вылечила (ошибок «PlaywrightBridge exit code» после 21.06 12:36 нет). Дополнительно
исправлен код-баг диагностируемости (launch вне try/catch → опасный exit-1-двойник
«login rejected»); теперь отказ запуска = exit 4 + JSON. Остаётся ops-шаг владельца:
`playwright install` под www-data в деплое (профилактика рецидива).
- **«Лиды не идут с 3 июня»** — **не дефект: владелец сам выключил проекты** (намеренно,
подтверждено 23.06.2026). По сути верно. Уточнения 23.06: (1) выключены проекты главного
клиента **tenant 2**, не только «омеги»; (2) FN-SESSION (сломанный браузер 4–21.06) — отдельная
реальная, уже починенная проблема того же периода, не первопричина; (3) если поставщик пришлёт
лид на заблокированный проект, тот падает МОЛЧА (`no_snapshot_skipped`=Log::info, невидим при
LOG_LEVEL>=warning) — мелкое необязательное улучшение наблюдаемости. Контекст:
[docs/superpowers/findings/2026-06-23-balance-block/2026-06-23-LEADS-STOPPED-correction.md](../findings/2026-06-23-balance-block/2026-06-23-LEADS-STOPPED-correction.md).
- **`supplier.session.refreshed`=0 в логе** — артефакт уровня логирования (`Log::info`
отфильтрован, на проде `LOG_LEVEL>=warning`), не сбой: refresh работает.
### Статус мелких находок (фикс-сессия 22.06)
- **FN-3** — ✅ починен: `RegistrationService::confirm` теперь ставит `users.email_verified_at`
(через `forceFill` — поле вне `$fillable`). TDD `ConfirmSetsEmailVerifiedAtTest`.
- **FN-2** — ✅ починен: миграция `2026_06_19_130000` забыла `ALTER COLUMN ... SET DEFAULT`,
реальный DB-дефолт `notification_preferences` оставался с мёртвым `reminder`. Новая
миграция `2026_06_22_120000` + TDD `NotifPrefsDefaultNoReminderTest` + CHANGELOG v8.50.
- **FN-1** — ❎ **не баг кода**: `SupplierProjectGrouping::resolvePlatforms` для смс+слово
уже возвращает `['B2','B3']` (корректно). Непоявление B3 — supplier-side «Doubles»
(HTTP 200 `status=Doubles`), который `SyncSupplierProjectJob` сам детектит как
«incomplete platform set (transient miss: B3)» и ретраит. Отдельная supplier-диагностика,
не локальный фикс.
- **FN-ENC** — ✅ починен: один битый UTF-8-байт в CSV-выгрузке ронял весь импорт на
INSERT (PG «invalid byte sequence»). `CsvLeadsParser::parse` теперь чистит невалидный
UTF-8 (`mb_convert_encoding`) до парсинга. TDD `CsvLeadsParserUtf8Test` + руками
подтверждено (PG отвергал raw-байт, принимает санитайзенное).
- **FN-RLS-CTX** — ❎ **no-action** (уже разрешено): путь `projects` идёт через
`tenant`-middleware (ставит `app.current_tenant_id`); старый no-auth
`GET /api/projects?tenant_id` (источник 29.05) заменён auth+tenant-версией
(web.php §291293). Не воспроизводится; менять RLS вслепую непропорционально.
## Новые находки после прогона
- **FN-INN-LOOKUP** (🟡, найдено владельцем 22.06 на проде): Настройки → Реквизиты →
Тип лица «ЮРЛИЦО», кнопка **«Найти по ИНН» не находит организацию** по валидному ИНН
юрлица (пример: `2460090423` — реальное ООО) → «Организация не найдена по ИНН».
Это DaData party-lookup (`TenantRequisitesController::lookupInn` / `PartyLookup`,
флаг `services.dadata.party_enabled` + ключ DaData). Вероятно на проде lookup выключен
или мисконфиг ключа/эндпоинта. Заполнение названия вручную работает — на гейт реквизитов
не блокирует. **Разобрать позже** (read-only диагностика: флаг + ключ DaData на проде).
## Критдефекты
- **Нет.**
## Teardown
- Выполнен полностью до 21:00. rt у поставщика удалены (DeleteSupplierProjectJob), слепки/тест-тенанты/orphan по vid вычищены, Redis digest-ключи убраны.
- Чистота TD5: 0 TEST-тенантов, 0 тест-площадок, 0 тест-лидов.
- **tenant 2 (омега) ЦЕЛ: 1 835 400.00 / 1013.**
- **tenant 24 (онбординг kdv1@bk.ru, 300₽) — ✅ УДАЛЁН 22.06.2026** (soft-delete тенант+пользователь, 0 сделок/транзакций; активных тенантов 1; обратимо, бэкап на проде).
- `auth_log` тест-строки оставлены намеренно (N-3, глобальная цепочка) — это допустимая остаточность.
---
## Эфир
14 карточек живого «телевизора» (`live-demo/efir.html`, шаги 1–14): старт→клиенты→проекты/заказ→первый лид→лимит/перелив→защита денег→каскад/тариф→API→импорт→нагрузка→деньги-итог→онбординг(рег→300₽)→колокольчик/дайджест.
## Что осталось владельцу (follow-up, не блокирует продажу)
- ~~Решить судьбу tenant 24 (онбординг-аккаунт) — оставить или удалить.~~ ✅ УДАЛЁН 22.06.2026 (soft-delete).
- Глянуть в админке crm.bp-gr.ru, что тест-rt `B*_test-*` исчезли (подтверждение TD1 глазами).
- FN-AUDIT → разработчику (почему verify краснеет на свежих строках + чистка transient-инцидентов m06).
- M-1 (админ fail-open) — бизнес-решение о defense-in-depth.