service: schedule-based alerting (alerting windows) - #3
Merged
Merged
Conversation
Adds a third notification urgency, `scheduled`, under which a service notifies only during configured weekly windows. Outside them alerts are still created, deduped and visible -- the escalation policy simply never runs -- so an alert captured off-hours notifies when the window next opens, the same way maintenance mode and low urgency already behave. This covers the case where a rotation is staffed 24x5: keep paging through the week, go quiet at the weekend. The window predicate is evaluated in Go, reusing schedule/rule.Rule (IsActive), and materialized into services.notification_suppressed by the escalation manager each tick. Evaluating it in SQL instead would mean a second implementation of subtle semantics -- Postgres `::time` discards the DST fall-back fold, so a window spanning the repeated hour would flap -- and one unresolvable timezone would abort the whole escalation batch rather than isolating to its own service. Gated behind the `svc-alert-schedule` experimental flag. Gating lives in the resolver rather than @experimental directives: the directive cannot be applied to a single enum value, and putting it on the input fields would leave already-scheduled services uneditable once the flag is turned off. Bumps the escalation processing version 5 -> 6, so old and new engines will not both process during a rolling deploy. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AjGEDUyqZq824xB6bzYX8E
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds a third service notification urgency,
scheduled: the service notifies only during configured weekly windows (day-of-week + time-of-day + IANA timezone). Outside them the alert is still created, deduped and visible in the UI — the escalation policy simply never runs.Motivating case: our rotations are staffed 24x5. We want to keep paging through the week and go quiet at the weekend, without losing the alerts.
Behavior
An alert captured outside a window notifies when the window next opens — its escalation policy never started, so it runs as if the alert had just arrived. This is exactly how maintenance mode and low urgency already behave in this fork. Operationally it means a quiet weekend's backlog pages at once on Monday; that was a deliberate choice, and it's pinned by a smoke test.
Manual escalation is refused outside the window, so the Escalate button isn't a way around it.
Design
The window predicate is evaluated in Go, reusing
schedule/rule.Rule.IsActive, and materialized intoservices.notification_suppressedby the escalation manager each tick. The three escalation joins gainand not s.notification_suppressed.Evaluating it in SQL was considered and rejected:
::timediscards the DST fall-back fold, so a window spanning the repeated hour would flap off and back on —Clock.FirstOfDay/LastOfDaydeliberately pick the first occurrence for a start and the last for an end.to_escalateCTE and abort the entire escalation batch. In Go it fails closed for that service alone.This also matches how the engine already works —
schedulemanagercomputes rule activity in Go and materializes it.The flag is tick-consistent, not instantaneously consistent (5s cycle). That's the same contract every other materialized-state module operates under.
Rollout
Behind the
svc-alert-scheduleexperimental flag. Gating is in the resolver rather than@experimentaldirectives, because the directive can't be applied to a single enum value, and putting it on the input fields would leave already-scheduled services uneditable once the flag is turned off.Bumps the escalation processing version 5 → 6. Old and new engines will not both process during a rolling deploy — expect a brief escalation pause between the migration and the new binaries. Same handoff the two prior service migrations performed.
Rollback converts
scheduled→high, deliberately: over-paging is recoverable, silently never paging again is not.Testing
America/Chicagospring-forward and fall-back, a half-hour-offset zone.Full smoke suite green; Playwright green across three consecutive full runs.
Reviewer notes
service/store.goandservice/search.goscan services independently — both were updated.🤖 Generated with Claude Code
https://claude.ai/code/session_01AjGEDUyqZq824xB6bzYX8E