Three types. Two pure functions. One loop.
What's wrong with this system right now?
Which page fixes the first issue?
One button, one InstallerEngine call.
SystemSnapshot, the wizard always shows the same page and offers the same fix. No hidden state, no timing dependencies, no async side effects in the decision path.SystemSnapshot and returns a list of WizardIssue values describing what's wrong.enum SystemInspector { static func inspect(snapshot: SystemSnapshot) -> (WizardSystemState, [WizardIssue]) } // Test: let snapshot = SystemSnapshot(permissions: noIM, components: allGood, ...) let (state, issues) = SystemInspector.inspect(snapshot: snapshot) XCTAssertEqual(issues.first?.identifier, .permission(.keyPathInputMonitoring))
WizardPage to show. Priority: helper → conflicts → permissions → components → service → summary.enum WizardRouter { static func route( state: WizardSystemState, issues: [WizardIssue], helperInstalled: Bool, helperNeedsApproval: Bool ) -> WizardPage static func nextPage( after: WizardPage, issues: [WizardIssue], state: WizardSystemState ) -> WizardPage }
@State for the current page, issues, and snapshot. On appear: inspect + route. Fix button: call InstallerEngine, then re-inspect + re-route.struct InstallationWizardView: View { @State private var currentPage: WizardPage = .summary @State private var issues: [WizardIssue] = [] func inspectAndRoute() async { let snapshot = await InstallerEngine().inspectSystem() let (state, issues) = SystemInspector.inspect(snapshot: snapshot) currentPage = WizardRouter.route(state: state, issues: issues, ...) } func fix(_ action: AutoFixAction) async { await InstallerEngine().runSingleAction(action, using: PrivilegeBroker()) await inspectAndRoute() } }
inspectSystem() → inspect() → route() → show pagerunSingleAction() → re-inspect → re-route → next pageWizardRouter.nextPage() — skips resolved pages.summaryInstallerEngine, page views, and infrastructure services — all unchanged.inspectSystem() which gathers the snapshot.SystemSnapshot. No timing-dependent navigation. One-time pages are simple booleans on the view.await InstallerEngine().runSingleAction(action, using: broker)Given a SystemSnapshot, assert the exact issues returned. No mocks, no async, no setup.
Given state and issues, assert the page. Test every priority transition.
Full inspect → route → fix → re-inspect cycle. Verify walk-through to summary.
PermissionOracle → — How the wizard detects which permissions are missing.
Privileged Helper & XPC → — What happens when the user clicks "Install" on the helper page.
Runtime & Service Lifecycle → — How the service starts after the wizard finishes.