Architecture

Privileged Helper & XPC

How KeyPath installs system services without asking for your password every time.

Authorize. Communicate. Install.

The app registers a privileged helper via SMAppService, talks to it over XPC, and the helper writes to system-owned paths on the app's behalf.

Authorize

SMAppService registers the helper. User approves once in System Settings.

APP XPC

Communicate

XPC for privileged operations. Signature-validated, type-safe.

.plist kanata

Install

Helper deploys daemon plist and kanata binary to system paths.

The Complete Picture

Two privilege boundaries: SMAppService for registration, XPC for runtime operations. The app never writes to system-owned paths directly.
USER SPACE ROOT / SYSTEM FACADE InstallerEngine ROUTER PrivilegedOpsRouter ACTOR HelperManager APPLE FRAMEWORK SMAppService register() XPC ROOT PROCESS PrivilegedHelper validates signature com.keypath.kanata.plist /Library/LaunchDaemons/ kanata binary + config /usr/local/bin/ + newsyslog writes writes DAEMON REGISTRATION KanataDaemonManager Blue arrows show the primary operation path. Dashed line marks the privilege boundary.
Core invariant. The privileged helper is the only process that writes to system-owned paths. The main app never runs with elevated privileges. All privileged operations go through XPC to the helper, which validates the caller's code signature before executing.

Five Types, Clear Boundaries

Each type owns one concern. InstallerEngine calls the router, the router picks the transport, HelperManager owns the XPC connection.
HelperManager Actor
Owns the NSXPCConnection to the privileged helper. Manages connection lifecycle, version checks, and routes all XPC calls through HelperProtocol. Wraps SMAppService.daemon() for helper registration via a testable SMAppServiceProtocol seam.
public actor HelperManager {
    var connection: NSXPCConnection?
    static let helperMachServiceName = "com.keypath.helper"

    // Every XPC method maps to HelperProtocol
    func installRequiredRuntimeServices() async throws
    func recoverRequiredRuntimeServices() async throws
    func getHelperVersion() async throws -> String
}
PrivilegedOperationsRouter Router
Decides whether to use the XPC helper (release) or direct sudo (debug). In release mode, tries the helper first and falls back to sudo if the helper is unavailable. Pure routing, no domain logic.
@MainActor public final class PrivilegedOperationsRouter {
    enum OperationMode { case privilegedHelper, directSudo }

    // Release = helper-first, Debug = sudo
    static var operationMode: OperationMode {
        #if DEBUG  .directSudo  #else  .privilegedHelper  #endif
    }
}
KanataDaemonManager Daemon Registration
Manages SMAppService registration for the Kanata LaunchDaemon (com.keypath.kanata). Handles status checking, registration, unregistration, and migration from legacy launchctl-based installs.
@MainActor public class KanataDaemonManager {
    nonisolated static let kanataServiceID = "com.keypath.kanata"
    nonisolated static let kanataPlistName = "com.keypath.kanata.plist"

    func registerDaemon() throws
    func unregisterDaemon() async throws
    func getDaemonStatus() -> SMAppService.Status
}
InstallerEngine Caller / Facade
The single entry point for all install, repair, and uninstall operations. Delegates privileged work to PrivilegedOperationsRouter. The wizard and UI code call this, never the router or helper directly.
SMAppServiceProtocol Test Seam
Protocol wrapper around Apple's SMAppService. Both HelperManager and KanataDaemonManager use an injectable smServiceFactory so tests can simulate .notFound, .requiresApproval, and other states without hitting real ServiceManagement IPC.
protocol SMAppServiceProtocol: Sendable {
    var status: SMAppService.Status { get }
    func register() throws
    func unregister() async throws
}

From First Install to Helper Update

Every privileged operation follows the same path through the router to the helper.
First install
SMAppService.register() → helper binary copied to /Library/PrivilegedHelperTools/
User approval
System Settings → Login Items → user approves com.keypath.helper
XPC established
HelperManager creates NSXPCConnection(machServiceName:) with HelperProtocol interface
Daemon installed
Helper writes com.keypath.kanata.plist to /Library/LaunchDaemons/ and loads it
Version check
getVersion() over XPC — if mismatch, prompt user to reinstall helper (ADR-014)
Update / repair
Re-register via SMAppService → new binary replaces old → app restart for clean state

Design Rules

Five rules that keep the XPC architecture safe and maintainable.
1
HelperProtocol must be identical in both targets
XPC requires the protocol compiled into both app and helper separately. If the files diverge, XPC calls fail with selector-not-found errors. HelperProtocolSyncTests enforces this in CI. (ADR-018)
2
Don't use KanataManager for installation
All install/repair/uninstall goes through InstallerEngine. It owns the orchestration of privileged operations, system inspection, and postcondition enforcement.
3
Don't manually call launchctl
Use InstallerEngine (which uses PrivilegedOperationsRouter). Manual launchctl calls bypass signature validation, error handling, and postcondition checks.
4
SMAppService.status == .enabled means registered, not running
Registration state and runtime liveness are different things. Use ServiceHealthChecker to determine if the daemon is actually running and responding.
5
Don't call SMAppService.status in a hot path
It does synchronous IPC to the ServiceManagement daemon and can block for 10-30+ seconds under load. Cache the result within a single validation cycle.

Cross-References

How the XPC layer connects to the rest of the architecture.