Files
portal/docs/superpowers/runbooks/2026-06-25-source-edit-test-acceptance-plan.md
T
Дмитрий 7053b02c9f docs: разбор швов + план + приёмка + design-gate разблокировки смены источника
Полный комплект под фичу «матч источника по слепку, без потери лидов»:
- findings: ультра-гранулярный аудит швов денежного ядра (9 субагентов, §9a-9c)
- plans: реализация в 6 эпиков (TDD, флаг отката, порядок выката)
- runbooks: план проверки (код + глаза + деньги, GO/NO-GO)
- specs: design-gate Путь A — согласован владельцем 25.06.2026

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 16:57:37 +03:00

19 KiB
Raw Blame History

План проверки: разблокировка смены источника без потери лидов

Дата: 2026-06-25 Связан с: план 2026-06-25-source-edit-unblock-snapshot-routing.md, разбор швов 2026-06-25-source-edit-block-snapshot-analysis.md. Назначение: как мы убедимся, что переделка работает и ни один лид не потерян, ни одного двойного списания. Три уровня: (1) автотесты по коду, (2) приёмка глазами по факту, (3) денежная приёмка отдельно.


Принцип (read first)

Готово ⟺ выполнены все три:

  1. 🟢 Код: все автотесты зелёные (Pest + Vitest), регрессия /regression full GREEN.
  2. 👁 Глаза: каждый пользовательский сценарий проверен руками на тест-стенде, со скриншотом «до/после».
  3. 💰 Деньги: баланс клиента и счётчики сходятся до копейки; журнал доставки — без дублей и без пропаж.

Любой красный пункт = NO-GO. Деньги — приоритет: при сомнении в денежном инварианте останавливаемся и зовём владельца.


Раздел 0. Где тестируем (НЕ на боевом)

  • Автотесты — локально/CI, composer test --parallel, npm run test:vue.
  • Ручная приёмка money-flow — на тест-стенде + имитация поставщика (app/tests/Support/Imitation/LeadInjector, SnapshotForge, сценарии ScenarioG3_OrphanLeadTest и др.). Имитация позволяет прогнать поток лидов и хвост источника БЕЗ реального поставщика и без риска для боевых денег.
  • Боевой liderra.ru — только финальная визуальная сверка UI (замок/баннеры) на своём проекте, без денежных операций, и только после GO. Перед любым выкатом — агент prod-deploy-validator.

Хвостовой лид по старому источнику и двойное списание нельзя проверять на боевом руками — только имитацией на стенде. Это деньги.


Раздел 1. Автотесты по коду (привязка к задачам плана)

Эпик / задача Тест-файл Что доказывает Цвет до→после
0.1 baseline SourceMatchCharacterizationTest лид по источнику доезжает (фиксируем «как было») 🟢🟢
2.1→2.2 ядро SourceMatchAfterChangeTest лид по старому источнику доезжает на след. день после смены 🔴🟢
2.3 группа SharedSourceUnaffectedTest сосед по общему источнику не пострадал 🟢
2.4 удаление DeleteSupplierProjectTailGuardTest sp не удаляется пока летит хвост/есть потребитель/есть снимок 🔴🟢
2.5 разблок SupplierSnapshotGuardTest смена источника больше не кидает 422; delete защищён 🔴🟢
2.6 источник из слепка SyncSourceFromSnapshotTest батч заказывает по источнику слепка, не live (окно 18:02→18:05) 🔴🟢
2.7 деньги NoDoubleChargeOnSourceChangeTest webhook+CSV одного лида = одно списание 🟢🟢 (страховка)
1.1 / 3.1 / 3.2 UX ProjectResourceSourceLockTest, ProjectDetailsDrawer.spec.ts поля статуса; баннеры/подтверждение; поля не залочены 🔴🟢
4.1 / 4.2 онлайн OnlineDeferWindowTest, FlushDeferredOnlineSyncTest онлайн молчит 18:00→00:00, досыл в 00:05 🔴🟢
5.1 отчёт SyncSummaryReportTest сводка завершения заливки шлётся 🔴🟢

Дополнительные тесты под швы из §9b/§9c (добавить в соответствующие задачи):

  • sms по sms_senders[0]+keyword (§9c-Q1, §9b sms): SmsSourceSnapshotMatchTest — лид Caranga+КРЕДИТ матчит проект с sender Caranga + keyword КРЕДИТ; лид Caranga (без keyword) НЕ матчит проект с keyword; коллизия «sender с keyword vs без» разводится верно.
  • root-domain (§9b): RootDomainSnapshotMatchTest — лид по корню carmoney.ru доезжает до подписчика на субдомен krasnoyarsk.carmoney.ru; обратное (лид-субдомен → корневой проект) НЕ матчит (направление сохранено).
  • DIRECT+sms фикс (§9c-Q5): DirectSmsNowMatchesTest — DIRECT+sms-лид, который раньше терялся, теперь доезжает (матч по sms_senders).
  • stale pivot (§9c-Q4): StalePivotCleanedOnSourceChangeTest — после смены источника старая связь (project_id, old_sp_id) удалена, проект не висит на двух источниках.
  • no_snapshot (§9b): NoSnapshotNoSilentLossTest — активный проект без слепка не теряет лид молча (либо слепок гарантирован, либо явная диагностика).

Команда прогона блока: cd app && composer test -- --filter='SourceMatch|SharedSource|DeleteSupplierProjectTailGuard|SupplierSnapshotGuard|SyncSource|NoDoubleCharge|SmsSource|RootDomain|DirectSms|StalePivot|NoSnapshot'


Раздел 2. Приёмка ГЛАЗАМИ по факту (ручные сценарии)

Каждый сценарий: предусловие → шаги (что нажать) → что должен увидеть → скриншот. Скрины складывать как accept-S<N>-<до|после>.png.

S1 — Замка «жди 2 дня» больше нет, есть понятное подтверждение

  • Предусловие: активный supplier-связанный проект (как Caranga на скрине владельца).
  • Шаги: Проекты → открыть проект → попытаться изменить «Источник — отправители SMS» → Сохранить.
  • Видеть: НЕ красный замок «изменить можно будет 27 июня». Вместо — окно подтверждения: «Мы уже ведём сбор на завтра. Лиды по старому источнику придут завтра в любом случае. Послезавтра — уже нет. Подтвердите смену.» После подтверждения — тост « Готово. Лиды по новому источнику пойдут со следующего дня.»
  • Провал: остался жёсткий замок, или сохранение молча не прошло.

S2 — Количество / регион / дни меняются свободно, с честным объявлением

  • Шаги: в том же проекте изменить «Лимит лидов в день» (или регион/дни) → Сохранить.
  • Видеть: поля НЕ серые/не заблокированы; после сохранения — объявление «Мы уже ведём сбор на завтра. Изменения вступят в силу с {дата}». Сохранение прошло (200), значение в карточке обновилось.
  • Провал: поле заблокировано, или нет объяснения когда вступит.

S3 — Новый проект: понятно, когда пойдут первые лиды

  • Шаги: Создать проект → заполнить → Сохранить.
  • Видеть: баннер «📣 Проект создан — Лидерра уже ставит его в сбор. Первые лиды по нему пойдут с {дата}».

S4 — Хвостовой лид по старому источнику доезжает (ТОЛЬКО имитация на стенде)

  • Предусловие: на стенде проект с источником Caranga, снят слепок на завтра, сменён источник на NewSender.
  • Шаги: через LeadInjector влить лид по СТАРОМУ Caranga «на следующий день».
  • Видеть в UI стенда: в проекте появилась сделка по этому лиду (Сделки → новая запись). Лид не потерян.
  • Провал: сделки нет (лид улетел в никуда) — это потеря денег, СТОП.

S5 — Отчёт о завершении вечерней заливки приходит

  • Шаги: на стенде прогнать вечерний цикл (слепок 18:02 → заказ 18:05) с имитацией.
  • Видеть: письмо/экран «Вечерняя заливка завершена в HH:MM — групп обновлено N, создано M, в ручной очереди K, расхождений 0». До 21:00 (успеть вмешаться).
  • Провал: «нормальный» прогон прошёл, а отчёта нет (как сейчас).

S6 — Онлайн-правка в окне 18:00→00:00 не уходит поставщику сразу

  • Шаги: на стенде выставить время 19:00, режим online, изменить лимит проекта.
  • Видеть (по журналу/очереди стенда): правка НЕ ушла поставщику немедленно, легла в отложенную очередь; в 00:05 досыл прошёл.

S7 — Клиент ПОНИМАЕТ правила (уведомления и объяснения)

  • Предусловие: обычный клиент в своём кабинете.
  • Шаги и что видеть:
    • создать проект → колокольчик/баннер «Проект в сборе. Первые лиды с {дата}»;
    • изменить лимит после 18:00 → «Изменения вступят в силу с {дата}»;
    • сменить источник → «Лиды по старому источнику придут до {дата}, дальше — по новому»;
    • поставить на паузу → «Сбор остановится сегодня в 18:00. До {дата} ещё могут прийти уже заказанные лиды — это нормально» (клиент НЕ пугается, что после паузы капают лиды);
    • возобновить → «Проект снова в сборе. Лиды пойдут с {дата}».
  • Видеть: уведомления в колокольчике + баннеры на экране; тексты понятные, без жаргона, с конкретными датами; тот же текст в баннере и в колокольчике (единый источник).
  • Провал: клиент видит изменение, но не понимает когда оно сработает; или после паузы пугается «хвоста»; или разные тексты в баннере и уведомлении.

Раздел 3. ДЕНЕЖНАЯ приёмка (отдельно, главное)

Цель — доказать два инварианта и кодом, и глазами: (И1) ни один лид не потерян. (И2) ни одного двойного списания.

Метод «баланс до/после» (глазами на стенде)

  1. Записать баланс клиента и счётчики delivered_today/delivered_in_month ДО прогона (Биллинг → баланс; карточка проекта → счётчики). Скрин money-before.png.
  2. Прогнать через имитацию ровно N лидов известного состава (часть — смена источника в процессе; часть — webhook+CSV одного лида).
  3. Записать баланс и счётчики ПОСЛЕ. Скрин money-after.png.
  4. Сверить вручную:
    • списано = N_доставленных × цена_тарифа (ровно, до копейки);
    • сделок создано = N_доставленных (Сделки → счётчик);
    • нет двух сделок по одному физическому лиду (по телефону+проекту);
    • delivered_count в слепке сходится с числом сделок.

Что проверяем по каждому шву (деньги)

Инвариант Как проверить глазами Авто-страховка
И2: webhook+CSV = 1 списание один лид влить и webhook'ом, и CSV-recovery; в Сделках — одна сделка, баланс упал на одну цену NoDoubleChargeOnSourceChangeTest
И1: хвост доезжает S4 выше — сделка появилась SourceMatchAfterChangeTest
И1: сосед не потерял смена источника у клиента A, лид по общему — у клиента B сделка есть SharedSourceUnaffectedTest
Тариф не съехал после прогона ступень тарифа клиента не «перепрыгнула» из-за лишних списаний покрыто И2
Счётчики сходятся live delivered_today = число сделок = delivered_count слепка NoSnapshotNoSilentLoss…

Сверка журналом (после прогона)

  • CsvReconcile drift = 0 (нет «лид пришёл, никому не доставлен»).
  • failed_webhook_jobs — нет новых записей по нашим лидам (нет retry-шторма).
  • Журнал supplier_lead_deliveries — по каждому лиду максимум одна строка на tenant.

Раздел 4. Регрессия и нагрузка

  • Полная регрессия: /regression full (Pest --parallel, Larastan, Vitest, Vite build, lychee, gitleaks) — вердикт GREEN. Денежные тесты должны быть внутри зелёного прогона.
  • Нагрузка (важно — 30k лидов/сутки): на стенде прогнать пиковый поток имитацией, убедиться что новый матч по слепку не просел по времени (матч теперь по индексу (snapshot_date, signal_type, lower(signal_identifier)) + sms по JSONB — проверить план запроса EXPLAIN). Цель: не медленнее текущего pivot-матча.
  • RLS-ревью: агент rls-reviewer на новые таблицы (supplier_deferred_sync) и изменённые запросы — изоляция tenantّов цела.

Раздел 5. GO / NO-GO перед боевым

Чеклист (все = GO):

  • Все автотесты Раздела 1 зелёные.
  • /regression full GREEN.
  • Денежная приёмка (Раздел 3): баланс/счётчики/сделки сходятся до копейки, 0 дублей, 0 пропаж — со скринами.
  • Ручные сценарии S1–S6 пройдены, скрины «до/после» собраны.
  • Нагрузка: матч не медленнее текущего (EXPLAIN + замер).
  • rls-reviewer — без замечаний.
  • prod-deploy-validator — вердикт GO.
  • План отката готов (см. ниже).

Порядок выката (поэтапно, не всё разом):

  1. Сначала Эпик 2 (ядро) — матч по слепку + отложенное удаление, БЕЗ снятия блокировки UX. На проде блокировка ещё стоит → клиент ничего не замечает, но ядро уже доводит хвост. Наблюдать сутки: drift=0, нет потерь.
  2. Затем Эпик 4 (онлайн-заморозка) и Эпик 5 (отчёт) — независимы.
  3. Только убедившись по факту, что хвост доезжает — Эпик 3 (снятие замка + баннеры). Это последним: пока он не выкачен, обещание клиенту не дано.

Откат: Эпик 2 — за флагом матча (slepok vs pivot), чтобы откатить раздачу на старый pivot-путь одним переключателем без релиза. Эпик 3 (UX) откатывается возвратом старого замка. Зафиксировать флаг в спеке 2.0.


Раздел 6. Пострелизный мониторинг (первая неделя)

  • Ежедневно: отчёт о вечерней заливке (Эпик 5) — групп/создано/ручная очередь/расхождения.
  • CsvReconcile business-drift — держать 0; рост = лиды теряются или дублируются.
  • Sentry — нет всплеска ошибок матча/списания.
  • Точечно глазами: на 2-3 реальных проектах после смены источника убедиться, что на след. день сделки по новому источнику пошли, а хвост по старому довёлся (по журналу).
  • Счётчик двойных списаний — отдельный запрос «два supplier_lead_deliveries/две сделки по одному телефону+проекту за сутки» = должно быть 0.

Сводка артефактов проверки

  • Скрины: accept-S1..S6-{до|после}.png, money-before.png, money-after.png.
  • Логи прогона имитации (состав N лидов, ожидаемые списания).
  • Вердикт /regression full, rls-reviewer, prod-deploy-validator.
  • Итоговый GO/NO-GO с подписью владельца (деньги — его решение).