# R5 — Финал: сверки, аудит, teardown, отчёт (приёмочный тест liderra.ru)

**Owner:** исполнитель | **Зависит от:** R0–R4 (прогон завершён). База: `prod-logic-map.md` §6, §9, §12; хартия teardown.
**Когда:** после прогона R3/R4. ⚠️ **Teardown тест-проектов — ДО 21:00 МСК** (иначе поставщик зафиксирует слепок и начнёт слать реальные платные лиды).

## Назначение
Свести итоговые сверки (деньги/изоляция/аудит целы), посчитать маржу, **полностью убрать** тест-данные и оформить отчёт.

## ⚠️ Безопасность teardown (читать перед удалением)
- Удаляем **только** тест-объекты по маркеру `TEST-`. **НИКОГДА** не трогаем `lkomega`/tenant 2 и сидовые тенанты.
- **Перед любым DELETE** — сначала `SELECT` списка id на удаление и глазами убедись, что среди них **нет tenant 2 / lkomega / сидовых**.
- Часть таблиц (`activity_log`, `balance_transactions`, прочие audit) **immutable** (`audit_block_mutation` блокирует DELETE) → нужен trigger-bypass под `postgres` (см. TD3).
- Точный каскадный скрипт **пара к скрипту провижининга R1** — финализируется вместе с R1 (после деплоя онбординга).

---

## Карточки уровня C — сверки

### F1 — Итоговая сверка денег (копейка-в-копейку)
**§ref:** §6. **Что:** по каждому тест-тенанту Σ всех списаний = (старт-баланс − текущий), без потерь.
**Действие:**
```
ssh liderra-prod 'sudo -u postgres psql -d liderra -tA -c "
  SELECT t.id, t.balance_rub,
         (SELECT COALESCE(sum(price_kopecks),0) FROM lead_charges lc WHERE lc.tenant_id=t.id)
  FROM tenants t WHERE t.name LIKE '\''TEST-%'\''"'
```
**Ожидаем:** для каждого: `(старт_баланс − текущий)*100 == Σ price_kopecks` его списаний (старт-баланс знаешь из R1). Возвраты/topup учесть в формуле.
**Если не так:** копейки не сходятся → **СТОП**, критдефект денег, не делай teardown, доложи.
**Покрывает:** денежный инвариант по всему прогону.

### F2 — Итоговая сверка изоляции
**§ref:** §9. **Действие:** под двумя тест-тенантами повторно открой ключевые разделы; негативный SQL-тест (ISO-NEG из R3).
**Ожидаем:** 0 утечек.
**Если не так:** утечка → **СТОП**, критдефект, доложи.
**Покрывает:** изоляция (финальная проверка).

### F3 — Аудит-цепочки целы
**§ref:** §12. **Действие:**
```
ssh liderra-prod 'cd /var/www/liderra/app && php artisan audit:verify-chains'
```
**Ожидаем:** зелёно по всем 6 audit-таблицам (цепочка хешей не побита прогоном). Попытка UPDATE/DELETE записи ledger → блок (`audit_block_mutation`).
**Если не так:** verify-chains красный → разобрать, какая таблица/тенант, доложи.
**Покрывает:** целостность аудита.

### F4 — Отчёт по марже (для владельца, не тест)
**§ref:** §6. **Действие:**
```
ssh liderra-prod 'sudo -u postgres psql -d liderra -tA -c "
  SELECT t.id,
    (SELECT COALESCE(sum(price_per_lead_kopecks),0)/100.0 FROM lead_charges WHERE tenant_id=t.id) AS revenue_rub,
    (SELECT COALESCE(sum(c.cost_rub),0) FROM supplier_lead_costs c
       JOIN deals d ON d.id=c.deal_id AND d.received_at=c.received_at
       WHERE d.tenant_id=t.id) AS cost_rub
  FROM tenants t WHERE t.name LIKE '\''TEST-%'\''"'
```
**Ожидаем:** сводка выручка (с клиента) vs себестоимость (поставщику) → маржа на лид по прогону. Информативно.
**Покрывает:** экономика прогона.

---

## Карточки teardown (после успешных сверок)

### TD1 — Убрать тест rt-проекты у поставщика (ДО 21:00 МСК!)
**§ref:** §3, §14 (`DeleteSupplierProjectJob`). **Действие:** удали тест-проекты в портале → авто-джоб чистит rt-проекты на crm.bp-gr.ru; либо админ bulk-delete списка supplier_projects.
**Ожидаем:** в админке поставщика тест-rt-проектов (`B*_test-*`) нет.
**Если не так:** rt-проект остался → удали вручную в админке поставщика. **Критично до 21:00.**
**Покрывает:** чистка у поставщика.

### TD2 — Убрать строки слепка за тест-даты
**Действие:**
```
ssh liderra-prod 'sudo -u postgres psql -d liderra -c "
  DELETE FROM project_routing_snapshots
  WHERE tenant_id IN (SELECT id FROM tenants WHERE name LIKE '\''TEST-%'\'')"'
```
**Ожидаем:** строки слепка тест-тенантов удалены. (snapshot — не audit, DELETE проходит.)
**Покрывает:** слепки инъекций.

### TD3 — Удалить тест-тенанты и их данные (каскад, trigger-bypass)
**§ref:** §12 (audit-block). **Что:** удалить всё по маркеру `TEST-`. Audit-таблицы immutable → нужен bypass под `postgres`.
**Действие:**
```
# 1) СНАЧАЛА покажи, кого удаляем — глазами проверь, что нет tenant 2/lkomega/сидовых:
ssh liderra-prod 'sudo -u postgres psql -d liderra -tA -c "SELECT id, name FROM tenants WHERE name LIKE '\''TEST-%'\''"'
# 2) удаление в одной транзакции с отключением триггеров (audit_block_mutation) — ТОЛЬКО postgres:
ssh liderra-prod 'sudo -u postgres psql -d liderra -c "
  BEGIN;
  SET LOCAL session_replication_role = replica;   -- отключает триггеры, вкл. audit-block
  -- порядок: дети → родители; <IDS> = список из шага 1
  DELETE FROM supplier_lead_deliveries WHERE tenant_id IN (<IDS>);
  DELETE FROM lead_charges            WHERE tenant_id IN (<IDS>);
  DELETE FROM balance_transactions    WHERE tenant_id IN (<IDS>);
  -- supplier_lead_costs БЕЗ tenant_id (N-1!) → через deal_id, ДО удаления deals:
  DELETE FROM supplier_lead_costs     WHERE deal_id IN (SELECT id FROM deals WHERE tenant_id IN (<IDS>));
  DELETE FROM in_app_notifications    WHERE tenant_id IN (<IDS>);
  -- reminders УДАЛЕНА из строки (таблица дропнута в G3 — была N-1: ABORT транзакции).
  DELETE FROM report_jobs             WHERE tenant_id IN (<IDS>);
  DELETE FROM pd_processing_log       WHERE tenant_id IN (<IDS>);   -- N-2 (ПДн 152-ФЗ)
  DELETE FROM tenant_operations_log   WHERE tenant_id IN (<IDS>);   -- N-2 (операции)
  DELETE FROM activity_log            WHERE tenant_id IN (<IDS>);
  DELETE FROM deals                   WHERE tenant_id IN (<IDS>);
  DELETE FROM project_supplier_links  WHERE project_id IN (SELECT id FROM projects WHERE tenant_id IN (<IDS>));
  DELETE FROM projects                WHERE tenant_id IN (<IDS>);
  DELETE FROM users                   WHERE tenant_id IN (<IDS>);
  DELETE FROM tenants                 WHERE id IN (<IDS>);
  -- N-2: orphan-таблицы инъекции (БЕЗ tenant_id) — детерминированно по TEST-диапазону vid
  -- (маркер из R2 Предусловий: все инъекции с vid 9000000000000..9999999999999).
  -- Порядок: лог резолва (по supplier_lead_id) ДО supplier_leads.
  DELETE FROM lead_region_resolution_log WHERE supplier_lead_id IN (SELECT id FROM supplier_leads WHERE vid BETWEEN 9000000000000 AND 9999999999999);
  DELETE FROM failed_webhook_jobs        WHERE raw_payload->>'vid' ~ '^9[0-9]{12}$';
  DELETE FROM supplier_leads             WHERE vid BETWEEN 9000000000000 AND 9999999999999;
  COMMIT;"'
# webhook_log — ОТДЕЛЬНОЙ командой ПОСЛЕ транзакции (таблица опциональна: Schema::hasTable;
# если её нет — DELETE в общей транзакции уронил бы ВЕСЬ teardown, как прежний N-1).
# accepted-логи тест-лидов: их lead_id теперь «висит» (supplier_leads тест уже удалены выше) →
# чистим supplier-строки с несуществующим lead_id. reject/rate-limit-строки (lead_id=NULL, без ПДн,
# только IP+статус) — низкий вред; при желании добей по тест-IP инъекции: AND ip_address='<TEST_IP>'.
ssh liderra-prod 'sudo -u postgres psql -d liderra -c "DELETE FROM webhook_log WHERE source='\''supplier'\'' AND lead_id IS NOT NULL AND lead_id NOT IN (SELECT id FROM supplier_leads)"'
```

> **⚠️ N-1 (КРИТИЧНО):** прежний скрипт падал на двух строках — `supplier_lead_costs` фильтровался по несуществующему `tenant_id`, и `DELETE FROM reminders` бил по дропнутой таблице → любая ошибка откатывала ВСЮ транзакцию → тест-данные **оставались на боевом**. Исправлено выше: `supplier_lead_costs` через `deal_id`-подзапрос (ДО `deals`), строка `reminders` убрана.
>
> **✅ N-2 (orphan-таблицы инъекции) — закрыто детерминированно (без ручного манифеста):** 4 таблицы инъекция пишет БЕЗ `tenant_id` (или с `NULL`) — фильтром `tenant_id IN (<IDS>)` их не достать. Решение: **все инъекции R2 идут с `vid` из TEST-диапазона `9000000000000..9999999999999`** (маркер в R2 Предусловиях). По нему скрипт выше чистит детерминированно: `supplier_leads` (`vid BETWEEN`), `lead_region_resolution_log` (через `supplier_lead_id`, маскир. ПДн), `failed_webhook_jobs` (`raw_payload->>'vid' ~ '^9[0-9]{12}$'`) — в транзакции; `webhook_log` (опциональна) — отдельной командой по «висящему» `lead_id`. Ловит и B-канал, и DIRECT, не зависит от обнуления FK на TD1. **Маркер vid в R2 — обязателен**, иначе orphan-данные останутся.
>
> **⚠️ N-3 (НЕ удалять глобальные audit-chain!):** `auth_log` и `saas_admin_audit_log` — **глобальная** hash-цепочка (`partition: ''`), не per-tenant; логины тест-клиентов вперемешку с боевыми. **Удаление их строк разорвёт глобальную цепочку → `audit:verify-chains` (F3) покраснеет.** Их в TD3 НЕТ — и быть не должно. Per-tenant цепочки (`activity_log`/`pd_processing_log`/`balance_transactions`/`tenant_operations_log`) при удалении целого тест-тенанта рвутся безопасно (своя цепочка тенанта исчезает целиком, чужие целы) — F3 для tenant 2 остаётся зелёным.
⚠️ **`<IDS>` — только id из шага 1.** Никогда не подставляй tenant 2. `session_replication_role` доступен только `postgres` (superuser). Точный список таблиц/порядок — сверь с актуальной схемой (могли добавиться tenant-таблицы); см. пару к R1.
**Ожидаем:** тест-тенанты и все их данные удалены; tenant 2/сидовые целы.
**Если не так:** FK-ошибка → не хватает дочерней таблицы в списке, добавь выше родителя. Никогда не форсируй удаление tenant 2.
**Покрывает:** полный teardown тест-данных.

### TD4 — Тест-ключи Redis
**Действие:** убери тест-ключи (throttle входа `supplier-webhook:*` по тест-IP, dedup и т.п.), если ставил вручную. Обычные — истекут сами.
**Покрывает:** кэш.

### TD5 — Проверка чистоты
**Действие:**
```
ssh liderra-prod 'sudo -u postgres psql -d liderra -tA -c "SELECT count(*) FROM tenants WHERE name LIKE '\''TEST-%'\''"'   # → 0
ssh liderra-prod 'sudo -u postgres psql -d liderra -tA -c "SELECT count(*) FROM supplier_projects WHERE unique_key LIKE '\''test-%'\''"'  # → 0
```
**Ожидаем:** 0 тест-тенантов, 0 тест-supplier_projects; у поставщика тест-rt-проектов нет; tenant 2 (lkomega) на месте (`SELECT id,name FROM tenants WHERE id=2`).
**Если не так:** остатки → дочисти. Tenant 2 пропал → НЕМЕДЛЕННО доложи (это катастрофа).
**Покрывает:** подтверждение чистоты + сохранность реального бизнеса.

---

## Формат итогового отчёта (REPORT)

Сложи в `docs/superpowers/runbooks/2026-06-18-acceptance-RESULT-<дата>.md`:

```
# Результат приёмочного теста liderra.ru — <дата>
## Вердикт: GO / NO-GO  (GO = 0 расхождений деньги+изоляция, ядро проходит сквозь)

## Сводка по пунктам (1–10 + слой D + онбординг)
| Пункт | Шаги | PASS/FAIL/SKIP | Заметка |

## Деньги: F1 сошлось? копейки? (да/нет + числа)
## Изоляция: F2 утечек? (нет/да)
## Аудит: F3 verify-chains (зелёно/нет)
## Слой D: один воркер держит X/сутки; 10k=Y%; узкое место; рекомендация по железу
## Маржа (F4): выручка/себестоимость/маржа на лид

## Находки (UX и прочее, НЕ блокеры): список
## Критдефекты (если были): список со «ожидали/получили»
## Teardown: выполнен (да/нет), чистота TD5 (да/нет), tenant 2 цел (да)
```

## Матрица покрытия R5
| Задача | Карточка |
|---|---|
| денежный инвариант по прогону | F1 |
| изоляция финал | F2 |
| аудит-цепочки | F3 |
| маржа | F4 |
| чистка у поставщика (до 21:00) | TD1 |
| слепки | TD2 |
| тест-тенанты (trigger-bypass) | TD3 |
| redis | TD4 |
| чистота + сохранность tenant 2 | TD5 |
| отчёт/вердикт | REPORT |

**Чек-лист «не пропущено»:** ✅ деньги сошлись · ✅ изоляция чиста · ✅ аудит зелёный · ✅ rt-проекты убраны до 21:00 · ✅ тест-тенанты удалены · ✅ TD5 чисто + tenant 2 цел · ✅ отчёт с вердиктом. **Конец набора (стабильная часть). R1 + R3b — после деплоя G1/G2/G3.**
