Skip to content

feat(notifications): Slice 3 — governance action queue (RBAC-scoped)#686

Merged
remyluslosius merged 1 commit into
mainfrom
feat/notifications-slice3-governance
Jun 25, 2026
Merged

feat(notifications): Slice 3 — governance action queue (RBAC-scoped)#686
remyluslosius merged 1 commit into
mainfrom
feat/notifications-slice3-governance

Conversation

@remyluslosius

Copy link
Copy Markdown
Contributor

Summary

Notifications Slice 3 (per the change-driven design §3/§7/§9): turn the bell into an action queue. Where Slice 1 (alerts) and Slice 2 (rule regressions) fan to every active user, governance items route only to the users who can act on them.

New primitives (the RBAC-scoped fan-out Slice 3 needed)

  • auth.RolesWithPermission(p) — resolves a permission to the built-in roles that grant it (pure function over BuiltInRoles).
  • notifyfeed.Store.RecordForRoles — fans one row per active user holding any of the given roles (EXISTS, so a user with two matching roles still gets one row; avoids an ON CONFLICT double-hit), with the same upsert/collapse/re-surface semantics as RecordFanout.

Producer: notifyfeed.GovernanceProjector

Event Recipients kind / severity Link
Exception requested roles granting exception:approve (auditor, security_admin, admin) exception_pending / high /settings/policies
Exception approved/rejected the requester only exception_approved / _rejected, medium /settings/policies
Remediation failed / rollback didn't restore roles granting remediation:execute (ops_lead, security_admin, admin) remediation_failed / high /hosts/{id}

Grouping: exceptions per exception id, remediation per (host, rule). A successful user-initiated rollback is not notified — it's the intended outcome, and alarming on it would violate the design's "noise is a bug" principle (§7).

Producer hooks (best-effort, nil-safe, never fail the transition/job)

  • exception.Service.WithNotifier(...) + calls in Request/Approve/Reject.
  • worker.RemediationWorker GovernanceNotifier + calls on the two terminal-failure sites (execute→failed, rollback-not-clean).
  • Wired in both boot paths: cmd/openwatch/main.go (serve) and worker.go (dedicated worker).

Both producers hold the interface in their own package, so neither imports notifyfeed (the projector satisfies them structurally).

Spec & tests

  • system-notifications v1.5.0: C-08 + AC-15/16/17. specter check green (114 specs); annotation coverage = system-notifications 17/17 ACs, 100%.
  • auth.RolesWithPermission unit test (which caught my own wrong assumption — exception:approve includes auditor, confirming the projector notifies auditors); DB-backed governance fan-out tests proving approver-scoped / requester-only / operator-scoped routing and that out-of-scope roles receive nothing.

Scope notes

  • No frontend change — new rows render in the existing Slice-1 bell drawer.
  • Exception expiring-soon (design §3) and info-level digests / batched good-news are Slice 4, deferred.

🤖 Generated with Claude Code

Turn the bell into an action queue: route governance + remediation events to
the users who can act on them, not the whole fleet.

New primitives:
- auth.RolesWithPermission(p) — resolve a permission to the built-in roles that
  grant it (pure helper over BuiltInRoles).
- notifyfeed.Store.RecordForRoles — fan one row per active user holding any of
  the given roles (EXISTS, so a multi-role user gets one row), same
  upsert/collapse as RecordFanout.

Producer: notifyfeed.GovernanceProjector
- ExceptionRequested -> users who can approve (roles granting exception:approve:
  auditor, security_admin, admin), kind exception_pending, high, deep-link
  /settings/policies, grouped per exception.
- ExceptionDecided -> the requester only, exception_approved/_rejected, medium.
- RemediationFailed -> users who can act (roles granting remediation:execute:
  ops_lead, security_admin, admin), kind remediation_failed, high, deep-link
  /hosts/{id}, grouped per host+rule. Fires on a TERMINAL failure (execute that
  failed, or a rollback that did not restore) — NOT a successful user-initiated
  rollback (that is intended, not an alarm).

Producer hooks (best-effort, never fail the transition/job; nil-safe):
- exception.Service.WithNotifier + calls in Request/Approve/Reject.
- worker.RemediationWorker GovernanceNotifier + calls on the two failure sites.
- wired in cmd/openwatch/main.go (serve) and worker.go (dedicated worker).

Both producers hold interfaces in their own packages, so neither imports
notifyfeed. Spec system-notifications v1.5.0: C-08 + AC-15/16/17. Tests:
auth.RolesWithPermission unit test; DB-backed governance fan-out tests
(approver-scoped, requester-only, operator-scoped). No frontend change — rows
render in the existing bell drawer.
@remyluslosius remyluslosius merged commit 9cb8ce8 into main Jun 25, 2026
13 checks passed
@remyluslosius remyluslosius deleted the feat/notifications-slice3-governance branch June 25, 2026 16:46
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.
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