Closes the three v0.1 Vision audit findings from .planning/audit-2026-05-12/vision-audit.md:
Closes#1 (HIGH) — AgentOrchestrator no longer claims T2 is available when only MissingT2Provider is wired. New hasRealT2Provider init flag (default false) gates the evaluatePostResponse(t2Available:) argument; production wires false until a real T2 sidecar lands (#84). Low-confidence T1 responses now correctly stay on T1 instead of escalating into MissingT2Provider and dying with t2ProviderUnavailable.
Closes#2 (MEDIUM) — FrameAttachController gains releaseAfterRejectedSubmit() (routed through the SOLE discardFrame() emission site, so the D-15 single-emission-site grep gate still holds). AppDelegate.handleChatSubmit / handleChatCancelAndSubmit call it whenever they consumed a pending frame and the orchestrator returned .rejected. JPEG bytes no longer sit in actor memory past the D-15 byte-release window.
Closes#3 (MEDIUM) — CameraCapture.startWatchers() adds an AVCaptureSessionWasInterruptedNotification observer that yields .midSessionRevoked (macOS quirk: the iOS-only InterruptionReason key is API_UNAVAILABLE(macos), so any interruption surfaces the banner). AppDelegate.installVision wires an NSApplication.didBecomeActiveNotification observer that re-checks AVCaptureDevice.authorizationStatus(for: .video) and calls becameAuthorized() on a non-authorized → .authorized flip — giving the previously-orphaned method a production caller.
Test plan
swift test --package-path packages/Vision — 75 tests, 0 failures (2 skipped real-hardware suite)
swift test --package-path packages/AgentCore — 243 tests, 0 failures
Manual verification: revoke camera in System Settings while Jarvis is running → cameraRevoked banner should appear (requires real-hardware/TCC manual run)
Manual verification: deny camera at first launch → grant in System Settings → bring Jarvis to foreground → presence + frame-attach should resume (requires real-hardware/TCC manual run)
New / modified tests
AgentOrchestratorVisionDispatchTests.testLowConfidenceT1StaysOnT1WhenT2ProviderIsMissing — wires real MissingT2Provider + default hasRealT2Provider: false + low-confidence T1; asserts the turn finishes cleanly (one .turnEnd, no .error).
FrameAttachControllerTests.testReleaseAfterRejectedSubmitClearsPendingFrame — confirm-then-release sequence; asserts pending slot cleared and idempotent.
CameraCaptureMidSessionRevokeTests.testInterruptionYieldsMidSessionRevoked — posts a synthetic wasInterruptedNotification; asserts the degradation stream yields .midSessionRevoked.
CameraCaptureTCCTests.testBecameAuthorizedOpensSessionAfterRegrant — seeds .denied, flips probe to .authorized, asserts becameAuthorized() opens the session.
Notes for review
The audit's F-V3 fix originally suggested classifying interruption reasons; macOS doesn't ship the reason key (AVCaptureSessionInterruptionReasonKey is API_UNAVAILABLE(macos) per AVFoundation headers). Implementation diverges: on macOS any interruption yields .midSessionRevoked. Commit body documents the quirk.
F-V1's hasRealT2Provider flag is plumbed through AgentOrchestrator.init and defaulted false, so existing test sites and production both stay on T1 by default. Flip to true when a real T2 provider replaces MissingT2Provider (issue #84).
## Summary
Closes the three v0.1 Vision audit findings from `.planning/audit-2026-05-12/vision-audit.md`:
- **Closes #1 (HIGH)** — `AgentOrchestrator` no longer claims T2 is available when only `MissingT2Provider` is wired. New `hasRealT2Provider` init flag (default `false`) gates the `evaluatePostResponse(t2Available:)` argument; production wires `false` until a real T2 sidecar lands (#84). Low-confidence T1 responses now correctly stay on T1 instead of escalating into `MissingT2Provider` and dying with `t2ProviderUnavailable`.
- **Closes #2 (MEDIUM)** — `FrameAttachController` gains `releaseAfterRejectedSubmit()` (routed through the SOLE `discardFrame()` emission site, so the D-15 single-emission-site grep gate still holds). `AppDelegate.handleChatSubmit` / `handleChatCancelAndSubmit` call it whenever they consumed a pending frame and the orchestrator returned `.rejected`. JPEG bytes no longer sit in actor memory past the D-15 byte-release window.
- **Closes #3 (MEDIUM)** — `CameraCapture.startWatchers()` adds an `AVCaptureSessionWasInterruptedNotification` observer that yields `.midSessionRevoked` (macOS quirk: the iOS-only `InterruptionReason` key is `API_UNAVAILABLE(macos)`, so any interruption surfaces the banner). `AppDelegate.installVision` wires an `NSApplication.didBecomeActiveNotification` observer that re-checks `AVCaptureDevice.authorizationStatus(for: .video)` and calls `becameAuthorized()` on a `non-authorized → .authorized` flip — giving the previously-orphaned method a production caller.
## Test plan
- [x] `swift test --package-path packages/Vision` — 75 tests, 0 failures (2 skipped real-hardware suite)
- [x] `swift test --package-path packages/AgentCore` — 243 tests, 0 failures
- [x] `bash scripts/check-app-builds.sh` — App target compiles cleanly
- [x] All 18 boundary gate scripts — green
- [ ] Manual verification: revoke camera in System Settings while Jarvis is running → `cameraRevoked` banner should appear (requires real-hardware/TCC manual run)
- [ ] Manual verification: deny camera at first launch → grant in System Settings → bring Jarvis to foreground → presence + frame-attach should resume (requires real-hardware/TCC manual run)
## New / modified tests
- `AgentOrchestratorVisionDispatchTests.testLowConfidenceT1StaysOnT1WhenT2ProviderIsMissing` — wires real `MissingT2Provider` + default `hasRealT2Provider: false` + low-confidence T1; asserts the turn finishes cleanly (one `.turnEnd`, no `.error`).
- `FrameAttachControllerTests.testReleaseAfterRejectedSubmitClearsPendingFrame` — confirm-then-release sequence; asserts pending slot cleared and idempotent.
- `CameraCaptureMidSessionRevokeTests.testInterruptionYieldsMidSessionRevoked` — posts a synthetic `wasInterruptedNotification`; asserts the degradation stream yields `.midSessionRevoked`.
- `CameraCaptureTCCTests.testBecameAuthorizedOpensSessionAfterRegrant` — seeds `.denied`, flips probe to `.authorized`, asserts `becameAuthorized()` opens the session.
## Notes for review
- The audit's F-V3 fix originally suggested classifying interruption reasons; macOS doesn't ship the reason key (`AVCaptureSessionInterruptionReasonKey` is `API_UNAVAILABLE(macos)` per AVFoundation headers). Implementation diverges: on macOS any interruption yields `.midSessionRevoked`. Commit body documents the quirk.
- F-V1's `hasRealT2Provider` flag is plumbed through `AgentOrchestrator.init` and defaulted `false`, so existing test sites and production both stay on T1 by default. Flip to `true` when a real T2 provider replaces `MissingT2Provider` (issue #84).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
Closes the three v0.1 Vision audit findings from
.planning/audit-2026-05-12/vision-audit.md:AgentOrchestratorno longer claims T2 is available when onlyMissingT2Provideris wired. NewhasRealT2Providerinit flag (defaultfalse) gates theevaluatePostResponse(t2Available:)argument; production wiresfalseuntil a real T2 sidecar lands (#84). Low-confidence T1 responses now correctly stay on T1 instead of escalating intoMissingT2Providerand dying witht2ProviderUnavailable.FrameAttachControllergainsreleaseAfterRejectedSubmit()(routed through the SOLEdiscardFrame()emission site, so the D-15 single-emission-site grep gate still holds).AppDelegate.handleChatSubmit/handleChatCancelAndSubmitcall it whenever they consumed a pending frame and the orchestrator returned.rejected. JPEG bytes no longer sit in actor memory past the D-15 byte-release window.CameraCapture.startWatchers()adds anAVCaptureSessionWasInterruptedNotificationobserver that yields.midSessionRevoked(macOS quirk: the iOS-onlyInterruptionReasonkey isAPI_UNAVAILABLE(macos), so any interruption surfaces the banner).AppDelegate.installVisionwires anNSApplication.didBecomeActiveNotificationobserver that re-checksAVCaptureDevice.authorizationStatus(for: .video)and callsbecameAuthorized()on anon-authorized → .authorizedflip — giving the previously-orphaned method a production caller.Test plan
swift test --package-path packages/Vision— 75 tests, 0 failures (2 skipped real-hardware suite)swift test --package-path packages/AgentCore— 243 tests, 0 failuresbash scripts/check-app-builds.sh— App target compiles cleanlycameraRevokedbanner should appear (requires real-hardware/TCC manual run)New / modified tests
AgentOrchestratorVisionDispatchTests.testLowConfidenceT1StaysOnT1WhenT2ProviderIsMissing— wires realMissingT2Provider+ defaulthasRealT2Provider: false+ low-confidence T1; asserts the turn finishes cleanly (one.turnEnd, no.error).FrameAttachControllerTests.testReleaseAfterRejectedSubmitClearsPendingFrame— confirm-then-release sequence; asserts pending slot cleared and idempotent.CameraCaptureMidSessionRevokeTests.testInterruptionYieldsMidSessionRevoked— posts a syntheticwasInterruptedNotification; asserts the degradation stream yields.midSessionRevoked.CameraCaptureTCCTests.testBecameAuthorizedOpensSessionAfterRegrant— seeds.denied, flips probe to.authorized, assertsbecameAuthorized()opens the session.Notes for review
AVCaptureSessionInterruptionReasonKeyisAPI_UNAVAILABLE(macos)per AVFoundation headers). Implementation diverges: on macOS any interruption yields.midSessionRevoked. Commit body documents the quirk.hasRealT2Providerflag is plumbed throughAgentOrchestrator.initand defaultedfalse, so existing test sites and production both stay on T1 by default. Flip totruewhen a real T2 provider replacesMissingT2Provider(issue #84).🤖 Generated with Claude Code