|
1 | | -# ADR 0009: Apple Platform Consolidation (AppleOS leaf axis) |
| 1 | +# ADR 0009: Apple platform consolidation |
2 | 2 |
|
3 | 3 | ## Status |
4 | 4 |
|
5 | | -Accepted |
6 | | - |
7 | | -Completed process history is in git history. |
8 | | - |
9 | | -> **Amended by [ADR 0019](0019-request-bound-platform-runtime.md).** The Apple family and explicit `AppleOS` leaf axis remain accepted. |
10 | | -> The lazy platform-module registry and private `@agent-device/platform-*` packages supersede the shallow `PlatformPlugin` shape and root `src/platforms/**` location. |
11 | | -> Each command cuts over to the new execution path in one atomic step. |
12 | | -> Shared physical mechanics move only through ADR 0019's legal injected substrate transition or after their last legacy user. |
13 | | -
|
14 | | -## Rules at a glance |
15 | | - |
16 | | -- Use one `apple` Platform with an `AppleOS` discriminant, not six Platform literals. |
17 | | -- Keep the OS-agnostic Apple engine in `packages/platform-apple/src/core/` and genuinely per-OS code in `packages/platform-apple/src/os/<os>/` leaves. |
18 | | -- Express per-OS capability differences as data keyed by `AppleOS`. |
19 | | -- Keep macOS as a distinct AppKit leaf. |
20 | | -- Do not flatten the tvOS focus-only interaction contract across OSes. |
21 | | -- Keep watchOS as an unsupported sentinel. Discovery never produces it. |
22 | | -- Internal family ownership must not leak into the public `ios`/`macos` leaf projection. |
23 | | -- As of 2026-08, no public-command coverage is claimed for visionOS or watchOS. Net-new visionOS spatial-input QA is deferred. |
| 5 | +Accepted. [ADR 0019](0019-request-bound-platform-runtime.md) supersedes the original platform-plugin shape and owns the package and request-bound runtime seam. |
| 6 | +Each command cuts over to the new execution path in one atomic step. |
| 7 | +Shared physical mechanics move only through ADR 0019's legal injected substrate transition or after their last legacy user. |
24 | 8 |
|
25 | 9 | ## Context |
26 | 10 |
|
27 | | -Apple support uses an asymmetric model. |
28 | | -Physically, Apple support is spread across an `ios` directory, and that directory is actually the shared Apple engine. |
29 | | -`Platform` has separate `ios` and `macos` literals. |
30 | | -However, tvOS is not a platform: it is `platform: 'ios' + target: 'tv'`. |
31 | | -`resolveApplePlatformName` in `src/kernel/device.ts` reconstructs the OS name late and loses information. |
32 | | -Approximately 697 LOC of macOS code resides in `src/platforms/ios/`. |
33 | | -`src/platforms/macos/devices.ts` is a 19-line stub imported by iOS discovery, which reverses the intended dependency direction. |
34 | | -The Apple interactor in `src/core/interactors/apple.ts` also imports from `platforms/ios`. |
35 | | - |
36 | | -A survey by four investigators found approximately 85% of `platforms/ios` already OS-agnostic: |
37 | | - |
38 | | -- Runner stack. |
39 | | -- Tool-provider. |
40 | | -- Discovery. |
41 | | -- Snapshot. |
42 | | -- Screenshot. |
43 | | -- Perf. |
44 | | -- Debug-symbols. |
45 | | - |
46 | | -The XCTest runner already builds `ios | macos | tvos` from one Xcode project. |
47 | | -Thus, distinguishing or adding an Apple OS costs more than its actual implementation requires. |
48 | | -iPadOS, visionOS, and watchOS have no model. |
| 11 | +Apple support shared an engine while presenting asymmetric iOS, tvOS, and macOS models. |
| 12 | +Inferring the OS from the target lost information, and the structure did not model iPadOS, visionOS, or watchOS. |
| 13 | +The XCTest runner builds iOS, macOS, and tvOS from one project. |
49 | 14 |
|
50 | 15 | ## Decision |
51 | 16 |
|
52 | | -Model Apple OSes with an **`AppleOS` discriminant** (`ios | ipados | tvos | watchos | visionos | macos`) under one `apple` Platform. |
| 17 | +Model Apple OSes with an `AppleOS` discriminant (`ios | ipados | tvos | watchos | visionos | macos`) under one `apple` Platform. |
53 | 18 | Do not use six `Platform` literals. |
54 | | -Consolidate the OS-agnostic Apple engine under `packages/platform-apple/src/core/`. |
55 | | -Keep genuinely per-OS code in `packages/platform-apple/src/os/<os>/` leaves. |
56 | | -The Apple plugin is the first instance of the platform-plugin registry. |
57 | | -The registry and enforcement paths named below are authoritative. |
58 | | -Per-OS capability differences become data keyed by `AppleOS`. |
59 | | - |
60 | | -## Alternatives Considered |
61 | | - |
62 | | -- **Give each Apple OS its own `Platform` literal.** Rejected. |
63 | | - `DeviceTarget` (`mobile | tv | desktop`) already spans platforms. |
64 | | - Android TV uses `target: 'tv'`, so a `tvos` literal would conflict with the form-factor axis. |
65 | | - Approximately 15 `isApplePlatform` sites and approximately 52 `macos` branch sites would need to enumerate six literals. |
66 | | - This would also break the working single-bucket `apple` capability/selector model. |
67 | | -- **Keep the existing structure.** Rejected. |
68 | | - It retains tvOS/macOS asymmetry, mislabeled macOS code, and lossy target→OS inference without a path to iPadOS/visionOS. |
69 | | -- **Exclude macOS from consolidation.** Rejected. |
70 | | - macOS is already entangled. |
71 | | - It builds through the same XCUITest project, and approximately 697 LOC of its code resides in `platforms/ios`. |
72 | | - Exclusion would retain the incorrect label. |
73 | | - A distinct AppKit leaf normalizes macOS without making it the same as the other OSes. |
74 | | - |
75 | | -## Consequences |
| 19 | +Keep the OS-agnostic Apple engine in the Apple platform package's core and genuinely per-OS code in its OS leaves. |
| 20 | +Express per-OS capability differences as data keyed by `AppleOS` in request-bound runtime facts. |
| 21 | +This design composes with the daemon facet and descriptor `platformExecution` declaration in ADR 0008. |
76 | 22 |
|
77 | | -Adding a first-class Apple OS becomes cheap: it needs a leaf module and a runner-profile row. |
78 | | -iOS/iPadOS/tvOS/macOS mostly require moves and renames because the engine never needed to know which Apple OS it drives. |
79 | | -visionOS is scoped net-new work. |
80 | | -XCUITest supports visionOS. |
81 | | -The work includes: |
| 23 | +Keep macOS as a distinct AppKit leaf with its helper binary and menubar and desktop surface model. |
| 24 | +Do not flatten the tvOS focus-only interaction contract, which has no coordinate `tap`, across OSes. |
| 25 | +Keep watchOS as an unsupported sentinel: the Apple interactor, gesture admission, and supported-deployment check reject it, and discovery never produces it. |
| 26 | +XCUITest cannot drive watchOS UI. |
82 | 27 |
|
83 | | -- A profile row. |
84 | | -- A build case. |
85 | | -- `#if os(visionOS)`. |
86 | | -- A wider discovery filter. |
87 | | -- Real spatial-input QA. |
| 28 | +The public wire continues to emit `ios` and `macos` leaves through `PUBLIC_PLATFORMS`. |
| 29 | +Internal family ownership must not leak into that compatibility projection. |
88 | 30 |
|
89 | | -watchOS is an explicit **unsupported sentinel** because XCUITest cannot drive watchOS UI. |
90 | | -macOS remains a distinct AppKit leaf, and its helper binary and menubar/desktop surface model are preserved. |
91 | | -The tvOS focus-only interaction contract, with no coordinate `tap`, must not be flattened across OSes. |
92 | | -Snapshot fidelity is uneven. |
93 | | -The deep-RN AX-server fallback is iOS-simulator-only. |
94 | | -Public leaf output remains a separate compatibility projection. |
| 31 | +## Rejected alternatives |
95 | 32 |
|
96 | | -This design composes with the daemon facet and the descriptor `platformExecution` declaration in ADR 0008. |
97 | | -Per-OS behavior is data in request-bound runtime facts. |
| 33 | +- One `Platform` literal per Apple OS: `DeviceTarget` already spans platforms, and a tvOS literal conflicts with the form-factor axis and the single Apple capability and selector model. |
| 34 | +- Keep the existing structure: it retains asymmetric and mislabeled support and lossy OS inference without a path to iPadOS or visionOS. |
| 35 | +- Exclude macOS: it shares the XCUITest project and was already entangled with the Apple engine. A distinct AppKit leaf preserves its differences. |
98 | 36 |
|
99 | | -Implementation status as of 2026-08: |
| 37 | +## Consequences and open gaps |
100 | 38 |
|
101 | | -- Shipped: |
102 | | - - The internal `Platform` collapse to `apple`. |
103 | | - - Additive `appleOs` groundwork. |
104 | | - - The shared Apple engine under `packages/platform-apple/src/core`. |
105 | | - - macOS leaf files under `packages/platform-apple/src/os/macos`. |
106 | | - - A dedicated tvOS leaf under `packages/platform-apple/src/os/tvos`. |
107 | | - - Direct internal imports to the Apple modules. |
108 | | - - The per-`AppleOS` runtime facts, including `packages/platform-apple/src/gesture-facts.ts`. |
109 | | - The former capability projection was retired after its predicates moved into request-bound facts. |
110 | | - - The watchOS **unsupported sentinel**. |
111 | | - The `AppleOS` type reserves watchOS, and the interactor rejects it. |
112 | | - XCUITest cannot drive watchOS UI. |
113 | | - `isSupportedAppleDeploymentLeaf`, the Apple interactor, and gesture admission reject it. |
114 | | - Discovery never produces it. |
115 | | - - visionOS profile/build/discovery, plus simulator-deployment evidence. |
116 | | -- Decision-only support boundary: |
117 | | - - visionOS discovery and simulator deployment are supported and unit-tested in `packages/platform-apple/src/inventory-classification.test.ts` and `packages/platform-apple/src/deployment/runtime.test.ts`. |
118 | | - - App deployment is also admitted for CoreDevice-backed physical devices. |
119 | | - - XCTest-backed physical deployment is unsupported. |
120 | | - - Push remains simulator-only. |
121 | | - - No public-command coverage is claimed for visionOS or watchOS. |
122 | | - - This ADR records this boundary without a command-coverage manifest for either leaf. |
123 | | -- Retained compatibility: the public wire still emits `ios`/`macos` leaves through `PUBLIC_PLATFORMS`. |
124 | | - Internal family ownership must not leak into that projection. |
125 | | -- Deferred: net-new visionOS spatial-input QA. |
| 39 | +Adding an Apple OS needs a leaf module and a runner-profile row. |
| 40 | +Snapshot fidelity is uneven, and the deep-RN AX-server fallback is iOS-simulator-only. |
126 | 41 |
|
127 | | -This ADR owns the Apple family/leaf decision. |
128 | | -ADR 0019 now owns the staged package/runtime seam. |
129 | | -Net-new visionOS spatial-input QA remains a separate follow-up outside the consolidation. |
| 42 | +visionOS discovery and simulator deployment are supported. |
| 43 | +App deployment is also admitted for CoreDevice-backed physical devices; XCTest-backed physical deployment is unsupported. |
| 44 | +Push remains simulator-only. |
| 45 | +No public-command coverage is claimed for visionOS or watchOS, and this record does not claim a command-coverage manifest for either leaf. |
| 46 | +Net-new visionOS spatial-input QA remains deferred. |
0 commit comments