The cross-user bucket query combined SELECT DISTINCT with ORDER BY md5(...),
which Postgres rejects at plan time (SQLSTATE 42P10). buildDiscoverCandidates
returned that error on first bucket failure, so the whole Discover playlist
was skipped every nightly run — even though the random bucket pool was
healthy. Switched to GROUP BY so the md5 ordering expression no longer needs
to appear in the select list, and hardened the function so future single-
bucket failures degrade gracefully via slot redistribution instead of taking
out the whole playlist.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds 'discover' as an accepted system_variant on the playlists
table — alongside 'for_you' and 'songs_like_artist' — and three
sqlc bucket queries that back the new playlist's daily candidate
selection.
The three buckets surface tracks the user hasn't played and
hasn't liked:
- Dormant artists: artists this user has played < 10 times total.
Surfaces the long tail of their library.
- Cross-user likes: tracks any OTHER user has liked but this one
hasn't engaged with. Empty on single-user servers; the Go-side
allocator redistributes the deficit across the other two
buckets.
- Random unheard: pure random sample as the safety net and the
cold-start fallback.
All three share exclusion filters: not-played by this user, not
liked by this user, not in lidarr_quarantine for this user. All
order by md5(track_id || dateStr) for daily determinism — same
pattern as the existing tieBreakHash logic in system.go.
LIMIT values are generous (80/60/200) so the per-album (<=2) /
per-artist (<=3) caps and slot redistribution in T2 have headroom.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>