The PATCH /api/v1/admin/admins/{id} handler's password branch updated
the password hash but explicitly preserved every live session and API
key for the target user. That made password rotation a non-event for
credential compromise: a hijacked session that triggered a password
change kept its grip after the rotation, and a leaked API key survived
its owner's "I'm rotating because I think I'm compromised" reaction.
Now the password branch mirrors the deactivation branch:
1. update the password hash (unchanged),
2. `state.sessions.delete_for_user(id)` — wipes every live session
including the caller's, which logs them out and forces re-login
under the new password,
3. `state.keys.expire_all_for_user(id)` — revokes every API key.
This matches the `cmd_reset_password` CLI flow already in the codebase.
The dashboard's 401 handler redirects to the login screen, so the UX
on self-change is "you're logged out; sign back in".
Audit ref: security_audit/01_authn_session.md (H-E1).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>