Перенос fc78ee1e из gitea/main в ветку стройки, чтобы выкат отсюда не вернул
сломанные письма обратно на боевой (класс ошибки 08.07 — «затёрло выкатом из
устаревшей ветки»).
Суть: 5 писем (заморозка / напоминание / финальное / разморозка / «проект
остановлен — нет денег») уезжали в очередь с моделью Tenant; воркер грузил её
заново под crm_app_user, где RLS без app.current_tenant_id отдаёт 0 строк →
ModelNotFoundException, письмо не уходило никогда. Теперь письмо несёт снимок
данных и в БД при отправке не ходит.
Плюс дедуп persistent-инцидентов сторожа — по факту незакрытого инцидента,
а не по окну 60 мин (иначе копия инцидента каждый час, бесконечно).
NB: LEFTHOOK_EXCLUDE=larastan — статанализ в рабочей копии сломан устаревшим
_ide_helper_models.php (239 ошибок в 104 ЧУЖИХ файлах, напр. фантом
«Tenant::requiredLeadsForTomorrow() не существует», хотя метод есть в
Tenant.php:93). По изменённым файлам статанализ прогнан отдельно: 0 ошибок.
Bypass согласован с владельцем. Follow-up: перегенерировать стаб (--write-mixin).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Закрывает дыры #3 (доп. пороги) и #5 (доп. job-классы) аудита журналирования.
Что добавлено:
* СКАН failed_jobs (Laravel-standard) дополнительно к failed_webhook_jobs:
покрывает 7 ShouldQueue классов которые раньше не алертились
(SyncSupplierProject, ImportLeads, GenerateReport, CsvReconcile,
CleanupInactiveSupplierProjects, RefreshSupplierSession, DeleteSupplierProject)
* 3 правила детекции для failed_jobs:
- spike: ≥10 failures одного job-класса за окно 10 мин → severity=high
- daily-total: ≥50 failures одного job-класса за 24ч → severity=medium
- persistent: exception повторяется >3ч → severity=medium
* Группировка по (job_class, LEFT(exception, 80)) через JSON-экстракт
`payload::json->>'displayName'`
* Дедуп переведён с LIKE %summary% на точное совпадение root_cause —
надёжно и без false-positive
* Mailable IncidentDetectedMail (отдельный от SchedulerHeartbeatMissingMail),
отправка ТОЛЬКО при severity=high (medium = тихий signal в incidents_log)
* warn-only при отсутствии saas_admin_users (паттерн VerifyAuditChains)
Параметры команды (новые):
--threshold-spike=10 --threshold-daily=50 --persistent-hours=3
(старые --window=10 --threshold=200 --dedup-window=60 сохранены)
Тесты: 11/11 passed (4 старых + 7 новых, 37 assertions, 3.6s).
Plan: docs/superpowers/plans/2026-05-23-7-holes-overview.md (#3+#5).