refactor: collapse public Platform ios/macos into apple (#979)#1002
Conversation
Phase 3 d.3: collapse the internal `Platform` union from `ios`/`macos` to a single `apple` platform, with `appleOs` as the sole OS discriminant. Approach (b) NON-BREAKING: the daemon still ACCEPTS the legacy `ios`/`macos` selectors on every read path and still EMITS the leaf `ios`/`macos` strings on every output, so machine consumers see no change. Kernel (src/kernel/device.ts): - PLATFORMS = ['apple','android','linux','web']; add PUBLIC_PLATFORMS (leaf) and PublicPlatform; PLATFORM_SELECTORS keeps legacy `ios`/`macos` as input aliases. - New predicates: isMacOs (appleOs- or legacy-leaf-based), isIosFamily (the post-collapse equivalent of `platform === 'ios'`), publicPlatformString (output projection), deviceFieldsFromPublicPlatform (inverse), isPublicPlatform. - isMobilePlatform and matchesPlatformSelector are now device-aware (appleOs). Discovery now stamps `platform: 'apple'` (+ appleOs); ~125 internal `device.platform === 'ios'|'macos'` branch sites migrated to the predicates, behavior-preserving. Apple plugin owns `['apple']`; platformDescriptors collapse to one `apple` row. Output projection (approach b) emits the leaf via publicPlatformString at: devices / session_list (session-inventory), boot / shutdown / appstate / prepare-ios-runner (session-state, session), the selector/backend platform (selector-runtime/screenshot-runtime/snapshot-runtime/interaction-runtime), proxy device key, request-lock backfill, runtime-set binding, click-button validation, and both `.ad` context-line writers. Contracts/client keep leaf types (PublicPlatform); read paths (parsePlatform, REPLAY_METADATA_PLATFORMS, matchesPlatformSelector) accept `apple` + legacy leaves. Adds a parity test gate (platform-collapse-parity.test.ts). Refs #979 (part of #972).
Size Report
Startup median (7 runs, lower is better):
Top changed chunks:
|
|
CI is blocked on Coverage and Integration Tests. The failing provider-integration assertions look directly tied to the public Platform ios/macos -> apple collapse:
Please make the intended provider boundary explicit: either preserve the leaf platform identity where provider/device lifecycle semantics require it, or update the provider contract/tests with clear compatibility rationale before re-running CI. |
…979) The Platform collapse left two daemon response builders emitting the raw internal `device.platform` ('apple'), which the client normalizer rejects (isPublicPlatform excludes 'apple') — dropping the resolved device from the response: - session-open-surface.ts: `open` result `platform`/device projection. - session-perf.ts: the perf/frames/memory base response builders. Both now go through `publicPlatformString(device)`, so output stays the leaf `ios`/`macos` per approach (b). (The android/non-apple perf branches were already leaf-safe.) Also update macos-desktop provider test: the lifecycle mock observes the INTERNAL DeviceInfo, which is now `platform:'apple'` (+ appleOs:'macos'), so the recorded tag is `prepare:apple:desktop`. Fixes the provider-integration assertions that blocked both the Integration Tests and Coverage CI jobs (both run the provider-integration project). Verified: provider-integration 82/82, coverage passes, tsc/oxlint/oxfmt/layering/ fallow green.
|
Pushed a fix (head
Local: provider-integration 82/82, coverage passes, tsc/oxlint/oxfmt/layering/fallow green. Verifying on CI now; will confirm Integration + Coverage specifically before this is considered ready. |
|
Review finding: The public-output projection misses the nested perf memory support payload. buildBasePerfMemoryResponse() now correctly emits platform via publicPlatformString(session.device), but response.support and artifact.support still come from buildMemorySnapshotSupport() -> buildAppleMemorySnapshotSupport(), which returns platform: device.platform. After this PR Apple sessions have internal device.platform = apple, so perf memory snapshot/unsupported responses can emit support.platform = apple even though the PR approach says machine outputs still emit leaf ios/macos, never apple. Please project the support payload through publicPlatformString(device), or make the nested field explicitly internal and document/test that, and add coverage for Apple perf memory support so this output contract stays pinned. |
The Platform collapse (approach b) projects device.platform through publicPlatformString at emit sites so machine consumers keep seeing the leaf ios/macos and never the internal `apple`. Several nested output fields were missed. Project them and narrow their emitted types to PublicPlatform: - Apple perf memory snapshot support (buildAppleMemorySnapshotSupport) — response.support.platform / artifact.support.platform, plus the sibling sampleAppleFramePerf error data. - Apple xctrace perf capture/result platform surfaced in the perf cpu-profile started/stopped response data. - snapshotDiagnostics.stats.platform (recordSnapshotTiming) surfaced in snapshot/test response data and the slow-snapshot warning string. - doctor target-app evidence.platform + human summary, and doctor target-app-device evidence.booted[].platform. - provider/cloud UNSUPPORTED_OPERATION error.data.platform for cloud Apple devices (reachable via deviceFieldsFromPublicPlatform). Internal 'apple' emissions (selector-matching input, diagnostic emitDiagnostic telemetry, session appLog state, replay .ad flags) are left as-is. Adds focused tests pinning Apple perf memory support to the leaf and a guard asserting no emitted platform field equals 'apple'.
|
Review follow-up: the perf memory support leak is fixed in head 35e5d06, including coverage for iOS simulator, physical iOS, and macOS support payloads. I also spot-checked the follow-up projections for xctrace perf, doctor evidence, snapshot diagnostics, provider error details, and the scoped snapshot script path; I do not see another public apple-platform leak from this delta. Checks are green (21/21). This looks ready for human maintainer judgment. Residual risk: I did not run an additional local simulator/device pass beyond the PR CI/smoke coverage. |
|
Closes #979 (Phase 3 d.3, part of #972). Human-review-only — do not merge without review (public API surface).
What this does
Collapses the internal
Platformunion fromios/macosinto a singleappleplatform, withappleOsas the sole OS discriminant (ADR-0009). Every discovered/persisted Apple device is nowplatform: 'apple'internally.Approach (b): NON-BREAKING
The public contract is unchanged for machine consumers:
ios/macosstrings ANDapple.matchesPlatformSelector(device-aware now):--platform ios= every Apple OS except the macOS host;--platform macos= the macOS host only;--platform apple= all Apple. Byte-identical device sets to before.parsePlatform(client response parser) andREPLAY_METADATA_PLATFORMSacceptappleadditively; replaycontext platform=apple|ios|macosall resolve.--platform/ MCP enum already carryappleviaPLATFORM_SELECTORS.ios/macos), neverapple. A singlepublicPlatformString(device)projection is applied at every emit site:devicesandsession_list(daemon/handlers/session-inventory.ts)boot/shutdown/appstate(daemon/handlers/session-state.ts) andprepare ios-runner(daemon/handlers/session.ts)selector-runtime-backend,screenshot-runtime,snapshot-runtime,interaction-runtime) —AgentDeviceBackendPlatformis nowPublicPlatformconnection-runtime), request-lock backfill (request-lock-policy),runtime setbinding (session-runtime/session-runtime-command), click-button validation (dispatch-interactions/interaction-touch).adcontext-line writers (replay/script.tsheal-write +daemon/session-script-writer.ts)PublicPlatform(client-types,contracts/device,parsing); checked-in.adfixtures untouched.Internal migration
platform: 'apple'(+appleOs);PLATFORMS→['apple','android','linux','web'];platformDescriptorstwo Apple rows → oneapplerow;applePlugin.platforms→['apple'].device.platform === 'ios'|'macos'branch sites migrated toisIosFamily/isMacOs(behavior-preserving;isIosFamilyis the exact post-collapse equivalent of the oldplatform === 'ios').isMobilePlatformandisMacOsare appleOs-aware;isMacOsalso honors a legacy leafplatform: 'macos'for persisted-record back-compat.selectors-*,snapshot-processing,selector-is-predicates) only branches on'android', so itsplatformparams are widened toPlatform | PublicPlatform(no projection needed).Parity gate
src/kernel/__tests__/platform-collapse-parity.test.tspins:--platform appleand--platform iosresolve to the same device;publicPlatformStringemits the pre-collapse leaf for every fixture (incl. iPadOS/tvOS/visionOS →ios, and legacy leaf records);apple/ios/macos;deviceFieldsFromPublicPlatforminverts the projection (round-trip);.adheal-write emits the leaf and round-trips through the reader.Verification (all green)
tsc --noEmit·oxlint . --deny-warnings·oxfmt --check src test· layering guard ·rslib build·vitest run --project unit(2960 tests; the androidfillAndroidcase is a known CPU-contention flake — passes in isolation and on re-run) ·node --test test/integration/installed-package-metro.test.ts·fallow audit --base origin/main(no new findings).Deferred (unchanged by design)
Surfacing
appleOson the publicdevicesshape (still stripped) is left as a deliberate later change.parsePlatform'sapple→iosfallback is forward-compat only (the daemon never emitsappleunder approach b).