feat(notifications): Slice 2 — transaction-log rule-regression projector#685
Merged
remyluslosius merged 2 commits intoJun 25, 2026
Merged
Conversation
The bell's second producer: when a scan flips a passing rule to failing,
surface ONE grouped per-host in-app notification ("web-01: 3 rules regressed
(1 critical)") instead of the alert-only Slice 1 feed.
- internal/notifyfeed/projector.go — Projector.ProjectScan reads a scan's
transactions for the host and records one grouped "rule_regression"
notification per active recipient. A regression is state_changed->fail; a
first_seen->fail counts only when severity=critical AND the host has prior
scan history (a host's first scan is all-first_seen baseline, not a
regression — must not flood the bell). group_key is per host so a burst
collapses to one row and later scans re-surface it; severity is the highest
among regressed rules; title summarizes counts.
- internal/worker/scan_worker.go — RegressionProjector interface + nil-safe
Config/field; called best-effort after Apply succeeds (a feed write never
fails or retries the scan).
- wired in both scan-worker boot paths: cmd/openwatch/main.go (serve, reusing
the Slice-1 feed store) and cmd/openwatch/worker.go (dedicated worker).
- spec system-notifications v1.4.0: C-07 + AC-12/13/14 (grouping/severity,
first-scan suppression, no-op + collapse). DB-backed tests in
internal/notifyfeed/projector_test.go.
No frontend change: the new rows render in the existing Slice-1 bell drawer.
…sion guard The leading bullet used 'compliance regressions' (a domain term), which trips TestChangelog_RegressionsNameAVersion (regression-FIX entries must name the version that regressed). Reword to avoid the trigger word; this is a feature, not a code-regression fix, so no version token is warranted.
This was referenced Jun 25, 2026
remyluslosius
added a commit
that referenced
this pull request
Jun 25, 2026
Bump version.env to 0.2.0-rc.16 and roll the CHANGELOG [Unreleased] accumulator into a dated section. Bundles the notifications action-queue work since rc.15: rule-regression projector (#685), RBAC-scoped governance queue (#686), and exception-expiry lifecycle (#687). Local: changelog + version-consistency + package-build + fips tests pass.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Notifications Slice 2 (per the change-driven design §3/§9): the bell's headline use case — a rule that was passing is now failing. Slice 1 wired the durable per-user feed + the alert-engine channel (host unreachable/recovered, drift). This adds the second producer: a transaction-log regression projector.
When a scan flips passing rules to failing, the operator gets one grouped per-host notification —
web-01: 3 rules regressed (1 critical)— severity-ranked and deep-linked to the host, instead of nothing (the alert engine doesn't classify rule-level changes).What it does
internal/notifyfeed/projector.go—Projector.ProjectScan(scanID, hostID)reads the scan'stransactionsfor the host and records one groupedrule_regressionnotification per active recipient:state_changed → fail(a passing/skipped/errored rule now fails).first_seen → failonly whenseverity=criticaland the host has prior scan history. On a host's first scan every rule isfirst_seen(baseline) — counting those would flood the bell with "N rules regressed", so they're suppressed.group_key = rule_regression:<host>— a burst of N rules in one scan is one row, and a later scan's regressions collapse onto (and re-surface unread) the same row rather than piling up. Severity = highest among regressed rules; title summarizes counts.internal/worker/scan_worker.go—RegressionProjectorinterface + nil-safeConfig/field; called best-effort afterWriter.Applysucceeds. A feed-write failure logs and is swallowed — it never fails or retries the scan (the compliance state already persisted).cmd/openwatch/main.go(serve, reusing the Slice-1 feed store) andcmd/openwatch/worker.go(dedicated worker).Spec & tests
system-notificationsv1.4.0: new C-07 + AC-12/13/14 (grouping/severity-rank, first-scan suppression, no-op + collapse).specter checkgreen (114 specs);specter coverage --strictness annotation= system-notifications 14/14 ACs, 100%.internal/notifyfeed/projector_test.go(grouping, severity ranking, first-scan suppression, recovery/severity-churn no-op, two-scan collapse + re-surface).Scope notes
🤖 Generated with Claude Code