18 Commits
Author SHA1 Message Date
james.maesandClaude Fable 5.2 e26116e075 perf: scheduled jobs and the signal queue issue far fewer statements
maven / build (push) Successful in 2m25s
build / maven (push) Successful in 2m25s
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>
2026-11-10 15:39:02 -06:00
james.maesandClaude Opus 5.5 90e389a55c chore: target QQQ 4.1.0-RC.15 (UI API 1.6.0)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 12:27:02 -05:00
james.maesandClaude Opus 5.5 489663a946 feat(ui-api): extension for QQQ UI API 1.6.0 (operationIds, BasicErrorResponseV1, QQQ 4.1.0-RC.14)
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>
2026-10-15 11:43:36 -05:00
james.maesandClaude Opus 5.5 411a8ff604 Merge origin/develop into feature/notification-preferences-section; pin QQQ 4.1.0-RC.7
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>
2026-10-14 16:36:00 -05:00
james.maesandClaude Opus 5.5 718e13c236 feat(notifications): deliver notifications to Slack per user preference
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>
2026-10-14 11:27:35 -05:00
james.maes ebfb71c910 Merge remote-tracking branch 'origin/develop' into feature/quiet-hours-digests
# 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
2026-10-14 11:12:39 -05:00
james.maesandClaude Opus 5.5 8c6e4dadfa feat(routing): apply quiet hours, user pause and digests
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>
2026-10-14 11:02:08 -05:00
james.maesandClaude Opus 5.5 28664d7d8b feat(engine): reminders and escalation policies
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>
2026-10-14 10:52:29 -05:00
james.maesandClaude Opus 5.5 3d56ccc403 feat(routing): fan record alerts out to followers via core RecordFollowers
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>
2026-10-14 07:43:46 -05:00
james.maesandClaude Opus 5.5 60b68cf722 refactor(notifications): deliver core QAlerts.notify; delete the spi stand-in
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>
2026-10-13 20:34:22 -05:00
james.maesandClaude Opus 5.5 2692f8b1dc feat(api): alerts UI routes as spec'd extension endpoints; webhook to /qqq/hooks; follow via collaboration
#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>
2026-10-13 20:18:36 -05:00
james.maesandClaude Opus 5.5 63bdc9313d feat(preferences): Notifications section in user Preferences
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>
2026-10-13 19:35:29 -05:00
james.maesandClaude Opus 5.5 cb3d206e0f refactor(alerts): use core QAlerts and WebhookSigner from QQQ 4.1.0-RC.5
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>
2026-10-13 19:31:16 -05:00
james.maesandClaude Opus 5.5 020d38e810 fix(api): notificationsHref is the Next UI route /app/notifications
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 10:15:52 -05:00
james.maesandClaude Opus 5.5 4a94a23314 feat(notifications): user notifications, recipients, audiences and the bell API (phase a)
- 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>
2026-10-13 10:03:47 -05:00
james.maesandClaude Opus 5.5 1a6bb6096e docs: user notifications design (owner decisions 2026-10-13)
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>
2026-10-13 09:45:34 -05:00
james.maesandClaude Opus 5.5 45c7b4fb8f feat: alerts tables, engine, monitors, in-app and email delivery, Alerts home and bell API (P1-P3)
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>
2026-10-12 16:57:39 -05:00
james.maesandClaude Opus 5.5 0e10190158 chore: bootstrap qbit-alerts with the QRun qbit repository standard
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>
2026-10-12 14:41:27 -05:00