Skip to content

Watchdog / integrity emails reach all admin users instead of the system owner only

Symptom

Owner received an [EOP] Reconciliation watchdog is not running alert email (To: me@jefferochan.com, alisonhytang@gmail.com, jake@establishrecords.com). The reconciliation cron had been off-air for 72h (after T-084 paused GitHub Actions cron pending NAS-side wiring), so the watchdog firing was correct β€” but the recipient set was wrong. Only the owner should receive infrastructure-class alerts; the two admin users (jake, alison) work the accounting / projects domains and can't act on a broken cron.

Audit (aote-system / users, 2026-06-22)

Email Role Status
me@jefferochan.com super_admin active (sole super_admin)
jake@establishrecords.com admin active
alisonhytang@gmail.com admin active
jeffero.c@gmail.com auditing_accountant suspended

The system uses one role per user (u.role: UserRole, see lib/rbac/types.ts:248). notifyAllMultiChannel fanned out to every active user β€” by design for true "anyone with the app open should see this" events, but mis-applied for infra/integrity alerts where only the owner can fix the underlying problem.

Resolution (this PR)

  • Removed notifyAllMultiChannel from lib/notifications/notify.ts.
  • Added notifySystemOwner β€” a thin wrapper that delegates to notifyRoles(['super_admin'], …), reusing the existing in-app + email fan-out path. Today's roster: only the owner holds super_admin, so the audience is exactly one address.
  • Routed all 5 ex-callers to notifySystemOwner:
  • pages/api/cron/reconciliation-heartbeat.ts β€” watchdog-down alerts
  • pages/api/cron/reconciliation.ts β€” daily cron summary
  • pages/api/accounting/reconciliation/run.ts β€” interactive run (3 call sites: summary, per-gap, imbalance)
  • Stale comments in lib/notifications/integrityEmail.ts, lib/periodClose/readiness.server.ts, and lib/notifications/notify.ts updated.

Net effect: jake and alison no longer receive watchdog / reconciliation-drift / imbalance alerts. Owner remains the sole recipient.

Escalation β€” T-095

Owner's preferred long-term shape is a dedicated it_support role (so infra/integrity alerts can be delegated to a non-admin IT contact without granting them accounting privileges). That's a structural RBAC change (extend UserRole union, add to ROLE_PERMISSIONS, role-assignment UI, custom claims) β€” broken out into T-095 to land as its own reviewed PR. This ticket's narrowing to super_admin is the zero-leak interim while T-092 is designed.

Decision log

2026-06-22 β€” leak observed, narrowed to super_admin

  • βœ… Attestation (Accounting [Infrastructure Development]): read AGENTS.md; checked the board β€” no existing task / ticket covers this notification scope (T-084 is cron migration, not RBAC scope).
  • Source: Accounting [Infrastructure Development] Β· https://claude.ai/code/session_015P6KzVYsQCLgEmUjR9bMwM
  • Proposed by: the owner. Approved by: the owner (same prompt).
  • Evidence β€” owner, 2026-06-22 (verbatim):

    "Please look thru the repo and make sure that this kind of email does not send to any other system admins but me@jefferochan.com only (look thru the RBAC and check please)"

"we either point notifyAllMultiChannel directly to just me, or you remove notifyAllMultiChannel and point those which uses notifyAllMultiChannel to me"

(Via AskUserQuestion: chose "super_admin now + T-092 later" β€” fix the leak today, design the it_support role as a separate PR.)