Phase 1 REVIEW.md CR-02 (SEC-01): `WizardStageAPIKeyView` kept the raw
Anthropic API key in an `@State String` for the lifetime of the view —
outliving the Keychain write, retained by SwiftUI attribute-graph
diffing, and visible to any memory-dump forensic vector (crash report,
Xcode memory graph, `leaks(1)`).
App/Wizard/WizardStageAPIKeyView.swift:53-101
- `verify()` now captures the key into a single `capturedKey` local and
reassigns `apiKey = ""` immediately, so the @State slot drops its
strong reference before the async validate + keychain.set chain runs.
- Error branches selectively restore `apiKey = capturedKey` only where
the user plausibly wants to retry without retyping (unauthorized /
network / generic / keychain-error); `malformed` keeps apiKey empty.
- `.onDisappear { clearAPIKey() }` hook: zeros the @State on view
teardown (including back-navigation and success-path dismissal),
preventing the plaintext from persisting in the attribute graph after
dismiss.
- `clearAPIKey()` first overwrites with null bytes of the same length
(best-effort — Swift Strings are CoW/immutable so the backing page
can't be zeroed in place), then drops the @State to empty so ARC can
release the CoW storage.
App/Tests/AppTests/WizardStageAPIKeyViewTests.swift (new)
- Source-level grep gates for the three regression points: onDisappear
hook, clearAPIKey helper shape, and verify()'s capture-then-clear
pattern. SwiftUI @State lifecycle isn't observable from XCTest
without ViewInspector, so text-level gates are the practical
regression guard.
Jarvis.xcodeproj/project.pbxproj: regenerated via xcodegen to pick up
the new test file.