Change DEDUPE_TYPES from bool to int|true — int value overrides the
default 30-min window. scheduler.job_failed uses 1440 min (24h) so
admins receive at most one notification per failed job per day.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add ?ModuleEventDispatcher to SchedulerRunService (fail-open, nullable).
Dispatch scheduler.job_failed event when a job fails with payload:
job_id, job_key, label, error_code, error_message, trigger_type.
New SchedulerJobFailedNotificationListener in notifications module
creates in-app admin notifications for users with JOBS_MANAGE
permission, scoped per tenant, with 30-min dedup per job_key.
Also fixes audit module manifest: system_audit_purge job_key was
mismatched (audit_system_purge vs system_audit_purge in DB).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove NotificationRepositoryFactory, NotificationServicesFactory, and
NotificationRepositoryInterface — all single-consumer abstractions with
no polymorphism benefit. Simplify container registrar to wire services
directly. Extract shared listener logic (sanitizeTenantIds,
resolveDisplayName, resolveUuid) into NotificationListenerTrait,
eliminating duplication across 4 listeners.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Module pages must use a unique top-level directory (audit/) instead of
nesting under admin/ which collides with core's pages/admin/ during
module:build symlink creation. Routes still map admin/* URLs to the
audit/ page directory via manifest route targets.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
In-app notification system with topbar bell icon, dropdown panel,
mark-as-read/dismiss actions, and scheduled cleanup of old read
notifications. Includes event listeners for user.created and
user.deleted events, authorization policy, and full test coverage.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>