mirror of
https://github.com/Joulenap/joulenap.git
synced 2026-08-11 13:21:43 +02:00
The startup check treated "a schedule slot came round and no run happened" as proof of downtime. It is equally true when the schedule was changed since, when a route was disabled and re-enabled, and when the kill-switch was off -- so setting a route to run earlier in the day produced "A scheduled route did not run because Joulenap was offline when it was due" about a process that had been running the whole time, and re-sent it on every restart until that route next ran. Reproduced on real hardware: a schedule changed at 10:21 alerted about a 02:00 slot that never existed, on a container with an uptime spanning both. Nothing recorded that the app was alive, so nothing could tell the difference. It does now: a file under the data dir is touched every minute, and its mtime is the whole payload -- no table and no migration. A slot is reported only if it fell after the app was last known to be running; unknown liveness (first boot, an unwritable data dir) reports nothing, because inventing downtime is the failure being fixed. The stamp is read before the heartbeat starts overwriting it, and touched again on clean shutdown so a graceful restart does not leave a minute looking like an outage. The heartbeat is armed outside rearm(): it records that the app is up, which is just as true with the kill-switch off, and one that stopped there would make the next restart invent downtime. The notification still shows the real last run -- only the decision to send it moved.