Skip to content

feat(notifications): change-driven in-app feed — Slice 1 (durable bell)#679

Merged
remyluslosius merged 3 commits into
mainfrom
feat/notifications-slice1
Jun 25, 2026
Merged

feat(notifications): change-driven in-app feed — Slice 1 (durable bell)#679
remyluslosius merged 3 commits into
mainfrom
feat/notifications-slice1

Conversation

@remyluslosius

Copy link
Copy Markdown
Contributor

Slice 1 of the change-driven notifications redesign (design: docs/engineering/notifications_design.md, #677). Repoints the bell from the session-scoped report.ready counter to a durable, per-user feed of meaningful changes, fed by the existing alert engine.

Backend

  • Migration 0048 notifications table: one row per (recipient, change), group_key dedup, per-user read_at.
  • internal/notifyfeed: Store (Record upsert that collapses a recurring change onto one row and re-surfaces it unread, List, UnreadCount, MarkRead scoped to the owning user, MarkAllRead) + an alertrouter in-app Channel that fans every classified alert (host_unreachable/host_recovered/drift_*) into one row per active recipient, registered alongside the stdout/Slack/email channels — so existing alerts light up the bell for free.
  • API: GET /api/v1/notifications/feed (items + unread_count), POST …/{id}:read, POST …:read-all — self-scoped to the caller.

Frontend

  • useNotifications (feed query + mark-read/mark-all mutations). The TopBar bell badges from unread_count and opens a drawer of recent notifications (severity dot, click → mark read + deep-link). Deletes the session-counter store; useLiveEvents invalidates the feed on report.ready.

SDD / tests

  • Specs: system-notifications v1.3.0 (C-05/06 + AC-09/10/11), frontend-notifications v2.0.0, frontend-live-events v1.4.0 (AC-10 repointed).
  • notifyfeed store + channel tests (dedup/re-surface, per-user read scoping, fan-out + mapping); frontend bell/feed + live-events tests; 6 account-menu tests wrapped in a QueryClient.
  • Backend builds + notifyfeed green; frontend 337 tests green; tsc + eslint + gofmt clean; specter 113 specs, 100%.

Next (not in this PR)

Slice 2 = the transaction-log regression projector (critical pass→fail → grouped per host/scan), then governance/security producers. Per-host RBAC scoping of recipients (Slice 1 fans to all active users) is a tracked refinement.

Repoints the bell from the session-scoped report.ready counter to a durable,
per-user feed of meaningful CHANGES, wired to the existing alert engine.

Backend:
- migration 0048: notifications table (per-recipient rows, group_key dedup,
  per-user read_at).
- internal/notifyfeed: Store (Record upsert/collapse + re-surface unread, List,
  UnreadCount, MarkRead scoped-to-user, MarkAllRead) + an alertrouter in-app
  Channel that fans every classified alert (host_unreachable/recovered,
  drift_*) into one row per active recipient, registered alongside Slack/email.
- API: GET /api/v1/notifications/feed (+unread_count), POST :read / :read-all,
  self-scoped to the caller. Handlers + openapi + regen.

Frontend:
- useNotifications (feed query + mark-read/mark-all mutations); TopBar bell
  renders the badge from unread_count and opens a drawer of recent
  notifications (severity dot, click marks read + deep-links). Deletes the
  session-counter store; useLiveEvents invalidates the feed on report.ready.

Specs: system-notifications v1.3.0 (C-05/06 + AC-09/10/11), frontend-notifications
v2.0.0 (durable feed), frontend-live-events v1.4.0 (AC-10 repointed). Backend +
frontend suites green; specter 113 specs, 100%. Design: notifications_design.md.

Slice 1 of AUTH/notifications redesign; rule-regression projector (transaction
log) is Slice 2.
Security/quality-review follow-up: Channel.Send previously looped Store.Record
once per active user (N DB round-trips per alert — a latency/DoS risk on large
user bases and during alert storms). Replace with a single
RecordFanout (INSERT ... SELECT id FROM users WHERE deleted_at IS NULL ...
ON CONFLICT) so an alert is one round-trip. NewChannel drops the now-unused
pool. Adds a cross-user isolation assertion (Bob's List/UnreadCount exclude
Alice's rows) alongside the existing MarkRead scoping test.
@remyluslosius remyluslosius merged commit 50e3898 into main Jun 25, 2026
13 checks passed
@remyluslosius remyluslosius deleted the feat/notifications-slice1 branch June 25, 2026 14:14
remyluslosius added a commit that referenced this pull request Jun 25, 2026
* docs: session meta — CHANGELOG, SESSION_LOG (2026-06-25), STATUS.md

Document the 2026-06-25 session's in-flight work (PKG-3 #673, AUTH-1 #675/#678,
notifications Slice 1 #679, avg-compliance #676): CHANGELOG [Unreleased]
entries, a SESSION_LOG handoff entry, and a new STATUS.md one-page snapshot.
BACKLOG findings from the security review land in a follow-up commit.

* docs(guides): truthfulness fixes from the 2026-06-25 audit + BACKLOG DOC-3

High-impact, verified guide defects fixed:
- UPGRADE/QUICKSTART/ENVIRONMENT/MONITORING: --config is a GLOBAL flag (Go flag
  parsing stops at the first non-flag arg), so 'openwatch migrate --config X'
  silently ignored --config. Moved --config before the subcommand everywhere.
- COMPLIANCE_CONTROLS: removed the invented 'analyst' role + 'three-tier role
  model' (real: 5 roles — viewer/auditor/ops_lead/security_admin/admin) and the
  fabricated '100/min per user, 1000/min per IP' rate-limit (real: per-IP
  sliding window on the auth endpoints).
- API_GUIDE: the 'not yet in the API' section was almost entirely false (scans,
  remediation, exceptions, posture/drift, audit export, rule browser all ship);
  rewrote it to list the live surface + only the genuinely-absent /metrics and
  /security-info. Added the missing ops_lead role to the role table.
- Version sweep rc.13 -> rc.14; bumped Last Updated to 2026-06-25 on edited guides.

BACKLOG DOC-3 captures the remaining audit items (SCANNING dead-endpoint
appendix, USER_ROLES matrix, INSTALLATION PG-dep, DATABASE_MIGRATIONS fake
output, style sweep) and flags the audit's '538->539' suggestion as a FALSE
POSITIVE — rc.14 bundles Kensa v0.6.0 = 538 (the guides correctly say 538).

* docs(backlog): note the gating TestApply_1000Rules_Under2Seconds perf flake

It hard-asserts 2s and gated #676's CI under -race (passed on rerun); it missed
the 2026-06-21 perftest.Budgetf() migration the other perf tests got.
remyluslosius added a commit that referenced this pull request Jun 25, 2026
Bump version.env to 0.2.0-rc.15 and cut the CHANGELOG [Unreleased] accumulator
into a dated [0.2.0-rc.15] section covering everything landed since rc.14:
- PKG-3 remediation store-path fix (production-breaking) (#673)
- AUTH-1: client idle timer (#675) + absolute-timeout ceiling +
  slide-on-user-activity (#678)
- Notifications Slice 1: durable change-driven bell (#679)
- Avg-compliance parity /hosts <-> /dashboard (#676)

Local: changelog + version-consistency + fips + package-build tests pass.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant