- integration-routes.js: accepts student.contact_email, calls notifyForEnrollment on a
genuinely new enrollment only (never on an idempotent replay — inherits Phase 5B's proven
enrollment-level idempotency, no separate notification dedup needed).
- admin-routes.js: create-student/convert/reset-password now enqueue the corresponding
notification using the SAME token already minted synchronously (never mints twice, which
would silently invalidate the token just handed back in the response). Fixed a real bug:
request-password-reset would mint a reset token for a never-activated student, which makes
no sense and would confuse them — now falls through to the generic anti-enumeration response.
- admin-programs-routes.js: manual enrollment triggers the same notification decision as the
website path.
- student-auth-routes.js: /activate records a metric; /change-password and /reset-password
both queue a password_changed security notice; /request-password-reset now actually delivers
email (production still never returns the token — unchanged anti-enumeration/isProduction
gating, just now with a real send behind it).
- rbac.js: requireStudentRelationshipAccess/checkStudentRelationshipAccess — the notification
visibility rule (cohort-shared or an enrollment they created), reusable beyond just
enrollments.
- server.js: wires notification-routes.js and starts the notification worker in main().
- docker-compose.dev.yml: PUBLIC_APP_ORIGIN, EMAIL_FROM_NAME, and a parametrized COOKIE_SECURE
(was hard-commented-out; needed so e2e-phase5c.js can actually prove production gating).