# Этап 1 (Путь А) — результаты локального полигона

**Дата:** 26.06.2026
**Где:** локальный PostgreSQL 16.13, база-полигон `liderra_sandbox` (копия dev-схемы v8.55, без реальных ПДн). Боевой не затронут, денег не потрачено.
**Цель:** доказать, что модель безопасности переезжает на управляемую базу (где нет BYPASSRLS/superuser) — швы B (политики вместо BYPASSRLS) и C (пересчёт аудита по GUC-метке).

## Что доказано ✅

Все тесты — под реальными ролями `crm_*` **без атрибута BYPASSRLS** (симуляция Managed PG; проверено `rolbypassrls=f` у всех пяти).

| Проверка | Ожидание | Факт |
|---|---|---|
| `crm_app_user` + свой tenant=1 → сделки | видит свои | **10** ✅ |
| `crm_app_user` + чужой tenant=999999 | 0 (изоляция) | **0** ✅ |
| `crm_supplier_worker` БЕЗ `srv_bypass` | не видит / падает | падал на `current_setting` (bypass реально убран) ✅ |
| `crm_supplier_worker` С `srv_bypass` | видит все | **10, без ошибки** ✅ |
| `UPDATE` аудита без метки (append-only) | запрет | **запрещено** ✅ |
| `UPDATE` аудита с `app.audit_rebuild='on'` (под supplier_worker) | разрешён | **UPDATE 1** ✅ |

**Вывод:** изоляция клиентов и пересборка аудита работают БЕЗ особых прав роли. Главный риск Пути А снят на копии.

## Валидированные артефакты (готовы к переносу в миграции)

### Шов B — разрешающие политики (44 таблицы, идемпотентно)
```sql
DO $$
DECLARE t record;
BEGIN
  FOR t IN SELECT tablename FROM pg_tables WHERE schemaname='public' AND rowsecurity LOOP
    EXECUTE format('DROP POLICY IF EXISTS srv_bypass ON public.%I;', t.tablename);
    EXECUTE format(
      'CREATE POLICY srv_bypass ON public.%I AS PERMISSIVE FOR ALL
         TO crm_admin_user, crm_migrator, crm_supplier_worker
         USING (true) WITH CHECK (true);', t.tablename);
  END LOOP;
END $$;
```
Создаёт 44 политики (== числу RLS-таблиц). `crm_app_user` НЕ включён → остаётся изолированным.

### Шов C — функция аудита по GUC-метке (исправлено: членство, не точное имя)
```sql
CREATE OR REPLACE FUNCTION public.audit_block_mutation()
RETURNS trigger LANGUAGE plpgsql AS $function$
BEGIN
  IF current_setting('app.audit_rebuild', true) = 'on'
     AND pg_has_role(current_user, 'crm_migrator', 'MEMBER') THEN
    RETURN COALESCE(NEW, OLD);
  END IF;
  RAISE EXCEPTION 'audit log is append-only (table %): UPDATE/DELETE forbidden', TG_TABLE_NAME
      USING ERRCODE = 'check_violation';
END;
$function$;
```
`AuditRebuildChain` (через `pgsql_supplier`) внутри транзакции делает `SET LOCAL app.audit_rebuild='on'` вместо `session_replication_role` (Odyssey-safe). `pg_has_role(..,'crm_migrator','MEMBER')` покрывает и `crm_supplier_worker` (член crm_migrator), и сам crm_migrator.

## Находки (для плана/нормативки)

1. **`db/02_grants.sql` ссылается на удалённые таблицы** `webhook_log` (снят v8.35) и `tenant_subscriptions` → ошибки при применении. **Подчистить** (шов E) перед провиженом управляемой базы.
2. **RLS-политики `tenant_isolation` непоследовательны:** часть зовёт `current_setting('app.current_tenant_id')` БЕЗ флага `, true` → служебная роль без контекста падала бы с ошибкой. **`srv_bypass` это перекрывает** (проверено: ошибки нет). Желательный future-fix — привести все к `current_setting(..., true)`, но для переезда НЕ блокер.
3. **Роль пересчёта аудита** на проде — `crm_supplier_worker` (не `crm_migrator`); функция должна проверять членство, а не имя (учтено выше).

## Состояние полигона / уборка

- В **локальном кластере** созданы/изменены роли `crm_*`: добавлен LOGIN + пароль `sb` + снят BYPASSRLS (они предсуществовали как NOLOGIN). Это dev-кластер, риск низкий; при желании вернуть — `ALTER ROLE crm_* NOLOGIN`.
- База `liderra_sandbox` оставлена для следующих шагов (прогон тестов приложения, шов D маскировки — но anon 1.3.2 проверяется уже на управляемом тест-кластере, локально его нет).
- Реальные ПДн на локалку НЕ заливались (152-ФЗ).

## Дальше

- Оформить швы B и C как реальные миграции репозитория (TDD по плану Task 2 / Task 4) — следующая сессия кода.
- Шов D (маскировка anon 1.3.2), шов E (чистка грантов), проверка пулера Odyssey/SSL — на коротком управляемом тест-кластере (Prerequisites P1–P4, нужен владелец).
