Skip to content

Audit & notifications

Every state change in Nucleus writes an audit event. A subset of those events project into the notifications customers see in the portal bell + dropdown. Same data, two layers — you can rebuild the second from the first.

Audit events

When something happens — a spoke is provisioned, a federation request is approved, a token is revoked, a backup fails — the control plane writes a row:

json
{
  "id": "...",
  "kind": "spoke_provisioned",
  "actor": { "id": "...", "email": "..." },
  "objectType": "spoke",
  "objectId": "...",
  "metadata": { "size": "medium", "domain": "..." },
  "ip": "...",
  "userAgent": "...",
  "at": "2026-04-28T08:43:11.123Z"
}

The event is the source of truth. Dashboards, notifications, and customer reports all derive from it.

Notifications

Customers don't see every audit event — most are operational noise (auth_login, internal_metric_emit). Notifications are a curated whitelist of customer-visible kinds, projected onto a per-user feed:

  • spoke_provisioned, spoke_destroyed, spoke_actioned
  • federation_request_approved / _rejected
  • tenant_backup_failed
  • billing_subscription_refunded, billing_subscription_past_due
  • … and roughly a dozen more

A notification is just an audit event the user is allowed to see, filtered by the user's federation/spoke memberships. There's no separate notifications table — the read state is tracked separately, the content is the audit row.

TIP

That's why marking a notification "read" takes the audit-event id, not a notifications id — the notification is the event, not a copy.

What customers see

In the portal:

  • Bell in the top bar — count of unread, dropdown with the latest 10
  • Toasts — anything that arrives while the customer is logged in pops as a transient toast (errors stick longer than info)
  • /notifications page — full archive with All / Unread tabs, paginated by before timestamp cursor

On the API:

  • GET /api/notifications — list (cursor-paginated)
  • GET /api/notifications/unread-count — for the badge
  • POST /api/notifications/:id/read — mark one read
  • POST /api/notifications/read-all — mark everything visible read

See Notifications API for the full surface.

Retention

Audit events are retained indefinitely on the hosted plan; on self-host it's whatever your Postgres retention strategy allows. The audit log is append-only — events never get edited or deleted, even when the underlying object (spoke, user, token) is destroyed. That's deliberate: a post-mortem usually needs to ask "what did we do to this thing before it disappeared?".

Released under the GPL v3 license.