The emit schemas existed without any emit() in the code (2026-07-16
'silent contract lie' audit). Session lifecycle events now go through the
transactional outbox atomically with the UserSession write, mirroring
staff_roles:
- SessionService.create emits user.session_created (login/refresh/SSO/QR)
- SessionService.revoke_by_jti (logout), new SessionService.revoke_session
(per-device revoke endpoint) and SessionService.revoke_all (password
change, revoke-all) emit user.session_revoked; idempotent re-revokes
stay silent
- suspicious-login 'this wasn't me' endpoint now revokes via
SessionService.revoke_all (its raw .update() skipped blacklisting and
would have skipped the event)
- events.py: payload dataclasses + registry entries
Tests validate outbox payloads against the published JSON schemas; the
three registration-emit tests updated for the second (session) event now
present in the flow.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>