Skip to content

Plan KL P3: a refused voice key is asked once - #950

Merged
straff2002 merged 2 commits into
mainfrom
feat/kl-p3-voice-key-latch
Nov 4, 2026
Merged

straff2002 merged 2 commits into
mainfrom
feat/kl-p3-voice-key-latch

Conversation

@straff2002

Copy link
Copy Markdown
Owner

Plan KL P3: a cloud voice key that was refused is tried once per key, not once per spoken line.

What changes

  • CloudVoiceRejection gains .keyNotAccepted: a plain 401/403, or Gemini's API_KEY_INVALID 400. .refused keeps its raw value and now means the chosen voice was refused (a 402, or voice_not_found at any status).
    • Voice markers are checked before the key's status, so a bad voice id never latches the key off.
    • Out-of-credit and withdrawn-model checks come first, as before.
  • CloudVoiceKeyLatch (pure) stores, per voice, the refused key's process-seeded hashValue. Nothing is persisted, so a changed key is tried again.
    • The ElevenLabs key field calls cloudVoiceKeyChanged(_:).
    • Voice or model changes leave the key latch alone.
  • No request once latched: the engine chain treats a latched voice as not ready and moves straight to the next engine. Each fetch…Audio refuses a latched credential before sending, because streamed sentences in flight skip the chain.
  • Voice lists: they go through fetchVoiceList. A latched key fetches no list, and a list refused for the key latches it for speech too.
  • Settings: while the latch holds for the saved key, the voice row says "ElevenLabs didn't accept the key" (or OpenAI/xAI/Gemini).
  • Spoken notice: said once per episode, like out-of-credit. It names where the key lives: Connections › Services and Integrations for ElevenLabs, the saved model for the others.
  • Behaviour change: a 404 voice_not_found used to be unclassified. It now gets the existing "refused the voice request" notice.
  • Known gap: an xAI sign-in reads as ready because there is no synchronous token. It is latched on the token actually sent, and a refreshed token is tried.

Checks

  • Debug on the simulator: 530 passed, 0 failed. That covers CloudVoiceKeyLatchTests 15, CloudVoiceFallbackTests 27, CloudVoiceRequestsTests 18, GeminiCloudVoiceTests 20, MedicalEgressCanaryTests 23, the TTS selector and delivery classes, SettingsHub, BrandNameGuard and every security-regression class.
  • Release: simulator build passed on the final head. The phone was paired but not connected.
  • check-privacy-logging.sh: pass. gitleaks in CI's form: no leaks (a fixture value was renamed). The three Contracts/tests/*.py: pass.
  • Plan KL's Status, its As-built note and the README row are updated.

Owed

  • Device check with a bad ElevenLabs key: expect one 401 and no further requests, the notice once and the Settings row; editing the key clears it.
  • The live ElevenLabs bad-voice-id body has not been seen; the voice_not_found marker is assumed.
  • A catalog sync for the new strings.

🤖 Generated with Claude Code

straff2002 and others added 2 commits November 5, 2026 04:42
A 401 or 403 from a cloud voice (or Gemini's API_KEY_INVALID 400) is now
CloudVoiceRejection.keyNotAccepted, latched per voice and per key
fingerprint (CloudVoiceKeyLatch, process-seeded hashValue, never stored).
A latched voice reads as not ready and is skipped with no request, each
fetch refuses a latched credential before sending, and the voice-list
fetches go through fetchVoiceList, which fetches nothing for a known-bad
key and latches a key the list refuses. A bad voice id (voice_not_found)
or a 402 stays .refused and never latches the key. Settings shows
"ElevenLabs didn't accept the key" (and the other vendors') while the
latch holds; the spoken notice is said once per episode and points the
OpenAI, xAI and Gemini keys at the saved model.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@straff2002
straff2002 merged commit 4ae194a into main Nov 4, 2026
17 of 18 checks passed
@straff2002
straff2002 deleted the feat/kl-p3-voice-key-latch branch November 4, 2026 17:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant