From f2c23636fe848b5347cf6c49a6acd4b13ebdeb78 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 24 Sep 2026 13:20:03 -0700 Subject: [PATCH] type only imports, add empty guard for threshold breaches do Docker monitors don't get this --- server/src/domain/notifications/notification.type.ts | 2 +- server/src/service/statusService.ts | 4 ++-- server/src/worker/worker.pipeline.ts | 2 +- server/test/unit/services/workerPipeline.test.ts | 7 +++++++ 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/server/src/domain/notifications/notification.type.ts b/server/src/domain/notifications/notification.type.ts index 0082f2098..b696bf752 100644 --- a/server/src/domain/notifications/notification.type.ts +++ b/server/src/domain/notifications/notification.type.ts @@ -1,4 +1,4 @@ -import { HardwareMetricKey } from "@/domain/monitors/monitor.type.js"; +import type { HardwareMetricKey } from "@/domain/monitors/monitor.type.js"; export const NotificationChannels = [ "email", diff --git a/server/src/service/statusService.ts b/server/src/service/statusService.ts index 0ceeb9540..48b75f1da 100755 --- a/server/src/service/statusService.ts +++ b/server/src/service/statusService.ts @@ -2,10 +2,10 @@ import { IMonitorStatsRepository } from "@/domain/monitor-stats/monitor-stats.re import { IMonitorsRepository } from "@/domain/monitors/monitor.repository.interface.js"; import type { Check, CheckDiskInfo } from "@/domain/checks/check.type.js"; import { - HardwareBreaches, - HardwareCounters, HardwareMetricKeys, MonitorStatuses, + type HardwareBreaches, + type HardwareCounters, type Monitor, type MonitorStatus, } from "@/domain/monitors/monitor.type.js"; diff --git a/server/src/worker/worker.pipeline.ts b/server/src/worker/worker.pipeline.ts index f1447fbd7..3d703d3eb 100644 --- a/server/src/worker/worker.pipeline.ts +++ b/server/src/worker/worker.pipeline.ts @@ -298,7 +298,7 @@ export class WorkerPipeline implements IWorkerPipeline { decision.incidentReason = "status_down"; decision.notificationReason = "status_change"; } else if (monitor.status === "breached") { - decision.thresholdBreaches = statusChange.thresholdBreaches; + if (statusChange.thresholdBreaches) decision.thresholdBreaches = statusChange.thresholdBreaches; decision.shouldCreateIncident = true; decision.shouldSendNotification = true; decision.incidentReason = "threshold_breach"; diff --git a/server/test/unit/services/workerPipeline.test.ts b/server/test/unit/services/workerPipeline.test.ts index cf4ae4293..bbb389573 100644 --- a/server/test/unit/services/workerPipeline.test.ts +++ b/server/test/unit/services/workerPipeline.test.ts @@ -864,6 +864,13 @@ describe("WorkerPipeline", () => { expect(decision).not.toHaveProperty("thresholdBreaches"); }); + // StatusService only sets thresholdBreaches for hardware monitors; a docker breach must not leave the key behind holding undefined. + it("leaves threshold breaches off a breached decision when the status change carries none", async () => { + const decision = await decide(makeStatusChange({ status: "breached", statusChanged: true, prevStatus: "up" })); + expect(decision).toMatchObject({ shouldCreateIncident: true, incidentReason: "threshold_breach" }); + expect(decision).not.toHaveProperty("thresholdBreaches"); + }); + // Each decision starts from a shared template. A transition must not leak its flags into the next decision // the same pipeline makes, whether that is a no-op evaluation or a degraded-egress short-circuit. it("starts every decision clean after a transition on the same pipeline", async () => {