Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Fixed

- **`delivery.cooldown_minutes` is reachable from the control plane** ([#3314]) - the per-fingerprint throttle on every Slack, Teams, PagerDuty, generic-webhook and email delivery was stored as `config_notification.email_cooldown_minutes`, reported by no MCP tool and accepted by none, so on a headless deployment with no SMTP configured the only path to the number governing channel volume was the WPF Settings window. `get_alert_settings` now reports it and `update_alert_settings` accepts it under a channel-neutral name beside `delivery.mode` / `delivery.per_event_max`, with the stored spelling kept as a write-only alias so existing configs and the Lite Settings window are unaffected. Both tool descriptions now distinguish it from the top-level `cooldown_minutes`, which gates the engine's fire decision rather than the post. Because the value lives on a second singleton config row, `update_alert_settings` now writes one statement per table inside one transaction, and the read/write round-trip invariant is held per table. The `mcp` role gains UPDATE on exactly that one column of `config_notification`; the SMTP password, the webhook URLs and the PagerDuty routing key stay unreadable and unwritable, and even the non-secret sibling `smtp_host` is not writable. The 120-minute ceiling is unchanged: the cooldown is one global number applied to every fingerprint on every server, so silencing one recurring signature belongs to `create_mute_rule`, which is scoped, expires, is listed by `get_mute_rules`, and still logs the alert.
- **High CPU now requires the condition to persist** ([#3282]) - the built-in High CPU alerts on both engines fired on a single sample over the threshold and resolved on the next sample under it, so a momentary spike was indistinguishable from sustained saturation; measured fire/resolve pairs on a 42-server fleet ran 42-147 seconds apart. Both evaluators now go through the shared `AlertPersistenceGate`: three consecutive breaching CPU samples to fire, two consecutive clearing samples to resolve, counted per collected SAMPLE rather than per alert sweep so a re-read of one reading cannot fill the streak. The per-server state is persisted on both SKUs, so a restart neither re-announces an already-open incident nor loses a partly-built streak, and a CPU reading that stops arriving now freezes the gate instead of announcing a recovery nobody measured.
- **A mute rule created or deleted outside the Viewer did not take effect, for an unbounded time, and every surface said it had** ([#3315]) - `config.config_mute_rules` carried no `config_version` bump trigger, and the alert engine consults `MuteRuleService`'s in-memory cache, which is reloaded only when that beacon changes. So `create_mute_rule` returned `created`, the row really landed, and matching alerts kept being delivered until an unrelated config write or a service restart happened to force a reload - with `get_mute_rules` listing the rule the whole time, because it reads the table and not the cache. `delete_mute_rule` had the same gap in the more dangerous direction: an operator un-mutes, believes alerting is restored, and the stale cache keeps suppressing. Storage V117 adds `trg_bump_mute_rules`, the fifth instance of V17's statement-level beacon trigger and sharing its function verbatim, so ANY write to the table makes the service reload its mute cache on the next sweep - `AFTER INSERT OR UPDATE OR DELETE`, so disabling a rule counts as well as removing it. The `mcp` role needs no new grant: the column-level `UPDATE (config_version, updated_at)` on `config_service` provisioned for the `config_alert_settings` trigger is exactly what this SECURITY INVOKER trigger uses, verified against a live store in both directions. The viewer's connect-time probe gains a `pg_trigger` sentinel for the rung rather than an `information_schema.triggers` one, which lists only triggers on relations the current role owns or holds a non-SELECT privilege on and so would report the rung absent to the read-only `viewer` seat on a store that is fully migrated.
- **Webhook and email alerts no longer re-render incidents that are still inside their own cooldown window** ([#3313]) - a batch posted when any one of its incident fingerprints was outside its #1154 cooldown window and then rendered every incident it carried, so a single new fingerprint re-delivered every co-resident fingerprint that had already gone out minutes earlier. Teams, Slack, PagerDuty, the generic webhook and email now render only the incidents actually outside their own window, with a one-line footer stating how many others are still open. The alert history row continues to record every incident, so nothing the MCP reader, the triage page, the Viewer's detail pane or the mute pre-fill reads has changed. Per-event delivery already sent one incident per message and is unchanged. PagerDuty's `dedup_key` now anchors on the first DELIVERABLE incident, which corrects a correlation defect: a batch of one in-window and one fresh incident previously triggered under the in-window incident's key and folded into its existing alert, so the genuinely new incident never surfaced there at all.
Expand Down Expand Up @@ -3660,3 +3661,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
[#3315]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/3315
[#3313]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/3313
[#3282]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/3282
[#3314]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/3314
Loading