# G7-B — Достройка admin-impersonation — ХЭНДОФФ (брейншторм не завершён)

**Дата:** 2026-06-19. **Статус:** в брейншторме, кода нет. Следующая сессия продолжает отсюда.

> ✅ **ОБНОВЛЕНИЕ 22.06.2026 (проверено на проде):** недоделки 1-3 (письмо-код на init, письмо клиенту на end, реальная сессия `Auth::login`) — **РЕАЛИЗОВАНЫ и НА ПРОДЕ**.
>
> 🚫 **РЕШЕНИЕ ВЛАДЕЛЬЦА 22.06.2026 — СНЯТО, отдельно не делаем:** недоделка 4 (настоящий saas-admin auth = Yandex 360 SSO), недоделка 5 (two-person approval для restricted-тенантов), а также связанные код-TODO в админке — **role-guard супер-админа** (`router/index.ts`), **fallback поставщика** (`SupplierResolver` → `system_settings.default_supplier_id`), **обезличенный `admin_user_id`** (`AdminSystemSettingsController`). Все они завязаны на настоящий вход сотрудников (Yandex SSO), который ждёт регистрации ООО (Б-1/DO-4). Пока админка закрыта паролем на сервере (nginx) — заглушки безопасны. Доделать **единым пакетом вместе с подключением SSO**, отдельными задачами не ведём.
**Ветка/HEAD на момент остановки:** `gitea/main` @ `734ed08c` (всё запушено).

---

## Контекст: где мы в go-live находках

- **G7-A (клиентская «Помощь») — DONE и запушено** (`a6db8d9c..734ed08c`): пункт меню → `/help`, email техподдержки + форма-заявка (`POST /api/support-requests` → таблица `support_requests` RLS + письмо `SupportRequestMail` best-effort) + заготовка JivoSite (config `services.jivosite.widget_id`, скрипт в welcome.blade условно). schema v8.48. Спека/план: `docs/superpowers/specs|plans/2026-06-19-g7a-client-support-*`.
- **G7-B (эта задача)** — достройка impersonation. Владелец выбрал **«доработать»** (НЕ убирать, НЕ оставлять).
- Осталось после G7-B: **G5** (пополнение баланса — ждёт ООО / Б-1).

---

## Что такое impersonation и что уже собрано

«Вход под клиентом» — SaaS-admin заходит в кабинет клиента для поддержки (ТЗ §22.7 / Ю-1). 3-шаговый flow с кодом на почту клиента (consent).

**УЖЕ СОБРАНО (работает):**
- `app/app/Http/Controllers/Api/ImpersonationController.php` — `init` (генерит 6-значный код, хеш в `impersonation_tokens`, TTL 15 мин, audit), `verify` (Hash::check, 5 попыток, ставит `used_at`), `end` (ставит `session_ended_at`), `active`/`recent` (списки). Все через `pgsql_supplier` (BYPASSRLS).
- `app/app/Models/ImpersonationToken.php` — поля включают **`session_id`** (уже есть в схеме!), `used_at`, `session_ended_at`, `failed_attempts`, `invalidated_at`, `second_approver_id`/`second_approval_at`. Методы `isUsable()`/`isExpired()`.
- `app/app/Services/Pd/ImpersonationAuditService.php` — recordInit/recordVerify/recordEnd.
- Фронт: `components/admin/ImpersonationDialog.vue` (3-step state-machine: reason→verify→active→done; шаг 'active' прямо говорит «На production здесь начнётся реальная сессия»; dev показывает `_dev_plain_code`), `ImpersonationBanner.vue`, привязка в `AdminTenantsView`/`TenantsTable`/`TenantDetailHeader`.
- Роуты: `app/routes/web.php` ~стр.97 `Route::middleware('saas-admin')->group(...)` → `/api/admin/impersonation/{init,verify,end,active,recent}`.

**ЧТО НЕ ДОСТРОЕНО (явные TODO в коде):**
1. **Письмо с кодом на `init` НЕ шлётся** — `ImpersonationController.php:137` `// TODO: отправить email на $tenant->contact_email с $plainCode`. Сейчас код уходит только в `_dev_plain_code` (dev/testing). ✅ СДЕЛАНО (подтверждено по коду app/app/Http/Controllers/Api/ImpersonationController.php:147-148, проверка 22.06.2026)
2. **Письмо-уведомление клиенту на `end` НЕ шлётся** — `ImpersonationController.php:235` `// TODO: уведомление клиенту по email о завершении`. ✅ СДЕЛАНО (подтверждено по коду app/app/Http/Controllers/Api/ImpersonationController.php:274-276, проверка 22.06.2026)
3. **Реальная сессия НЕ создаётся** — `verify` (стр.193-197) только ставит `used_at`; `// Создание saas_admin_session с impersonating_token_id — отдельный коммит после saas-admin auth`. Поле `session_id` в токене есть, но не заполняется. Админ фактически НЕ начинает браузить как клиент. ✅ СДЕЛАНО (подтверждено по коду app/app/Http/Controllers/Api/ImpersonationController.php:228-234, проверка 22.06.2026)
4. **SaaS-admin auth = заглушка.** `app/app/Http/Middleware/EnsureSaasAdmin.php` — **passthrough** (`return $next()`). На проде админку (`/admin` + `/api/admin/*`) держит **nginx HTTP Basic Auth** (`/etc/nginx/.htpasswd-admin`). Настоящий saas-admin SSO (Yandex 360) **заблокирован Б-1 (ООО) + DO-4**. `requested_by`/`admin_user_id` — стаб `super_admin` (трейт `ResolvesAdminUserId`). Это находка **F-T2** из аудита.
5. **Two-person approval** (CTO-15/Ю-9) для restricted-тенантов (`processing_restricted` / `chargeback_unrecovered_rub>0`) — не реализовано.

---

## РЕШЕНИЕ ВЛАДЕЛЬЦА (зафиксировано)

На вопрос «письма можно сейчас, реальный вход-под-клиентом упирается в ООО (Б-1) — как делаем?» владелец выбрал: **«Строить сессию сейчас (на стаб-админе)»** — то есть достроить и реальный session-takeover сейчас, не дожидаясь SSO/ООО.

**Это override моей рекомендации** (я советовал «сейчас только письма, сессию после ООО»).

### Уточнённая security-позиция (важно для спеки)
При разборе нашёлся смягчающий контроль: **6-значный код на `init` шлётся на почту САМОГО КЛИЕНТА** (`tenant.contact_email`), и админ без этого кода не пройдёт `verify`. Значит «вход под клиентом» **требует согласия клиента** (клиент передаёт код из своей почты админу). Поэтому строить сессию приемлемо **ПРИ УСЛОВИЯХ**:
- **Письмо с кодом — обязательный consent-контроль и load-bearing**: без реальной отправки кода на почту клиента остаётся только dev-байпас — это дыра. Поэтому пункт 1 (email на init) — НЕ опционален, он фундамент безопасности сессии.
- **Доступ только за nginx-паролем** (прод) — session-takeover нельзя оголять в интернет. Зафиксировать как deploy-gate.
- **Стаб-атрибуция аудита** (всё пишется на `super_admin`, нет реальной личности до SSO) — задокументировать как известное ограничение до Б-1; приемлемо пока админ фактически один (владелец).
- Сессия должна: автоистекать (≤1 час, как уже сказано в UI), завершаться по `end`, аудироваться, **не давать эскалацию** (админ действует строго в RLS-периметре тенанта, без доступа к другим тенантам / saas-admin-зоне во время impersonation).

---

## ЧТО ЕЩЁ НЕ ИССЛЕДОВАНО (следующая сессия — доисследовать ПЕРЕД спекой)

Главный нерешённый вопрос — **МЕХАНИЗМ session-takeover**: как сделать, чтобы браузер админа реально действовал как пользователь тенанта.
- Изучить auth-механику приложения: Sanctum SPA cookie-сессия (`config/sanctum.php`, `stores/auth.ts`, `/api/auth/me`, как логинится обычный пользователь, какой guard).
- Решить модель takeover: (а) выдать админу сессию/токен целевого tenant-user с impersonation-маркером; (б) middleware, который при активном impersonation-токене подменяет `auth()->user()` на tenant-user и ставит `app.current_tenant_id`; (в) баннер «вы вошли как клиент X, [Выйти]» поверх клиентского кабинета (ImpersonationBanner уже есть — проверить, к чему привязан).
- Какой tenant-user выбирается (владелец тенанта? первый admin tenant-user?) — определить.
- Безопасность: маркер impersonation в сессии, запрет смены пароля/2FA/опасных действий под impersonation? (обсудить с владельцем); гарантия RLS-изоляции; авто-expiry; чистое завершение.
- Переиспользование письма-кода: G1 шлёт коды через механизм `EmailVerification` + `resources/views/emails/email_verification_code.blade.php` (+ Mailable). Посмотреть `RegistrationService::sendCode` (G1, очередь+try/catch+Log, без ПДн) — тот же паттерн для init-кода и end-уведомления.

---

## ПЛАН СЛЕДУЮЩЕЙ СЕССИИ

1. Дочитать механику auth/сессии (Sanctum) + `ImpersonationBanner.vue` + G1 `RegistrationService::sendCode`/EmailVerification Mailable.
2. Продолжить **брейншторм** (skill superpowers:brainstorming): спроектировать (а) две почты (init-код на `tenant.contact_email` + end-уведомление), (б) безопасный session-takeover на стаб-админе с условиями выше. Предложить 2-3 подхода к takeover, согласовать с владельцем.
3. Спека → ревью владельца → writing-plans → реализация (subagent-driven, git-safety) → Pest + живой Playwright → push.
4. **Безопасность — обязательно:** рассмотреть прогон skill `threat-model` / `security-go-live` по impersonation перед мержем; пункт деплоя про nginx-auth + (если решим) ограничения действий под impersonation.

---

## Рабочие факты окружения (повтор)
- Pest: `composer --working-dir=app test -- <path>`. Pint: `composer --working-dir=app pint <files>`. stan: `composer --working-dir=app stan` (Pest-ложноположительные `TestCall::*` — в `phpstan-baseline.neon` тем же паттерном).
- Миграции: dev `php app/artisan migrate --force`; тест `DB_DATABASE=liderra_testing php app/artisan migrate:fresh --force`. **Схема — источник истины:** новые таблицы/поля в `db/schema.sql` И в guard'нутую миграцию (`CREATE TABLE/INDEX IF NOT EXISTS`, `DROP POLICY IF EXISTS` перед `CREATE POLICY`); после — `migrate:fresh` зелёный + **ZERO_DRIFT** (скан pg_tables vs schema.sql). Дрейф схемы закрыт полностью (v8.48).
- Фронт: vitest сломан (G8) → `type-check`+`eslint`+`build`+живой Playwright. Демо-клиент dev: `admin@demo.local`/`password` (tenant 1). Сервер: `php artisan serve`.
- Коммит: `LEFTHOOK=0 git commit`. Push: `git push gitea main` (github мёртв). ⚠ Стейджить ТОЛЬКО явные пути (репо имеет пред-существующие грязные файлы: CLAUDE.md, settings.json, observer, `CTemp*.txt` — НЕ трогать, НЕ `git add -A`).
- Larastan-долг G1/G6 закрыт (0), схема-дрейф закрыт (ZERO_DRIFT). Кодовая фраза стены (если вернётся) — «роутер-наставник».
