# Спека: PII-scrubbing процессор логов

## Цель

Закрыть Medium go-live: `config/logging.php` пишет в `laravel.log` (LOG_LEVEL=debug)
без маскирования ПДн — если телефон/email попадёт в текст исключения или контекст,
он осядет в логе открытым. Добавить Monolog-процессор, который централизованно
маскирует ПДн во ВСЕХ записях файловых каналов, не правя вызовы `Log::` по коду.

## Процессор маскирования {#D1}

`App\Logging\PiiScrubbingProcessor` (Monolog 3, `ProcessorInterface`):

- `__invoke(LogRecord $record): LogRecord` — возвращает запись с маскированными
  `message`, `context`, `extra` (через `$record->with(...)`).
- Телефоны РФ: `/(?<!\d)(?:\+?7|8)\d{10}(?!\d)/` → `[PHONE]` (11-значные 7/8/+7;
  lookbehind/lookahead не дают маскировать часть более длинной цифровой строки).
- Email: `/[\p{L}0-9._%+\-]+@[\p{L}0-9.\-]+\.\p{L}{2,}/u` → `[EMAIL]`.
- `context`/`extra` обходятся рекурсивно: строки маскируются, вложенные массивы —
  рекурсивно, не-строки (int/bool/null/object) — без изменений.

## Проводка {#D2}

`App\Logging\ScrubPii` (tap): `__invoke(\Illuminate\Log\Logger $logger)` →
`$logger->getLogger()->pushProcessor(new PiiScrubbingProcessor())`.

В `config/logging.php` добавить `'tap' => [\App\Logging\ScrubPii::class]` к каналам
`single` и `daily` (оба пишут в `laravel.log` — основной риск). Каналы `stderr`/
`papertrail` (driver `monolog`) расширяются через свой `processors` массив отдельно
по мере надобности; `null`/`emergency`/`stack` не требуют (stack делегирует в single).

## Sentry — фактическая проверка {#D3}

Проверено фактом: пакета `sentry`/`sentry-laravel` в `app/composer.json` НЕТ;
`config/sentry.php` отсутствует; Sentry в приложении не сконфигурирован. Значит
«регекс-маска в config/sentry.php» (OPEN-И-16) **не реализуема в коде** без установки
SDK — Sentry self-hosted это инфраструктура, но Laravel-интеграция отсутствует.
Это фиксируется как факт для владельца (отдельный open-item), не реализуется здесь.

## Граничные случаи {#D4}

- Длинная цифровая строка (например 14-значный id) НЕ маскируется (lookahead `(?!\d)`).
- Не-строковые значения context (int/bool/null/объекты) проходят без изменений.
- Процессор идемпотентен и без побочных эффектов (чистая трансформация записи).
- `[PHONE]`/`[EMAIL]` — фиксированные плейсхолдеры, не зависят от исходной длины.

## Критерий приёмки {#D5}

Pest-файл `tests/Feature/Logging/PiiScrubbingProcessorTest.php`:

- телефон (`79161234567`/`+79161234567`/`89161234567`) в `message` → `[PHONE]`,
  исходные цифры отсутствуют;
- email в `message` → `[EMAIL]`;
- телефон и email во вложенном `context` (массив в массиве) → маскированы;
- 14-значная цифровая строка в `message` НЕ изменена (нет ложного срабатывания);
- не-строковые значения `context` (int) сохранены как есть.

## Переговоры

### Круг 1

Открытая точка для наставника: процессор вешается на `single`/`daily` (файловые
каналы `laravel.log` — задокументированный риск), `stderr`/`papertrail` оставлены на
будущее расширение, чтобы не раздувать правку. Прошу наставника зафиксировать
forward-рекомендацию: достаточно ли покрытия файловых каналов на этом шаге, либо
расширить на все каналы сразу. Это конкретный предмет рекомендации, не пустой слот.

```verified-context-json
[
  {
    "id": "ctx-logging",
    "kind": "EXTRACTED",
    "ref": "app/config/logging.php",
    "anchor": "'path' => storage_path('logs/laravel.log'),"
  }
]
```
