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)¶
| 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
notifyAllMultiChannelfromlib/notifications/notify.ts. - Added
notifySystemOwnerβ a thin wrapper that delegates tonotifyRoles(['super_admin'], β¦), reusing the existing in-app + email fan-out path. Today's roster: only the owner holdssuper_admin, so the audience is exactly one address. - Routed all 5 ex-callers to
notifySystemOwner: pages/api/cron/reconciliation-heartbeat.tsβ watchdog-down alertspages/api/cron/reconciliation.tsβ daily cron summarypages/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, andlib/notifications/notify.tsupdated.
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_supportrole as a separate PR.)