1b7916ac69
Accessibility (Pa11y live) / a11y (push) Has been cancelled
Лиды не идут с 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>
170 lines
21 KiB
Markdown
170 lines
21 KiB
Markdown
# Результат приёмочного теста боевого liderra.ru — 22.06.2026
|
||
|
||
**Прогон:** 22.06.2026, ~05:46–07: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` — **целиком 28–29.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 §291–293). Не воспроизводится; менять 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.
|