fix(scrobbles): prevent duplicate scrobbles across sources
Same track+timestamp from different at-uris was slipping past the
uri-only unique constraint. Three layers:
- DB UNIQUE (user_id, track_id, timestamp) + dedupe migration that
re-points shouts before deleting losing rows.
- ON CONFLICT DO NOTHING in jetstream + navidrome inserts; jetstream
skips the NATS publish + Discord webhook on conflict.
- Redis SETNX guard around putScrobbleRecord so concurrent sources
(Spotify/Last.fm/Navidrome) can't each write their own at-record
for the same listen.