Repository navigation
feat(alerts): on-call schedules, and escalation steps that page whoever is on call - #2306
Merged
Merged
Maple Review Bot / Maple / review
succeeded
Nov 6, 2026 in 3m 27s
Confidence 10/10 · No issues found
🟢 Confidence 10/10 · safe to merge. Resolver, paging transaction and notifier were read and are pinned by new tests; the v2 schedule schemas and some wiring modules were only partly read.
no findings · tests covered · risk high · 4/4 new units observable
Adds on-call schedules with a layered rotation resolver, escalation steps that page a member or whoever is on call, an email/chat-DM notifier, and v2 REST plus MCP surfaces. Reading the resolver, the paging transaction and delivery found no defect. The open McpQueryError and VcsWebhookEnqueueFailure issues are untouched by this diff.
onCallAt/renderOnCallresolve layered rotations, weekly windows and overrides in a schedule's time zone- Escalation steps take
userandscheduletargets, queued asalert_member_notificationsin the step's transaction AlertMemberNotifierdrains member pages to email, Slack DM and Discord DM, skipping those queued before an ack- New v2
/v2/alerts/oncall_schedulesCRUD, overrides, oncall/shifts reads, and seven MCP tools
Before merge
- Migration · Check it is safe on production data and with the code still deployed ·
packages/db/drizzle/20261106232100_oncall_schedules/migration.sql
Production impact
Open errors in the changed files (3)
| Issue | Service | Occurrences | File |
|---|---|---|---|
| @maple/mcp/errors/McpQueryError (4 issues) | maple-chat | 30 | apps/ai/src/mcp/tools/registry.ts |
| @maple/api/routes/VcsWebhookEnqueueFailure (2 issues) | maple-api | 9 | apps/api/src/runtime/http-graph.ts |
| @maple/mcp/errors/McpQueryError (4 issues) | maple-ai | 12 | apps/ai/src/mcp/tools/registry.ts |
After this merges, Maple checks whether they stop.
Production traffic of the changed files (last 7 days)
| File | Calls/day | Busiest operations |
|---|---|---|
packages/backend/src/services/alerts/AlertMemberNotifier.ts |
116 | AlertMemberNotifier.drain 116/day, 0.0% err |
packages/backend/src/services/alerts/ChatAlertPoster.ts |
24 | AlertDelivery.chat 24/day, 0.0% err |
packages/backend/src/services/alerts/EscalationChainsService.ts |
15 | EscalationChainsService.list 15/day, 0.0% err |
Telemetry this change adds and removes (25)
- ➕ span name
McpTool.createOnCallOverride·apps/ai/src/mcp/tools/create-oncall-override.ts:40 - ➕ span name
McpTool.createOnCallSchedule·apps/ai/src/mcp/tools/create-oncall-schedule.ts:37 - ➕ span name
McpTool.deleteOnCallOverride·apps/ai/src/mcp/tools/delete-oncall-override.ts:23 - ➕ span name
McpTool.deleteOnCallSchedule·apps/ai/src/mcp/tools/delete-oncall-schedule.ts:21 - ➕ span name
McpTool.listOnCallSchedules·apps/ai/src/mcp/tools/list-oncall-schedules.ts:21 - ➕ span name
McpTool.updateOnCallSchedule·apps/ai/src/mcp/tools/update-oncall-schedule.ts:38 - ➕ span name
McpTool.whoIsOnCall·apps/ai/src/mcp/tools/who-is-on-call.ts:29 - ➕ span name
AlertIncidentPagesService.listPages·packages/backend/src/services/alerts/AlertIncidentPagesService.ts:77 - ➕ attribute
maple.escalation.chat_workspaces·packages/backend/src/services/alerts/AlertMemberNotifier.ts:346 - ➕ span name
AlertMemberNotifier.deliver·packages/backend/src/services/alerts/AlertMemberNotifier.ts:354 - ➕ attribute
maple.escalation.recipients_missing·packages/backend/src/services/alerts/AlertsService.ts:6133 - ➕ attribute
maple.escalation.oncall_gap·packages/backend/src/services/alerts/AlertsService.ts:6145 - ➕ span name
AlertDelivery.chatDirect·packages/backend/src/services/alerts/ChatAlertPoster.ts:262 - ➕ span name
OnCallSchedulesService.list·packages/backend/src/services/alerts/OnCallSchedulesService.ts:308 - ➕ span name
OnCallSchedulesService.get·packages/backend/src/services/alerts/OnCallSchedulesService.ts:358 - ➕ span name
OnCallSchedulesService.create·packages/backend/src/services/alerts/OnCallSchedulesService.ts:367 - ➕ span name
OnCallSchedulesService.update·packages/backend/src/services/alerts/OnCallSchedulesService.ts:419 - ➕ span name
OnCallSchedulesService.delete·packages/backend/src/services/alerts/OnCallSchedulesService.ts:458 - ➕ span name
OnCallSchedulesService.listOverrides·packages/backend/src/services/alerts/OnCallSchedulesService.ts:505 - ➕ span name
OnCallSchedulesService.createOverride·packages/backend/src/services/alerts/OnCallSchedulesService.ts:521 - ➕ span name
OnCallSchedulesService.deleteOverride·packages/backend/src/services/alerts/OnCallSchedulesService.ts:586 - ➕ span name
OnCallSchedulesService.onCallAt·packages/backend/src/services/alerts/OnCallSchedulesService.ts:616 - ➕ span name
OnCallSchedulesService.shifts·packages/backend/src/services/alerts/OnCallSchedulesService.ts:636 - ➕ span name
OnCallSchedulesService.memberNames·packages/backend/src/services/alerts/OnCallSchedulesService.ts:660 - ➕ span name
Discord.directMessage·packages/chat-platform/src/connectors/discord/outbound.ts:521
What was checked
renderOnCallmerges adjacent equal pieces and clampsonCallAtbounds back to the real handoff (packages/alerting-core/src/oncall.ts)- Member rows are claimed by compare-and-set and settled only by the claimer, so a concurrent drain cannot double-page
- Every schedule and override query filters
orgId(OnCallSchedulesService.ts:261,285), and schedule delete answers 409 when a chain step targets it
Observability coverage: 4 of 4 changes observable
| Change | Kind | Observable | Evidence |
|---|---|---|---|
| v2 on-call schedule, override, oncall and shifts routes | inbound HTTP entrypoint | yes | Registered on the existing v2 HttpApi group; api HTTP spans are auto-instrumented via apps/api/src/runtime/http-graph.ts |
| AlertMemberNotifier drain loop and per-page delivery | background worker | yes | Effect.withSpan("AlertMemberNotifier.drain") (AlertMemberNotifier.ts:521) and Effect.fn("AlertMemberNotifier.deliver") per page with maple.escalation.* attributes (:354) |
| Slack and Discord direct message | outbound chat client | yes | AlertDelivery.chatDirect span in ChatAlertPoster.deliver; existing AlertDelivery.chat spans are live in telemetry (~24/day) |
| Page email | outbound email | yes | Nested in the AlertMemberNotifier.deliver span with maple.escalation.recipient_channel; no address recorded |
3ef14dc · Reply "won't fix" to dismiss a finding, or mention @maple-review-bot to ask about one.
Loading