feat: module alertes (sender fail-soft + builders digest/J-1) (v0.16.0)
AlertEvent sans secret ; HttpAlertSender (POST webhook n8n, best-effort) + NullAlertSender ; buildDailyDigest / selectExpiringSoon purs. 6 tests. Rattrape l'entrée CHANGELOG 0.15.1 omise. Palier de risque : reversible. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
import type { Logger } from "pino";
|
||||
import type { AlertEvent, AlertSender } from "./types.js";
|
||||
|
||||
/**
|
||||
* Envoie les alertes à un webhook n8n (qui envoie le mail). Fail-soft : une
|
||||
* panne d'alerte ne doit jamais casser la boucle agent ni le gatekeeper — on
|
||||
* logge et on continue.
|
||||
*/
|
||||
export class HttpAlertSender implements AlertSender {
|
||||
constructor(
|
||||
private readonly webhookUrl: string,
|
||||
private readonly logger: Logger,
|
||||
private readonly timeoutMs = 10_000,
|
||||
) {}
|
||||
|
||||
async send(event: AlertEvent): Promise<void> {
|
||||
const controller = new AbortController();
|
||||
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
||||
try {
|
||||
const res = await fetch(this.webhookUrl, {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/json" },
|
||||
body: JSON.stringify({ source: "chlova", ts: Date.now(), ...event }),
|
||||
signal: controller.signal,
|
||||
});
|
||||
if (!res.ok) {
|
||||
this.logger.warn({ kind: event.kind, status: res.status }, "alerte: HTTP non-OK");
|
||||
}
|
||||
} catch (err) {
|
||||
// Jamais propagé : l'alerte est best-effort.
|
||||
this.logger.warn(
|
||||
{ kind: event.kind, err: err instanceof Error ? err.message : String(err) },
|
||||
"alerte: envoi échoué (best-effort)",
|
||||
);
|
||||
} finally {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Sender inactif : aucun webhook configuré → on logge seulement (fail-safe). */
|
||||
export class NullAlertSender implements AlertSender {
|
||||
constructor(private readonly logger: Logger) {}
|
||||
async send(event: AlertEvent): Promise<void> {
|
||||
this.logger.info({ alert: event }, "alerte (non envoyée : pas de webhook)");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user