# WebRTC Trickle ICE: приёмка 05.10.2026

В той же Chrome-сессии медиана local SDP → connected сократилась с 16,108 с
до 1,245 с. Все пять штатных подключений установили DTLS через TURN relay;
после подключения росли Opus/RTP-счётчики. Синтетическая речь прошла через
Core PCM, streaming ASR/NATS и вернулась в чат как partial, final и ответ.
Это измерение подключения в текущей сети, а не скорости модели распознавания.

## Исходники, CI и фактические контейнеры

- Реализация: [95595f2](<https://git.giscloud.ru/voice-chat/webrtc-komaroff/-/commit/95595f22ad0c84a9140e3a564b9eb8e6afc744ac>),
  [pipeline 4899](<https://git.giscloud.ru/voice-chat/webrtc-komaroff/-/pipelines/4899>).
- Совместимость с TCP host-кандидатами Chrome:
  [a925c28](<https://git.giscloud.ru/voice-chat/webrtc-komaroff/-/commit/a925c28c21e33dc83424704c7bdf4f5407a7fe29>),
  [pipeline 4900](<https://git.giscloud.ru/voice-chat/webrtc-komaroff/-/pipelines/4900>).
- В обоих pipeline Python, Frontend, Ruby checks и `build streaming consumers`
  завершились успешно. `build push` и `deploy` остались manual; полный DRS deploy
  не запускался. Состав стэков, NATS, TURN и ASR mode не менялись.
- Core сначала обновлён на 95595f2, затем Frontend на 95595f2; после проверки
  Core обновлён на a925c28. Listener выполнил rollout, повторное U не запускалось.

Insight подтвердил `running`, `1/1`, `UpdateStatus=completed` и OCI revision.
Image ID совпал с `writing image` соответствующего CI build:

| Контейнер во время приёмки | OCI revision | Image ID / SHA-256 конфигурации образа |
|---|---|---|
| Core | a925c28c21e33dc83424704c7bdf4f5407a7fe29 | 021629b24ffa13231a5ae819cfca5fadd400d4bd611d7c98bbb53628e8487c21 |
| Frontend | 95595f22ad0c84a9140e3a564b9eb8e6afc744ac | 067e526f5ee85dee59590f3eb381dc5155c4d859a17684f560db590b3f587430 |

Это content hash образа контейнера, не registry manifest digest: Insight inspect
не выдаёт RepoDigests, а сохранённый Docker SSH context не имеет доступа.
Позднейшая поставка этого отчёта может иметь другую Frontend revision.

## Замеры в одной сети и браузере

Контроль до изменения: local SDP → POST занимал 10002 мс; POST → answer —
5224/5291 мс; answer → connected — 868/828 мс. Итог: 16094/16121 мс.
Время разрешения микрофона исключено. Во всех новых замерах source —
синтетический MediaStream; физический микрофон и его permission latency не измерялись.

| Попытка | Local SDP → connected, мс | POST → answer, мс | Answer → connected, мс | RTP за следующую секунду |
|---|---:|---:|---:|---:|
| 1 | 1363 | 149 | 1214 | 0 → 50 packets |
| 2 | 1229 | 128 | 1101 | 0 → 49 packets |
| 3 | 1245 | 126 | 1118 | 0 → 50 packets |
| 4 | 1287 | 125 | 1162 | 0 → 50 packets |
| 5 | 1243 | 120 | 1123 | 0 → 50 packets |

Во всех пяти: `connected`, DTLS `connected`, remote `relay`, local `srflx`.
Серверный `end` ещё не получен в момент connected: медленное gathering Core
не задерживает media. Клиентское gathering в этой сети уже успевало завершиться.
Тест с зависшим STUN на **обеих** сторонах выполнен отдельно на точных версиях
библиотек: реальные ICE/DTLS/Opus/RTP/decoded PCM работают до его завершения.

## Пользовательские и негативные сценарии

- WAV `webrtc-control-2plus2-20261002.wav`: PCM16 mono 48 kHz, 3,866125 с,
  SHA-256 `513c1066858c3b6052cb1f648cdce18825edd29c47a3a546f149097f9e6fd419`.
  После WAV продолжалась тишина, чтобы не прерывать PCM-поток.
- NATS: пять partial, затем один final «Сколько будет? 2 плюс 2. Ответь коротко.»;
  затем `SHOW_MESSAGE`, в UI «2 плюс 2 будет 4!».
- Отдельный текстовый запрос «Сколько будет 3 плюс 4?» получил ответ «3 плюс 4 будет 7!».
  В `/core/history` сохранены четыре user/assistant сообщения; после reload они
  восстановлены в UI. Тесты использовали отдельную сессию.
- Legacy offer **без** `trickle` на актуальном Core: DTLS connected,
  `prflx → relay`, RTP 0 → 49 packets; local SDP → connected 16092 мс.
  Для вызова frontend fallback только в тестовой вкладке capability заменялась на 404.
- Дополнительный Trickle NAT-тест: из исходного SDP убраны кандидаты, два
  отправляемых srflx-кандидата имеют намеренно изменённый публичный порт;
  все пять UDP host-кандидатов содержат приватные адреса.
  Core обнаружил рабочий NAT-путь: connected за 1936 мс, DTLS и RTP 0 → 50.
  Это проверка несовпадения сообщённого и фактического порта; browser stats
  по-прежнему называют локальный кандидат srflx. Реальный prflx явно виден
  в отдельном legacy-сценарии выше.
- Без OAuth GET capabilities, DELETE и WebSocket Upgrade возвращают 302 на вход,
  защищённые данные не выданы. С OAuth handshake работает.
- Неверный connection token: WS закрыт с 1008, ни одного сообщения с данными;
  действующее соединение владельца остаётся connected.
- После закрытия ICE-канала уже connected peer остаётся connected: DTLS работает,
  RTP растёт 1950 → 2000 packets; UI предупреждает `audio remains connected`.
- Явная остановка закрыла все девять созданных тестовых peer. HTTP DELETE после
  одновременного peer close может получить 404: запись уже удалена; UI cleanup
  идемпотентен. Изолированные HTTP/WS tests подтверждают token DELETE 204,
  освобождение registry, auth timeout, лимиты и разрыв до connected.

Все браузерные подмены getUserMedia/RTCPeerConnection/fetch/WebSocket, временный
file input и AudioContext удалены после проверки; голос отключён.

## Найденная несовместимость и исправление

Первый Trickle rollout отвергал валидные TCP host-кандидаты Chrome и закрывал
попытку с `Invalid ICE signaling message`. UDP-кандидаты уже были пригодны для
соединения. [handle_ice.py](<https://git.giscloud.ru/voice-chat/webrtc-komaroff/-/blob/2fde34101e26fac4771a2f99e3f1b3a7de8125e3/src_core/handlers/handle_ice.py>) теперь проверяет
принадлежность MID/ufrag и общий лимит, затем пропускает TCP-кандидат без отмены
UDP/TURN. [Регрессионный тест](#archive-availability) отправляет
Chrome TCP active, дубликат, UDP и end через настоящий aiohttp WebSocket.
Пять успешных замеров выполнены **после** этого исправления.

## Границы результата

Точная длительность до первого PCM до изменения не наблюдалась в доступной
production телеметрии; сравнения этого времени нет. Декодирование подтверждено
реальными pinned aiortc tests и production ASR-результатом. WER/DER, физический
микрофон, ICE restart и auto-reconnect не проверялись и не добавлялись.
Дополнительное полное отключение клиентского STUN не установило соединение;
поддерживаемая конфигурация сохраняет STUN/TURN, как в принятом плане.
Числа не являются обещанием для других сетей.

Приёмка полного состава chat/voice от 04.10 остаётся отдельным
[датированным снимком](</experiments/voice-ai/archive/w/DOCS/PRODUCTION_STATE.md>). Сегодня повторно проверен изменённый
путь WebRTC → PCM → ASR/NATS → чат/история; файловые voice UI и CPU benchmark
не менялись и не объявляются заново проверенными этим отчётом.
