[Backend] Stop the ASR server's speaker label overriding the known track owner #342
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Severity: HIGH. Found in the August 2026 session lifecycle review (#319).
The defect
The backend knows exactly who owns each track — it sent one file per speaker — and then discards that knowledge in favour of whatever the remote server echoes back.
audio_service.py:535—transcribe_sessiontakesseg.get("speaker", "Unknown")with no cross-checkaudio_service.py:460— on the VAD path,seg.get("speaker") or display_namegives the server's value precedence over the known ownerIf the server runs diarization or normalises to a label like
SPEAKER_00, that silently replaces the real name._apply_character_names(reminder_tasks.py:2121-2130) then fails to match, and the character mapping is lost for that whole track. If the server associates results by completion order rather than by the order files were sent, every line of a track is attributed to the wrong player — wholesale, plausible-looking, and undetectable from inside the backend.Proposed fix
The track owner is authoritative. Make the fallback unconditional: use
display_namealways, and treat any server-supplied speaker label as advisory metadata at most. On the session endpoint, zip results back to tracks by an explicit identifier rather than by position.This is a one-character change on the VAD path and a small one on the session path, and it removes an entire class of silent misattribution.
Acceptance criteria
SPEAKER_00or a wrong name does not change attributionVerified, and closed in
dd8f305+38631ba— but not by the fix this issue proposes.The proposed fix would not have worked
This issue asks to "zip results back to tracks by an explicit identifier rather than by position". Implementing that literally would have ticked the criterion and changed nothing: a permutation of ids survives an id-join exactly as a permutation of names survives a name-join. Any echo-based join is unverifiable in principle, because the only thing that could confirm the association is the association being asserted.
Commit
3ee27ffrecorded the partial-swap case and called it unfixable by a test. It understates the exposure. Consider the failure this issue's own body names as its headline — "if the server associates results by completion order rather than by the order files were sent, every line of a track is attributed to the wrong player — wholesale, plausible-looking, and undetectable":A symmetric swap returns Kira's audio labelled "Bryn" and Bryn's labelled "Kira". Every label sent comes back, so
by_labelresolves every segment, every track receives segments,check_every_speaker_was_transcribedpasses cleanly, and the whole session is misattributed with nothing raised and nothing logged. A one-directional swap was caught; this was not, and it was still fully live.What was done instead
transcribe_sessionnow sends one file per request throughtranscribe_track, which stamps each segment with the label of the file it sent. A response can only be about the file that was sent, so there is nothing to permute and no join to get wrong.Not a new design: it is what the VAD path has always done, and VAD is on by default since #323 — the safe shape was already shipping for most deployments, and this brings the last path in line.
/transcribeper file is also the ordinary Whisper contract;/transcribe/sessionwas a custom multi-file extension.Cost, flagged plainly: VAD-off sessions now make one request per speaker instead of one per session. Against the same endpoint the VAD path already calls many times per session, so a proven load shape rather than a new one. Sequential, matching
transcribe_session_vad; concurrency belongs with #356.Criteria
SPEAKER_00or a wrong name does not change attribution — previously this raised and lost the session; now the label is simply not consulted. Also fixes the three cases where raising was avoidable: a single-track session (attribution needs no information from the server at all), a server that omitsspeaker, and cosmetic normalisation.The one that mattered most had no test at all
transcribe_track— the function whose entire claim is that it never reads the echo, and the default production path — had no direct test. Every VAD test patches it out and the doubles return a pre-baked"speaker": track.label, so the fixture supplied the answer the assertion checked.Verified by mutation: reverting it to
seg.get("speaker") or track.label— the exact defect this issue's body names as its second site — passed all 1,300 tests. Now covered directly.A bug this uncovered
The non-VAD path never passed a language, because the multi-file endpoint took none. A campaign that had pinned one under #419 had it honoured on the VAD path and silently ignored on the other. Per-file requests make it the same call, so it now applies on both.
Also corrected
get_vad_config's docstring claimedenabled"defaults to False so the transcribe path stays byte-identical to the untrimmed behaviour" — backwards since #323, and exactly the wrong thing to read when sizing the blast radius of a change to either transcription path.Closing.