합성 통화 재생기가 마지막 턴을 잃는다 — 연결 닫는 시점

정성윤이 09-22 오늘 진행 기록·미결 항목을 보고 만든 티켓이다. 담당은 «제안»이다 — 본인이 확인하고 맞으면 착수, 아니면 옮긴다.

무엇을

09-22 운영 SYN-010(류준)에서 마지막 턴(5, 상담원 인사)이 저장되지 않았다(4/5). 같은 날 정성윤 재투입은 5/5, SYN-008 은 16/16. 재생기가 마지막 확정 직후 연결을 닫는 경쟁으로 보인다(가설 — 류준 05번 기록).

완료 조건

2026-09-22 — 코드로 본 원인과 재생기 수정 (류준, 운영 재확인 전)

원인 — 증명된 것과 가설을 가른다.

재생기 수정(원인이 어느 쪽이든 조용히 잃지 않게) — replay_persona_call.ts · persona_replay/final_echo.ts

남은 것: 로컬 24건 재생(다음 E2E 묶음) · 운영 SYN-010 재투입으로 5/5 확인 · 콜 미디에이터 로그로 ①/② 가르기.

2026-09-22 — 운영 재투입 5/5 (정성윤) → done

2026-09-22 (저녁) — 운영 확인, 닫는다 (류준)

운영 SYN-010 재투입(syn-prod-syn-010-20260922-v2)에서 저장 5/5, 재생기가 「확정 자막 5건이 전부 /ws 로 돌아왔다(마지막 확인까지 207ms)」를 확인한 뒤 닫았다. 로컬 E2E 도 394/394. 원인은 확정하지 못했다 — 코드로 「재생기가 일찍 닫아서」는 기각됐고, 남은 가설(① 마지막 확정이 미디에이터에 안 닿음 ② 미디에이터→서버 전사 실패)은 오전 운영 미디에이터 로그로 가른다(정성윤, 미결). 재생기는 이제 같은 일이 나면 조용히 넘어가지 않고 경고 + 종료 코드 1 을 낸다.

2026-09-22 (밤) — 원인 판정: ② 콜 미디에이터 → 서버 전사 요청 실패 · 확정 재시도 (류준, 운영 로그 없이)

판정 — ② (증명: 코드 + 운영 산출물 소거법). ①(마지막 확정이 콜 미디에이터에 닿지 않음)은 기각된다. 실패 종류(시간 초과 · 5xx · 연결 끊김)는 여전히 미결이다.

수정 — 콜 미디에이터가 확정 전사를 일시 실패에 한 번에 버리지 않는다 (call_registry.ts ingest()·INGEST_RETRY_DELAYS_MS). 확정만, 연결 실패·시간 초과·5xx·429 일 때 300ms · 1,000ms 뒤 다시 보낸다(최대 3회). 4xx·interim 은 다시 보내지 않는다. 다시 보내도 되는 근거는 서버 저장이 (call_id, segment_id) UPSERT + 마스킹 구간 지우고 다시 넣기라는 것(decisions/205) — 첫 요청이 늦게 서버에서 끝났어도 행은 하나다. 재시도는 채널 줄 안에서 해서 순서가 뒤집히지 않는다(그동안 뒤 자막이 늦는다). 경고 로그에는 통화·발화 번호·상태·시도 횟수만(SEC-1). 단위 테스트 6건 추가 — 옛 코드에서 4건 실패, 새 코드 153/153.

남은 것: 실패 종류(시간 초과인지 5xx 인지 연결 끊김인지)와 그 서버 쪽 원인 — 운영 콜 미디에이터·서버 로그(정성윤, 파드가 교체됐으면 못 가른다). 로컬 콜드 스타트로는 재현되지 않았다. 확인이 안 되는 가설 하나: undici 연결 재사용 ↔ uvicorn keep-alive 5초 경계(연결 끊김이면 서버가 요청을 못 받아 DB 에 없는 것과 맞는다).