Files
Jarvis/App/Wizard
james.maesandClaude Opus 4.7 b92d062f8b fix(tcc): explicit mic + camera permission requests in installVoice/installVision
Live test 2026-05-06 surfaced that the production app never actually triggered
the macOS mic or camera permission dialogs — the wizard's TCC stage was
explainer-only ("(later)") and the "first-use prompt" code that was supposed
to land later was never written. Result: AVAudioEngine opened cleanly, mic
input was silent, no wake-word ever fired, and CameraCapture.open() failed
.cameraDenied without ever asking.

Fix: explicit AVCaptureDevice.requestAccess(for:) calls at the first-use
boundary of each subsystem.

* App/AppDelegate.swift installVoice — checks mic auth status; on
  .notDetermined awaits requestAccess(for: .audio) BEFORE
  graphOwner.open(); on .denied/.restricted logs and returns dormant. Voice
  loop only proceeds with confirmed mic grant.

* App/AppDelegate.swift installVision — same pattern for camera. Checks
  status; .notDetermined → requestAccess(for: .video); .denied/.restricted
  surfaces the existing degradationStream banner via cameraDegradationTask.

* App/Wizard/WizardStageTCCView.swift — wizard rows updated from
  "Microphone (later)" / "Camera (later)" to "(auto-prompts shortly)" with
  body text matching reality: prompts fire automatically after the wizard,
  when the matching subsystem starts. The previous "later" copy implied
  indefinite deferral when actually the prompts fire ~5s after wizard
  completion.

Live verification:
- tccutil reset Microphone com.koftwentytwo.jarvis
- Launch → wizard appears → click through
- tccd log: "AUTHREQ_PROMPTING ... kTCCServiceMicrophone" — system
  dialog displayed
- After click-Allow: AudioGraph: probed format sampleRate=48000 channels=4,
  AudioGraphOwner: opened with variant <private>, WakeWordDAG: started
  ring-feed loop, VoiceController: starting

Voice 87/87 pass.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 11:26:44 -05:00
..