Voice: VoiceTTSAdapter.tierResolver never wired; tts.tier config dead for real synthesis #31

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

Summary

VoiceTTSAdapter.tierResolver is never wired in production. AppDelegate constructs the adapter with only engine:, leaving tierResolver at its default { .tier1 } constant. Setting tts.tier = "tier2" in config does nothing for the real synthesis path — only for the get_self_state tool, which reads snap.tts.tier directly via a separate closure.

Evidence

  • File: App/AppDelegate.swift:1236:
    let ttsAdapter = VoiceTTSAdapter(engine: VoiceOutputWiring.makeTier1Engine())
    
  • File: App/Voice/VoiceTTSAdapter.swift:29-35:
    init(engine: TTSEngineActor, tierResolver: @escaping @Sendable () async -> TTSTier = { .tier1 }) {
    
  • File: App/AppDelegate.swift:2020-2024 — get_self_state reads snap.tts.tier via separate closure

Impact

Today hidden by P0-5 (no Orpheus weights shipped → tier-1 is correct). The moment Orpheus weights ship, user can't enable tier-2 via config.

Recommended fix

Wire the resolver from configStore:

let ttsAdapter = VoiceTTSAdapter(
    engine: VoiceOutputWiring.makeTier1Engine(),
    tierResolver: { [configStore] in
        let snap = await configStore.perTurn()
        return TTSTier(rawValue: snap.tts.tier) ?? .tier1
    }
)

Test

  • Set tts.tier = "tier2" in config → on next synthesis, adapter resolves .tier2

Source

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

Cross-references

  • Related: #11 (M-6 Voice) — Settings surface (M-2 wired by M-6) becomes the canonical resolver path
## Summary `VoiceTTSAdapter.tierResolver` is never wired in production. AppDelegate constructs the adapter with only `engine:`, leaving `tierResolver` at its default `{ .tier1 }` constant. Setting `tts.tier = "tier2"` in config does nothing for the real synthesis path — only for the `get_self_state` tool, which reads `snap.tts.tier` directly via a separate closure. ## Evidence - File: `App/AppDelegate.swift:1236`: ```swift let ttsAdapter = VoiceTTSAdapter(engine: VoiceOutputWiring.makeTier1Engine()) ``` - File: `App/Voice/VoiceTTSAdapter.swift:29-35`: ```swift init(engine: TTSEngineActor, tierResolver: @escaping @Sendable () async -> TTSTier = { .tier1 }) { ``` - File: `App/AppDelegate.swift:2020-2024` — `get_self_state` reads `snap.tts.tier` via separate closure ## Impact Today hidden by P0-5 (no Orpheus weights shipped → tier-1 is correct). The moment Orpheus weights ship, user can't enable tier-2 via config. ## Recommended fix Wire the resolver from `configStore`: ```swift let ttsAdapter = VoiceTTSAdapter( engine: VoiceOutputWiring.makeTier1Engine(), tierResolver: { [configStore] in let snap = await configStore.perTurn() return TTSTier(rawValue: snap.tts.tier) ?? .tier1 } ) ``` ## Test - Set `tts.tier = "tier2"` in config → on next synthesis, adapter resolves `.tier2` ## Source - Audit: `.planning/audit-2026-05-12/voice-audit.md` (2026-05-12) — finding P0-4 ## Cross-references - Related: #11 (M-6 Voice) — Settings surface (M-2 wired by M-6) becomes the canonical resolver path
KofTwentyTwo commented 2026-05-13 11:52:00 +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#31