- cleanupExpired() drops sessions expired by tokenTtl or sessionIdleTimeout and is
called opportunistically on issuing a token, bounding the session table; it is
protected and self-locking so subclasses can run it periodically
- issueToken() refuses a token already used by an active session instead of
silently merging two sessions (login replies 500 on a generator collision)
- document that the token generator must be collision-resistant
- tests: SessionCleanupOnLogin, ManualCleanup, TokenCollisionRejected