First piece of "export a mirrored playlist to Spotify/Deezer" (diegocade1). Reuses the exact
machinery the ListenBrainz/JSPF export already proves out, additively:
- resolve_playlist_tracks gains an `id_key` param (default "recording_mbid" → LB/JSPF callers
byte-for-byte unchanged). The dedup/stats/order logic is ID-agnostic; only the output field
name differs, so service export plugs in with id_key="service_track_id".
- export_sources gains db_service_track_id(artist, title, service) + build_service_resolve_fn —
text-matches a library track (same pattern as the MBID resolver) and returns its stored
spotify_track_id / deezer_id. Enrichment already pinned those IDs, so export is a lookup, not a
re-search — which is what makes the reverse direction reliable (no fuzzy guessing).
No schema change needed: get/set_playlist_export_target already key by service name, so Spotify/
Deezer targets store for free (idempotent re-export, like the LB #903 fix).
7 tests: LB default id_key unchanged, service id_key carries the id + unmatched handling, service→
column mapping, unknown-service/no-title guards, resolve_fn id+source. 38 export tests green,
ruff clean.
Remaining increments: Spotify write client (+ playlist-modify scope / re-auth), Deezer write via
the ARL gw-light gateway, the export-job branch + endpoint, the modal options.