refactor(speakers): single locked durable writer for speaker_labels.json master
speaker_labels.json is a read-modify-write merge store but had multiple uncoordinated writers (CLI/backfill, web routes, talent hooks, discovery) doing unlocked whole-file replaces — concurrent edits stale-read and clobbered each other's corrections. Collapse every writer onto one locked, fsync-durable, atomic read->merge->write leaf in attribution.py (update_speaker_labels + save_speaker_labels / save_speaker_labels_stub / apply_label_patches), mirroring the update_npz locked-RMW pattern. The merge re-reads on-disk state inside the lock and preserves user_* labels, so concurrent edits to different sentences both survive (last-writer-wins is gone). Route handlers surface lock-timeout as the SPEAKER_LABELS_BUSY owner-voice reason; api_discovery_identify discriminates labels vs voiceprint timeouts by lock path. Delete routes._save_speaker_labels and the raw talent stub writes (no shims). Register attribution.py as the speaker-labels write owner (AGENTS.md §7 L2 + check_journal_io_access OWNER_FILES). Fix stale agents/ docstrings to talents/. Add a real two-process merge-survival test. Out of scope (separate writers, deferred): think/entities/merge.py entity-rename rewrite (shares its batch writer with speaker_corrections.json) and speaker_corrections.json locking. Co-Authored-By: OpenAI Codex <codex@openai.com>