From ea40c46b8b0dc548b5240df69dcf986254360038 Mon Sep 17 00:00:00 2001
From: cai <cai@nbcai.cc>
Date: Wed, 12 Aug 2026 13:11:40 +0800
Subject: [PATCH] fix(helper): project fixture probe events to runtime logs

---
 docs/runtime-contract.md |   58 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 files changed, 57 insertions(+), 1 deletions(-)

diff --git a/docs/runtime-contract.md b/docs/runtime-contract.md
index 1ac5424..aa48970 100644
--- a/docs/runtime-contract.md
+++ b/docs/runtime-contract.md
@@ -27,6 +27,7 @@
 ```bash
 node tools/validate-turn-stream-fixture.mjs fixtures/turn-stream-happy.ndjson
 node tools/validate-turn-stream-fixture.mjs fixtures/turn-stream-mp3-chunks.ndjson
+node tools/validate-asr-realtime-fixture.mjs fixtures/asr-realtime-happy.ndjson
 ```
 
 The fixture validates only the protocol shape and fast-path invariants:
@@ -34,7 +35,7 @@
 - `reply_playback_mode_selected` appears before audio chunks.
 - `reply_playback_started` appears before the first audio chunk.
 - `reply_audio_chunk.audioChunk.format` is `pcm_s16le`, `mp3`, `mpeg` or `wav`.
-- `pcm_s16le` chunks are `48000Hz` mono and 16-bit aligned.
+- `pcm_s16le` chunks are `16000Hz` or `48000Hz` mono and 16-bit aligned.
 - Encoded chunks such as `mp3` must have non-empty payload bytes. This validates contract shape only; decoder and LiveKit write behavior still require runtime smoke.
 - A terminal event exists.
 
@@ -80,6 +81,61 @@
 
 Service mode does not change the media worker boundary. It only replaces `lmrobot-app -> ProcessBuilder` with `lmrobot-app -> helper HTTP control plane`.
 
+## Bot audio output profile
+
+The bot output profile is a helper deployment setting, not a per-call business field. Worker mode reads it directly from `CV_*` variables. Service mode workers inherit it from the helper service process.
+
+For the current Lmtest / Combrabo Voice dynamic reply target, the business chain is fixed to `16000Hz / mono / pcm_s16le` from Java stream bridge to helper bot-track output. The helper must not explicitly convert dynamic reply output to `48000Hz` in this path. `livekit-48k` is retained only for historical paths or emergency rollback, not as this round's target profile.
+
+| Profile | NativeAudioSource | Purpose |
+| --- | --- | --- |
+| `pcm-16k` | `16000Hz / mono` | Default and current dynamic reply target profile. |
+| `livekit-48k` | `48000Hz / mono` | Historical / emergency rollback profile only. |
+| `custom` | `CV_BOT_SAMPLE_RATE_HZ / CV_BOT_NUM_CHANNELS` | Local debug only unless promoted by a separate decision. |
+
+```bash
+CV_BOT_AUDIO_PROFILE=pcm-16k
+CV_BOT_AUDIO_PROFILE=livekit-48k
+CV_BOT_AUDIO_PROFILE=custom CV_BOT_SAMPLE_RATE_HZ=16000 CV_BOT_NUM_CHANNELS=1
+```
+
+`bot_track_ready` is the authoritative runtime signal for the selected profile:
+
+```json
+{
+  "eventType": "bot_track_ready",
+  "eventPayload": {
+    "trackName": "bot-main-audio",
+    "audioProfile": "pcm-16k",
+    "sampleRate": 16000,
+    "numChannels": 1
+  }
+}
+```
+
+Dynamic streaming reply completion must expose both source and target audio metadata:
+
+```json
+{
+  "eventType": "bot_reply_audio_write_finished",
+  "eventPayload": {
+    "format": "pcm_s16le",
+    "sourceSampleRate": 16000,
+    "sourceChannels": 1,
+    "targetAudioProfile": "pcm-16k",
+    "targetSampleRate": 16000,
+    "targetChannels": 1,
+    "networkChunkCount": 68,
+    "debugSourcePath": "/tmp/.../stream-reply-turn-0001-source.wav",
+    "debugPcmWavPath": "/tmp/.../stream-reply-turn-0001-target.wav"
+  }
+}
+```
+
+This profile affects bot output only. User audio observation, VAD and turn artifacts remain `48000Hz / mono` in this phase.
+
+The current acceptance requires both `stream-reply-{turnId}-source.wav` and `stream-reply-{turnId}-target.wav` to be `16000Hz / mono / pcm_s16le`. If the target dump is `48000Hz`, the dynamic reply 16k chain is not complete.
+
 ## Service mode endpoints
 
 All session endpoints require:

--
Gitblit v1.9.3