# Conflicts: # CHANGELOG.md # README.md # src/main/java/com/kingsrook/qbits/alerts/engine/AlertRouter.java # src/main/java/com/kingsrook/qbits/alerts/metadata/AlertsProcessesMetaDataProducer.java # src/main/java/com/kingsrook/qbits/alerts/processes/AlertsScheduledSteps.java # src/main/resources/db/liquibase/qbit-alerts/alerts.changelog.sql # src/test/java/com/kingsrook/qbits/alerts/AlertsQBitProducerTest.java
29 lines
5.8 KiB
Markdown
29 lines
5.8 KiB
Markdown
# QBit Alerts: design
|
||
|
||
The design for this qbit is tracked in [QRun-IO/qqq#1090](https://github.com/QRun-IO/qqq/pull/1090). This repository implements it. Phases P1 to P3 are built (see README.md); Quiet hours and subscription digests from P4 are also built; the remaining work is listed in README.md.
|
||
|
||
Deviations from the design, and why:
|
||
- **Timed notifications (#27, part 2):** reminders and escalation are implemented ahead of the remaining P4–P6 features. The minute job uses `stepOrder` and typed `userId` / `teamId` / `channelId` fields instead of the design's generic `targetId`. Policy description and enabled fields make the standard admin records usable. Types override groups; only OPEN, unacknowledged alerts run, even if a step is configured for another status, because acknowledgement must stop escalation.
|
||
- **Escalation progress:** existing ESCALATED events hold level transitions and final-step repeat attempts; repeats keep the same level. Reopen events define each cycle's start and repeat budget. One step runs per minute, including when several steps became overdue. Null/zero repeat settings disable repeats. Suppression consumes an escalation attempt, as the normal router records why no delivery was queued.
|
||
- **Timed notification transactions:** `AlertsDb` passes the current QQQ transaction to table actions. Each alert's timed fan-out, events and progress commit together. A read-only `timedNotificationCheckedAt` field prevents repeated evaluation in the same minute, including attempts with no eligible recipients. Alert and team scans use bounded pages. Runtime ticks are serialized locally; hosts must run the job on one scheduler node, matching the existing single-node scheduling guidance.
|
||
- **Reminders and EXPIRED:** null reminder interval means 60 minutes for CRITICAL and off otherwise; explicit zero is off. Reminders use subscription event filters and NOTIFIED timeline entries, without introducing a REMINDER event. Record followers do not receive scheduled reminders without a matching subscription. EXPIRED remains reserved but is excluded from the resolution possible values; the design defines no expiration behavior. No ESCALATION feature switch is present in the current config.
|
||
- **UI API and record following:** the RC.6 alerts extension, collaboration ownership of follow, and machine webhook migration are specified in [UI-API.md](UI-API.md). Existing RECORD watch subscriptions remain readable without migration.
|
||
- **Alerts and notifications use core's RC.6 producer API**, including sink registration in core instance metadata (QRun-IO/qqq#1108 and #1125). `AlertsSinkAdapter` delegates `raise`, `resolve`, `heartbeat` and `notify` to the runtime queue. No local SPI types remain; storage-specific audience and severity conversion belongs to `NotificationService`.
|
||
- **Visibility checks 1 to 3 (section 7.2)** are enforced by record security locks on session keys (`alerts_permission`, `alerts_readable_table`), not by a pre-query customizer. QQQ has no pre-query customizer, and locks also apply to counts, exports and the API.
|
||
- **Heartbeats** are stored in an extra table, `alerts_heartbeat`.
|
||
- **Claims** are leases, not atomic conditional updates (see README.md, Known limits).
|
||
- **Email threading headers** wait for core messaging support.
|
||
- **The delivery log is `alerts_delivery`** (the design calls it `alerts_notification`), and **`alerts_inbox_item` is retired**: user notifications took those roles (NOTIFICATIONS.md section 2).
|
||
|
||
User notifications (announcements, job failures, shares, security notices, releases and marketing), the recipient model and the Next UI notification center are designed in [NOTIFICATIONS.md](NOTIFICATIONS.md), with the owner decisions of 2026-10-13.
|
||
|
||
## Quiet hours and digest delivery (#27, part 1)
|
||
|
||
- Quiet days identify the start day of the window. Missing/invalid time zones use UTC; equal endpoints disable quiet hours. Pause and quiet hours apply only to personal push channels (email, email lists and other non-IN_APP channels), never to the in-app bell.
|
||
- Existing delivery statuses are sufficient: pending `DIGEST` items are excluded from the ordinary worker. Changeset 21 adds window keys, a unique channel/window claim and the claim's last included item id to the standard `alerts_delivery` table, mirrored in its entity and H2 DDL.
|
||
- Digest keys extend the documented user/window prefix with cadence and channel. This avoids collisions between hourly and daily subscriptions with the same start, different channels, and general notification digests. Channel-only digest keys use the separate `alerts:channel-digest` prefix and UTC windows; email adds a recipient hash for list channels. The unique claim is atomic even across schedulers; immediate-delivery leases retain their existing behavior.
|
||
- Delivery has at-most-once dispatch semantics. A crash between claiming and recording a provider result cannot be resolved safely for every supported channel. Such claims fail after ten minutes and are not automatically resent. Transport errors are FAILED; late items after the frozen cutoff are SUPPRESSED/DUPLICATE. The shared email API also receives the durable key.
|
||
- Digests summarize alerts represented by events in their window, rather than resending the complete outstanding-alert inventory on every run. Rendering uses MINIMAL detail, checks recipient visibility again (failing closed for unverifiable protected content), and rechecks pause/quiet hours for push channels only. In-app summaries are snapshots of the content visible at dispatch.
|
||
- To bound memory, each message accepts 500 source events; overflow fails the window with an actionable error instead of silently dropping items or sending several messages. Each run scans at most 100 due candidates; result updates use pages of 200. This is an explicit v1 limit.
|
||
- There is no feature-switch collection or DIGESTS switch in the current config. Reminders, escalation and team member sources are unchanged.
|