Skip to main content

System Logs & Notifications

Updated: 2025-10-08 16:18

This chapter unifies the authoritative spec and the later tiles‑first routing update into a single, no‑wiggle‑room reference you can hand to any agent. It defines the data model, delivery matrix, tile badge routing, SSE, APIs, and governance.


1) Core concepts

  • Single source of truth: system_logs is the master, append‑only history.
  • Per‑user state, not copies: notifications stores each user’s visibility/read state for a master row; there is no per‑user duplication of log content.
  • No unhide: when a user marks an item “read”, it disappears from their bell dropdown and personal notifications page and stops counting toward badges. The master entry remains in system_logs.
  • Targets (eligibility): a user is eligible when targets includes 'all', 'user:<uuid>' (them), or any 'group:<uuid>' they belong to.
  • Deliver: silent | normal | push controls if/how it surfaces for eligible users.
  • Editable vocabularies: log_types (multi‑tag) and log_platforms (single) are maintained in Settings and validated by the server.
  • One entry point: all logging goes through emitLog().

2) Data model (tables & indexes)

2.1 Master system log — system_logs

Key columns:

  • id uuid, ts timestamptz, message text
  • context jsonb (structured details)
  • types text[] (multi‑tag; validated against log_types)
  • party text (who or what originated it; e.g., user name/id, service)
  • platform citext (FK to log_platforms.slug)
  • targets text[]'all' | 'user:<uuid>' | 'group:<uuid>'
  • deliver text CHECK ('silent'|'normal'|'push')
  • display boolean (global soft‑hide; used by “Clear Logs” and purge rules)

Indexes:

  • btree: (ts DESC), (display), (deliver), (platform)
  • GIN: (types), (targets), (context)

2.2 Per‑user notifications — notifications

  • user_id uuid, log_id uuid, display boolean default true, delivered_at timestamptz
  • Primary key (user_id, log_id)
  • Insert a row only when the item should appear in the mini‑log or should count for badges (badge‑only entries use display=false).

2.3 Tile badges — user_tile_badges

  • Tracks unread counters per tile: inbox, messages, tasks, calendar, profile (Settings has no badge).
  • API bumps via bump_tile_badge(user_id, tile, +Δ).

2.4 Vocab registries & targeting

  • log_types(slug, name, active, protected) — multi‑tag set validated by server.
  • log_platforms(slug, name, active) — single select validated by server.
  • log_groups(id, slug, name, category, active) and log_user_groups(user_id, group_id) — for 'group:<uuid>' targeting.

3) Delivery behaviour (final matrix)

Per eligible user, considering Account Setting reduce_notifications (boolean):

  • false = “Everything”
  • true = “Targeted only”
delivertargeted?reduce = false (“Everything”)reduce = true (“Targeted only”)
silentanySystem log only (no toast • no mini‑log • no badge)same
normalYestoast + mini‑log; badge if tile‑routedif tile‑routed → badge‑only (insert notifications with display=false, bump badge); else → mini‑log only (display=true); no toast
normalNotoast‑only (no mini‑log, no badge)nothing
pushYestoast + mini‑log; badge if tile‑routedsame

Offline users: DB inserts (notifications + badge bumps) happen at emit time; SSE merely reflects truth when online.


4) Tile badges — routing by Settings

Tiles with badges: inbox, messages, tasks, calendar, profile (Settings tile never tallies).
A log can map to at most one tile. Mapping is config‑driven via admin.app_settings.tile_routes:

{
"priority": ["messages","inbox","tasks","calendar","profile"], // first match wins
"rules": {
"inbox": [ {"by":"platform","anyOf":["Email"]}, {"by":"type","anyOf":["email"]} ],
"messages": [ {"by":"type","anyOf":["message"]} ],
"tasks": [ {"by":"context.kind","anyOf":["task_created","task_assigned","task_commented","task_status_changed"]} ],
"calendar": [ {"by":"context.kind","anyOf":["calendar_event_created","calendar_event_updated","calendar_event_invite"]} ],
"profile": [ {"by":"context.kind","anyOf":["profile_comment","profile_mention","profile_share"]} ]
}
}

Server helper routeTileFor(log) evaluates tiles in priority and returns a tile or null.

Rule kinds:

  • by:"platform" → compare to log.platform
  • by:"type" → intersect with log.types
  • by:"context.kind" → compare (log.context->>'kind')

Earlier drafts used the tile name “messenger”. We standardize on messages. Use whatever display label you want in UI, but the tile key should be messages.


5) emitLog() — the only way to write logs

Input (validated):

type EmitLogInput = {
message: string;
types?: string[]; // must exist & be active in log_types
party?: string | null;
platform?: string; // must exist & be active in log_platforms
targets?: string[]; // 'all' | 'user:<uuid>' | 'group:<uuid>'
deliver?: 'silent' | 'normal' | 'push';
context?: Record<string, any>;
};

Flow:

  1. Normalize defaults: types=['info'], platform='Backend', targets=['all'], deliver='normal', display=true, context={}.
  2. Validate types/platform via registries; validate targets and expand 'group:<uuid>' to member user ids.
  3. Insert 1 row into system_logs (return log_id).
  4. Fan‑out per eligible user:
    • Decide tile via routeTileFor(log).
    • Apply the delivery matrix (Section 3):
      • mini‑log → insert into notifications with display=true; send event: log SSE.
      • badge‑only → insert notifications with display=false; bump tile badge; send event: badge SSE.
      • toast‑only → no DB row; send event: toast SSE.
    • Always push a consolidated event: badge after any badge change.

Forbidden: direct inserts to system_logs or notifications outside emitLog().


6) SSE — events & endpoints

Endpoint: GET /api/notifications/stream (per‑user auth; retry/reconnect).
Events:

  • event: log → a mini‑log item
  • event: toast → toast payload, no DB row created
  • event: badge{ badges: { inbox, messages, tasks, calendar, profile } }
  • event: telemetry → metrics (optional)
  • event: vocab → notify clients to refresh vocab dropdowns (optional)

7) REST API surface

Admin / System log

  • GET /api/logs → filters: q, types, party, platform, targets, deliver, display, from, to, page
  • PATCH /api/logs/:id → flip display (admin)
  • POST /api/logs/clear → hide display=true older than 7 days (senior mgmt/exec)
  • POST /api/logs/export (range)
  • POST /api/logs/export-all
  • POST /api/logs/purge → exec only; enabled only after successful “Export All”

Users / Notifications

  • GET /api/notifications → user’s mini‑log (visible entries only)
  • PATCH /api/notifications/:logId{ display:false } (mark read)
  • GET /api/notifications/badges → counters

Vocab (Settings‑managed)

  • GET/POST/PATCH /api/vocab/log_types
  • GET/POST/PATCH /api/vocab/platforms

Groups (App Settings)

  • GET/POST/PATCH /api/log-groups
  • GET /api/log-groups/:id/members
  • PUT /api/log-groups/:id/members{ add: uuid[], remove: uuid[] }

8) Retention & governance

  • notifications: purge after 161 days (23 weeks). System setting notifications_keep_unread controls whether unread (display=true) are protected.
  • system_logs:
    • “Clear Logs” = bulk set display=false for visible rows older than 7 days.
    • “Export”/“Export All”: include both display=true/false.
    • “Purge” (exec‑only): allowed only after a full export; typical policy is purge hidden rows older than 180 days.

9) UI behaviours

  • Header bell (mini‑log): shows only notifications.display=true; “Mark read” toggles to false. “Show more” goes to /me/notifications filtered to current user.
  • 3×3 Grid tiles: Calendar, Inbox, Settings, Profile, Tasks, Messages (five tally; Settings has none). Badges are pulled from user_tile_badges; clicking a tile can zero its counter server‑side.
  • Admin Logs page: full filters, tag badges for types, pill for deliver, and quick toggles for display. Buttons: Clear (role‑gated), Export, Export All, Purge (gated by Export All).

10) Performance & testing

  • Indexes in Section 2 keep queries snappy; prefer pagination on /api/logs.
  • Unit tests: emit validation; matrix permutations; tile routing; badge bumping.
  • Integration: mini‑log visibility; toast‑only, badge‑only; SSE receiving; admin actions.
  • E2E: role gates; persistence of Settings; vocab edits hot‑reload (event:vocab).

11) Glossary (final names)

  • Tiles: inbox, messages, tasks, calendar, profile (Settings has no badge)
  • Deliver: silent, normal, push
  • Targets: 'all', 'user:<uuid>', 'group:<uuid>'

Appendix — Minimal SQL pointers (already migrated elsewhere)

  • Tables: system_logs, notifications, user_tile_badges, log_types, log_platforms, log_groups, log_user_groups
  • Setting keys used here: tile_routes, notifications_keep_unread, notifications_purge_cutoff_days, ui_toast_autohide_ms, log_level, log_redact_secrets