expire_alerts_job clears on timestamp alone, without any feed cross-check #147
Labels
No labels
area:ai
area:ci-cd
area:notifications
area:observability
area:public-pages
backlog
bug
duplicate
enhancement
help wanted
invalid
question
type:decision
type:feature
type:infra
type:maintenance
type:security
v1.0.1
v1.1.0
v1.2.0
v1.3.0
v2.0.0
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
rbrooks/WeatherBot#147
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
expire_alerts_job(app/services/scheduler.py:94-119) clears alerts on the stored timestamp alone:_clear_missing_alertsrequires an alert to be both past its expiry and absent fromactive_matchesbefore clearing it. This job has no such cross-check, so the two paths disagree about what "expired" means.That asymmetry matters when the feed and the stored timestamp disagree:
fetch_alerts_for_statesreturnsNoneduring an NWS outage andprocess_alertscorrectly skips the cycle (#40) — but this job keeps running every 5 minutes and keeps clearing on stale timestamps, firing all-clears nobody can verify.EXT) has itsexpiresrefreshed by the poll path — but if the poll is failing or lagging, this job can clear it in the window before the extension is seen.The job's docstring says it exists for the case where "the poll cycle missed the cancellation window", which is reasonable — but a poll cycle that is failing is exactly when its timestamps are least trustworthy, and that is precisely when this job acts unilaterally.
Fix
Gate the job on recent poll health before clearing. The heartbeat/freshness machinery already exists (
product_freshness,heartbeat_watchdog_job,settings.heartbeat_stale_minutes): if the last successful alert poll is stale, skip the expiry sweep rather than clearing blind. Optionally require the alert to have been absent from the last successful poll's match set, to match_clear_missing_alertssemantics exactly.Related
Same family as #145 and the pagination-truncation issue: all three end in a false all-clear by a different route.
Fixed in #150 (merged). CI green.
expire_alerts_jobnow gates on poll freshness via a newobservability.latest_alert_poll_successhelper, reusing the existingheartbeat_stale_minutesthreshold rather than adding another knob. No recent successful poll — including none ever recorded, which reads as "not fresh" rather than "fine" — means skip the sweep and log why, while still delivering all-clears already decided by a healthy path.One existing test changed:
test_expire_alerts_job_sends_lifted_once_across_repeated_runsnow seeds a healthy poll record. Its subject is lifted-notification dedup, not freshness, so the precondition is declared rather than the assertion weakened.Verified locally in Docker: ruff, 805 SQLite-tier tests, migrations on Postgres 16, 4 Postgres-tier tests. Includes a control (fresh poller → still expires).
Not done here: making the job require the alert to have been absent from the last successful poll's match set, which would match
_clear_missing_alertssemantics exactly. The freshness gate covers the dangerous case (acting while blind); exact parity would need the poll to persist its match set, which is a larger change and did not seem worth it yet.