Voice: .ttsStopped emitted only on barge-in; natural completion leaves ducking engaged #30

Closed
opened 2026-05-12 19:06:23 +00:00 by KofTwentyTwo · 1 comment
KofTwentyTwo commented 2026-05-12 19:06:23 +00:00 (Migrated from github.com)

Summary

.ttsStopped is emitted ONLY on barge-in; normal completion never releases ducking. TTSEngineActor.synthesize yields .started, .firstAudio, .finished — never .ttsStopped. The class header and TTSEvent.swift document the canonical sequence as started → firstAudio → ... → finished → ttsStopped, and the TTSInterrupt.swift grep gate enforces wc -l == 1 for .ttsStopped emission sites across the package. The one site that emits is InterruptSequence.run — only when barge-in fires.

Evidence

  • File: packages/Voice/Sources/Voice/TTS/TTSEngineActor.swift:91-178
  • File: packages/Voice/Sources/Voice/TTS/TTSEvent.swift:7-12 (doc-comment canonical sequence)
  • File: packages/Voice/Sources/Voice/TTS/InterruptSequence.swift:97 (sole emit site, enforced by grep gate)
  • File: packages/Voice/Tests/VoiceTests/TTSInterruptTests.swift:181 (I4 test validates .ttsStopped is the ducking-release signal)
  • grep -rn ttsEventStream App/ packages/.../Sources/ finds only the TTSEngineActor definition — no production consumer wired yet

Impact

Not biting today (no production ducking consumer exists yet). But the design contract is broken — the first production consumer of ttsEventStream that implements ducking will leave the input duck engaged forever after a normal spoken reply. The contract / test pairing is an impedance mismatch hidden by mocks.

Recommended fix

Decide and execute one of:

  1. Make TTSEngineActor.synthesize emit .ttsStopped on natural completion (in addition to .finished). Update the file-level comment "Ducking releases ONLY on .ttsStopped" to stay correct. Loosen InterruptSequence grep gate to wc -l >= 1.

  2. Rewrite the contract docs to say "ducking releases on .finished for natural completion AND .ttsStopped for barge-in." Loosen InterruptSequence comment from "ONLY site that emits."

Option 1 is the cleaner contract; option 2 is documentation-only. Pick before #11 (M-6) wires the first production ducking consumer.

Test

  • AVSpeechSmokeTests / TTSEngineActorTier1Tests run a real short synthesis and assert .ttsStopped arrives (currently they short-circuit on .ttsStopped filter and the suite aborts before timeout)

Source

  • Audit: .planning/audit-2026-05-12/voice-audit.md (2026-05-12) — finding P0-3

Cross-references

  • Related: #11 (M-6 Voice) — natural landing for the production consumer; contract MUST be coherent before
## Summary `.ttsStopped` is emitted ONLY on barge-in; normal completion never releases ducking. `TTSEngineActor.synthesize` yields `.started`, `.firstAudio`, `.finished` — never `.ttsStopped`. The class header and `TTSEvent.swift` document the canonical sequence as `started → firstAudio → ... → finished → ttsStopped`, and the `TTSInterrupt.swift` grep gate enforces `wc -l == 1` for `.ttsStopped` emission sites across the package. The one site that emits is `InterruptSequence.run` — only when barge-in fires. ## Evidence - File: `packages/Voice/Sources/Voice/TTS/TTSEngineActor.swift:91-178` - File: `packages/Voice/Sources/Voice/TTS/TTSEvent.swift:7-12` (doc-comment canonical sequence) - File: `packages/Voice/Sources/Voice/TTS/InterruptSequence.swift:97` (sole emit site, enforced by grep gate) - File: `packages/Voice/Tests/VoiceTests/TTSInterruptTests.swift:181` (I4 test validates `.ttsStopped` is the ducking-release signal) - `grep -rn ttsEventStream App/ packages/.../Sources/` finds only the `TTSEngineActor` definition — no production consumer wired yet ## Impact Not biting today (no production ducking consumer exists yet). But the design contract is broken — the first production consumer of `ttsEventStream` that implements ducking will leave the input duck engaged forever after a normal spoken reply. The contract / test pairing is an impedance mismatch hidden by mocks. ## Recommended fix Decide and execute one of: 1. **Make `TTSEngineActor.synthesize` emit `.ttsStopped` on natural completion** (in addition to `.finished`). Update the file-level comment "Ducking releases ONLY on `.ttsStopped`" to stay correct. Loosen `InterruptSequence` grep gate to `wc -l >= 1`. 2. **Rewrite the contract docs** to say "ducking releases on `.finished` for natural completion AND `.ttsStopped` for barge-in." Loosen `InterruptSequence` comment from "ONLY site that emits." Option 1 is the cleaner contract; option 2 is documentation-only. Pick before #11 (M-6) wires the first production ducking consumer. ## Test - `AVSpeechSmokeTests` / `TTSEngineActorTier1Tests` run a real short synthesis and assert `.ttsStopped` arrives (currently they short-circuit on `.ttsStopped` filter and the suite aborts before timeout) ## Source - Audit: `.planning/audit-2026-05-12/voice-audit.md` (2026-05-12) — finding P0-3 ## Cross-references - Related: #11 (M-6 Voice) — natural landing for the production consumer; contract MUST be coherent before
KofTwentyTwo commented 2026-05-13 11:51:58 +00:00 (Migrated from github.com)

Closed by v0.1 cluster merges on 2026-05-13. Develop now at a8c7f1a0.

Closed by v0.1 cluster merges on 2026-05-13. Develop now at `a8c7f1a0`.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: KofTwentyTwo/Jarvis#30