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

21 KiB
Raw Blame History

Результат приёмочного теста боевого 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 🔴 клиент-видимый, чинить ДО продажи Сброс пароля полностью сломан. forgotPasswordPassword::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×/день каждый день (1421.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.

  • 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.
  • 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.