527f628a21
Закрывает дыры #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).
45 lines
1.2 KiB
PHP
45 lines
1.2 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Mail;
|
|
|
|
use Illuminate\Mail\Mailable;
|
|
use Illuminate\Mail\Mailables\Content;
|
|
use Illuminate\Mail\Mailables\Envelope;
|
|
|
|
/**
|
|
* Уведомление об автоматически обнаруженном инциденте.
|
|
*
|
|
* Отправляется только для severity=high командой incidents:watch-failures.
|
|
* Subject: [Лидерра HIGH] Incident: {summary first 100}.
|
|
*/
|
|
final class IncidentDetectedMail extends Mailable
|
|
{
|
|
public function __construct(
|
|
public readonly string $summary,
|
|
public readonly string $severity,
|
|
) {}
|
|
|
|
public function envelope(): Envelope
|
|
{
|
|
$subjectSnippet = mb_substr($this->summary, 0, 100);
|
|
|
|
return new Envelope(
|
|
subject: "[Лидерра HIGH] Incident: {$subjectSnippet}",
|
|
);
|
|
}
|
|
|
|
public function content(): Content
|
|
{
|
|
return new Content(
|
|
text: 'emails.incident_detected_text',
|
|
with: [
|
|
'summary' => $this->summary,
|
|
'severity' => $this->severity,
|
|
'now' => now()->timezone('Europe/Moscow')->toIso8601String(),
|
|
],
|
|
);
|
|
}
|
|
}
|