fix(backups): give the off-host verdicts slack for a Berlin night that is 25 hours long

The threshold was the schedule period with no budget on top of it. Two things
spend that budget on a host where nothing is wrong. The cron runs 02:30
Europe/Berlin, so on the DST fall-back Sunday the gap between two runs is 25
hours and every account on every host would cross one period on the same
morning. And the ledger instant is when that account's object landed, by
design, so on a cohort walked one account at a time an account reached an hour
later tonight than last night drifts past 24 with two clean runs behind it.

The schedule period and the verdict threshold are now separate numbers. The
period is still what the card quotes; the threshold is the period plus six
hours, applied to both steps, so due still means one run produced nothing and
stale still means two. The fall-back night is pinned in the unit file by its
real dates rather than by an arithmetic 25, and a genuinely missed night at 31
hours still reads due. The runbook says why the slack is there.
This commit is contained in:
Marc-André Bombeck committed 2026-09-10 10:09:59 +02:00
1 parent e64435f6a5
commit cfc19a211c
5 files changed
+97 -17

No files matched your search

@@ -14,6 +14,7 @@ import type { Readable } from "node:stream";
import {
classifyOffhostBackup,
OFFHOST_BACKUP_GRACE_HOURS,
OFFHOST_BACKUP_PERIOD_HOURS,
} from "@/lib/jobs/offhost-backup-freshness";
import {
@@ -129,7 +130,9 @@ describe("off-host backup ledger (real Postgres)", () => {
// because the run that skipped this account still reported a success for
// everybody else.
const aged = new Date(
Date.now() - (OFFHOST_BACKUP_PERIOD_HOURS * 2 + 1) * HOUR,
Date.now() -
(OFFHOST_BACKUP_PERIOD_HOURS * 2 + OFFHOST_BACKUP_GRACE_HOURS + 1) *
HOUR,
);
await prisma.offhostBackupState.update({
where: { userId: "account-one" },