D2: persist label attrs (from GoldIngest or the judge labeler) into
labeled_pairs, idempotently. A re-run upserts on the natural key
(bank_id, query_ref, memory_ref, source) rather than duplicating, so a labeling pass can be
safely resumed or corrected. Invalid rows are collected, not fatal — the batch reports
what it wrote and what it rejected.
Summary
Types
@type result() :: %{written: non_neg_integer(), errors: [Ecto.Changeset.t()]}