Permission identity drift
The launch subject, the canonical permission target, and the effective HID-owning process could disagree. That makes onboarding and debugging confusing for users and developers.
KeyPath used to rely on a runtime path where the thing macOS launched, the thing users were told to trust, and the thing that actually touched the keyboard could drift apart. The split-runtime cutover fixes that by making the user-session host own input capture while a separate privileged bridge owns VirtualHID output.
Input Monitoring is user-session oriented, while pqrs VirtualHID access still crosses a privileged boundary. Treating those as one process made health, permissions, and recovery harder to reason about.
The previous runtime worked often enough to look healthy, but it had three structural problems. They were not just bugs. They were signs that the architecture no longer matched how macOS actually grants permission and isolates privileged I/O.
The launch subject, the canonical permission target, and the effective HID-owning process could disagree. That makes onboarding and debugging confusing for users and developers.
`SMAppService` being enabled only means something is registered. It does not prove the runtime is actually running, responding, and capturing input.
A user-session runtime could still trip over pqrs root-only boundaries because output readiness and event emission were too tightly coupled to the same runtime path.
A single runtime path carried too many responsibilities and too many assumptions.
UI, configuration, diagnostics, and service control.
The thing launchd starts, but not necessarily the long-term runtime identity users reason about.
Captures input, runs remapping logic, checks output readiness, and emits output through the same path.
Required for output, but not aligned with the same trust model as Input Monitoring.
Each component now owns one job and the trust boundaries are explicit.
Still owns PermissionOracle, InstallerEngine, diagnostics, and high-level coordination.
Owns HID capture and runs the Kanata runtime under the stable app-owned identity macOS sees.
Owns the root-scoped path to VirtualHID and exposes handshake, emit, sync, reset, and health status.
Still exists, but no longer drags the input-capture identity across the same boundary.
The split-runtime design is not just “more components.” It is a more accurate map of the operating system. That gives KeyPath better correctness, better upgrade behavior, and a cleaner story for novice contributors.
The process that opens HID devices is now the process whose identity matters. The permission contract is no longer hidden behind a launcher handoff.
If input capture breaks, that is different from the output bridge breaking. Each problem has its own health surface, logs, and recovery path.
Installer and repair flows can verify “running and actually ready” instead of assuming a registered daemon means everything is fine.
These are the deeper engineering ideas behind the cutover. They are the difference between a feature that “works on my machine” and a system that stays understandable after months of evolution.
macOS does not treat input permission, launch registration, and privileged output as the same thing. The software should not pretend they are.
`PermissionOracle` still owns permission truth. `InstallerEngine` still owns installation and repair. The cutover refines runtime responsibilities without scattering decision-making.
The output bridge only needs a versioned contract for handshake, emit, modifier sync, reset, and health. A small protocol is easier to secure, test, and evolve.
The split runtime changes hosting and transport on macOS. It does not replace Kanata’s parsing or remapping core.
KeyPath.app remains the place where users see diagnostics, permissions guidance, and installation state.
The service lifecycle rules remain: registration metadata is not liveness, and success must be postcondition-verified.
This page summarizes the split-runtime cutover described in PR #225 and the supporting design docs around the macOS runtime identity change, the bridge-host spike, and the Kanata backend seam.