The alerts jobs run every 15 to 60 seconds, so their statement count is a
host's constant database load. StatementBudgetTest counts the statements of
each job and polled request and fails when they grow with the rows held.
Measured with the same scenarios before (0.1.0-RC.18) and after:
- alertsEscalateAndRemind, 50 open alerts, nothing due: 452 -> 1. It reads
only alerts a reminder or an escalation policy can apply to, decides from
the row and the reference tables, and takes the per-alert transaction only
when something may be due. An alert with nothing due is not written, so
timedNotificationCheckedAt is the last attempt, not the last tick.
- alertsEvaluateMonitors: 20 monitors not due 5 -> 2 (due monitors selected
in SQL, upkeep in one read); 20 due clean monitors 185 -> 84; a per-record
monitor refreshing 10 alerts 205 -> 120.
- alertsDeliverNotifications: idle 9 -> 7; 60 pending deliveries in 2 groups
317 -> 22; the purge runs hourly instead of every tick.
- Queued producer signals are recorded in batches: 200 resolves for alerts
that are not open plus 50 heartbeats 369 -> about 33. A repeated raise of
an open alert 17 -> 10.
AlertsReferenceCache holds alert types, groups, escalation policies, steps
and enabled mutes in a core Memoization registered with core ClusterCaches.
A record-change listener clears a table on every node (ClusterBus) when it
is written through QQQ; entries expire after 60 seconds; a table over 2,000
rows is not held. A type's openCount and lastRaisedAt are engine counters:
they do not clear the cache and are no longer record-audited.
Changeset 55 adds ix_alerts_alert_flapping for the upkeep read.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Regenerated against UI API 1.6.0: every operation has an operationId and
the standard error responses (BasicErrorResponseV1: error, code, message,
fieldErrors, traceId); the extension's own error schema is kept but
deprecated; hand-written error bodies go through QJavalinUtils so they carry
code/message/traceId. Extension MINOR bumped.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Keeps develop's per-user Slack delivery, escalation and mute work; the
in_app_min_severity changeset moves to qbit-alerts:29; the Notifications
preferences section gains Slack per category; pause also skips non-security
Slack; the summary schema carries preferencesSection (alerts extension 1.1.0).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
NotificationSlackSender runs after the emailer: core SendMessageAction to the
configured provider (default "slack") with user:<id>, no qbit-slack import.
Pause and quiet hours apply (CRITICAL bypasses quiet hours), SECURITY sends
title and link only, daily digest DMs, SKIPPED without a provider or on a
user-facing refusal. Supersedes the 2026-10-13 "Slack never per user"
decision (owner, 2026-10-14; QRun-IO/qbit-slack DESIGN section 10).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Quiet hours, digest hour and pause were editable in My Alert Settings
but never applied, and DIGEST_HOURLY / DIGEST_DAILY subscriptions sent
immediately.
- The router suppresses push deliveries (email, lists, webhooks) to a
user during their quiet hours (time zone, windows across midnight,
quiet days; severities at or above quietBypassSeverity, default
CRITICAL, break through) as QUIET_HOURS, and while paused as
USER_PAUSED. The in-app bell is never suppressed by either. A channel
below its minimum severity is now logged as MIN_SEVERITY.
- Digest subscriptions queue DIGEST deliveries per user/channel and
window. The hidden hourly alertsSendDigests job sends one grouped
message per window (hourly, or daily at the user's digest hour in
their time zone) through the existing email path or one in-app
notification, rechecks visibility, skips empty digests and never
sends a window twice. Failed or oversized windows stay visible.
- Changeset 27 adds the durable digest dispatch columns.
Refs #27
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
reminderIntervalMinutes was stored but never read, escalationLevel was
always 0 and nothing raised ESCALATED or REMINDER.
- Reminders: an open, unacknowledged, unsnoozed, unmuted alert sends a
REMINDER through the normal router every reminderIntervalMinutes of
its type (CRITICAL defaults to 60, explicit 0 turns it off).
- Escalation policies (design table 14): alerts_escalation_policy and
alerts_escalation_step (step list on the policy record), linked from
alert types and groups (type wins). A hidden scheduled job notifies
each step's USER, TEAM or CHANNEL target as ESCALATED when the alert
reaches the step's age, records an ESCALATED event and the level,
then repeats the last step every repeatAfterMinutes up to maxRepeats.
Acknowledge, snooze, resolve and mute stop both; a reopen resets.
Idempotent within a minute.
- EXPIRED stays reserved and is no longer offered as a resolution.
Changesets 23-26 (21-22 are taken by the team-sources PR).
Refs #27
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An alert about a record now also reaches the record's followers, from
core RecordFollowers.get(instance, ref, FollowEvent.ALERT) (QRun-IO/qqq#1164,
QQQ 4.1.0-RC.6). Followers are deduplicated with existing recipients by
user and channel. They must pass the source-record permission and lock
checks, and they get the same preferences and suppression as any other
recipient. With no provider registered, routing is unchanged. There is no
dependency on qbit-collaboration, and the #1164 stand-in marker is gone.
Also adopt qbit-build-parent 2.1.0 with the guardrails enforced and strict
raw-route mode. Comments that only used the word "stand-in" are reworded.
Refs #16 (architecture review QRun-IO/qqq#1150, finding F12).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Core 4.1.0-RC.6 ships user notifications (QAlerts.notify,
NotificationSignal and friends; QRun-IO/qqq#1125). The qbit's sink now
implements core's notify path, so a QAlerts.notify from any qbit reaches
the notification queue, inbox, channels, quiet hours and preferences.
The rest of the stand-in package com.kingsrook.qbits.alerts.spi goes
(NotificationSignal, NotificationAudience, NotificationCategory,
NotificationSeverity, AlertLink, package-info), and so does
AlertsRuntime.sink().
Breaking for anything that used com.kingsrook.qbits.alerts.spi; its only
consumer, qbit-easypost-tracking, moved to core in #16 there.
Closes#15 (architecture review QRun-IO/qqq#1150, F3 and F12).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#13: the six inbox and record-panel routes move from the raw
AlertsRouteProvider onto a MiddlewareSpecExtension "alerts" (prefix
/alerts). Paths are unchanged. The committed fragment
openapi/qqq-v1/alerts.yaml is checked by a contract test, and the server
tests run in strict mode. Additive fields let Next stop hard-coding
alerts names: the summary has inboxHref, and inbox items and panel
alerts have actions with process names and hrefs.
#14: the Alertmanager webhook moves to POST /qqq/hooks/alerts/alertmanager.
The old /qqq/v1 path is an opt-in deprecated alias (legacyAlertmanagerPath,
default off; it needs non-strict mode). The alias sends Deprecation and
Sunset headers and logs a warning per call.
#16: POST /alerts/record-panel/watch is removed; collaboration owns
following a record. The record panel reports follow.available when the
collaboration extension is registered. Existing record-scoped watch
subscriptions stay readable and are deprecated. Follower fan-out waits for
core RecordFollowersProviderInterface (STAND-IN(core=QRun-IO/qqq#1164,
removeBy=4.1.0-RC.7)).
The checkstyle header template now matches the short Apache header that
the sources already use. The core baseline becomes 4.1.0-RC.6.
Closes#13, closes#14, closes#16 (architecture review QRun-IO/qqq#1150,
F2, F7, F12, F20).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Registers a core Preferences section 'notifications' backed by
alerts_user_preference, alerts_notification_preference and the user's own
alerts_subscription rows: pause, digest time and time zone, alert email and
in-app switches, per-category in-app/email settings (security can't be
muted) and per-subscription overrides. Adds in_app_min_severity
(changeset qbit-alerts:21), honours it for in-app delivery, and skips
non-security emails while paused. The inbox summary returns
preferencesSection.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Bump the core baseline to 4.1.0-RC.5 and delete the alert-side copies in
the spi package. The qbit now registers its sink in core's
AlertsInstanceMetaData, so core QAlerts.raise/resolve/heartbeat from any
host qbit reaches it. The alertsQBitStandIn name is gone.
The notification types (NotificationSignal and friends) and
AlertsRuntime.sink().notify stay, binary compatible, for the released
qbit-easypost-tracking. They are tagged
STAND-IN(core=QRun-IO/qqq#1125, removeBy=4.1.0-RC.6).
StandardWebhookSigner delegates to core WebhookSigner. The wire format is
unchanged. Legacy secrets shorter than core's 16-byte minimum are
zero-padded, which gives an identical HMAC-SHA256.
Refs #15 (architecture review QRun-IO/qqq#1150, F3 and F11).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- alerts_notification (user notifications), alerts_notification_recipient
(one row per user, read/dismissed) and alerts_notification_preference
(per-category mute and email settings), with entities, changelog and
test DDL.
- The delivery log becomes alerts_delivery and alerts_inbox_item is
retired (guarded changesets 16-17); in-app alert delivery writes ALERT
notifications and recipient rows.
- Audiences resolved at send time (all users, roles, permission, users)
through a pluggable user directory; record actions Preview audience,
Send now, Schedule and Retract; scheduled sends and retention purge in
the delivery job.
- Bell API: summary with banners, a paged page endpoint with filters,
read, unread and dismiss; alerts_recipient security key and
alerts.notification.admin.
- NotificationSignal / NotificationAudience in the SPI stand-in, and the
sink's notify.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Extends the alerts model with admin-authored and producer-emitted user
notifications, per-user recipient rows, dynamic audiences, the Next UI
notification center, and QAlerts.notify in the core SPI.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Implements phases P1 to P3 of the Alerts design (QRun-IO/qqq#1090):
- 15 alerts_* tables with the Liquibase changelog, record security
(alerts_user / alerts_permission / alerts_readable_table keys) and a
write guard so alerts change only through the engine and record actions
- the state machine: dedup, refresh, severity changes, snooze, resolve,
reopen window, flapping, quiet-period auto-resolve, maintenance windows
- monitors: TABLE_QUERY, AGGREGATE, HEARTBEAT, PROCESS_FAILED, CODE, with
the full-state diff, forMinutes, timeouts, CHECK_FAILED meta-alert and
seeded monitors
- routing with scopes, teams, the bootstrap rule, recipient visibility and
logged suppression reasons; in-app, email and email-list channels; a
channel SPI; retries to DEAD and channel health; redaction
- record actions as streamed ETL processes; hidden scheduled processes
- the Alerts app home, AlertSummaryLoader, and /qqq/v1/alerts routes for
the Next UI bell and record panel
The core producer SPI (QRun-IO/qqq#1108) is not released yet, so its types
are a stand-in in the spi package, marked for replacement.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Maven scaffold (qbit-build-parent 2.0.0, com.kingsrook.qbits, <revision>
versioning, qqq 4.1.0-RC.4 BOM, JaCoCo gate), QBit config and producer,
CircleCI qqq-orb pipeline, and the standard repository files.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>