Drift-audit Group 8 (lifecycle gaps):
- change_password no longer 500s for OAuth-only users: short-circuit when
password_hash is None (verify_password would crash on None) so the route
returns a clean 4xx instead of a 500.
- register_with_invitation no longer locks the invitee out on a username
collision: create the user FIRST, then mark the token used, so a failed
creation (409) leaves the single-use invite valid for retry.
- update_event re-arms reminder_sent_at when start_dt/reminder_minutes change,
so a rescheduled event fires again instead of being permanently suppressed.
- Migration 0061: uq_topic_per_rulebook / uq_rule_per_topic become PARTIAL
unique indexes (WHERE deleted_at IS NULL). Trashing 'X' then recreating it
no longer 500s on the dead row's title. Model __table_args__ updated to match.
Deferred: per-occurrence reminders for recurring events (event_scheduler) —
needs a per-occurrence reminder-state design, not a one-line gate tweak.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>