feat(apple): serve the Simulator AX bridge from the XCTest runner

Merged
TH
1d ago
+3428-713

Summary

Limrun iOS snapshots read the same Simulator AX bridge as a local Simulator. Limrun cannot start the guest bridge process, so the XCTest runner compiles the shared apple/snapshot-bridge reader and serves snapshotBridge off its XCTest main thread. Both channels share one route: generation circuit, surface-absence proof (runner foreground read, then the reader's own host check), and one typed XCTest fallback. snapshot-tree-changed (a list scrolling between two reads) costs one capture, not the generation. A refusal-code fixture and guard keep Objective-C, Swift and TypeScript in step. The Limrun runner must be built with Xcode 26. ADR 0004 is amended.

84 files against main (which now has #4224's runner switch). Follow-ups: #4205, #4241.

Validation

Head da8e78ee7c, base main.

  • pnpm check:affected --run --head da8e78ee7c: passed, exactCommit=true (run-dbxS07).
  • Live at this head, Limrun iOS 26.4 with an Xcode 26.2 runner: bridge captures, both web sheets read by XCTest, appstate before and after the runner starts (evidence).
  • Earlier live runs: Bluesky on Limrun and a local Simulator bridge-served (p50 0.64 s / 0.18 s), no runner restart or circuit retirement (evidence); snapshot-tree-changed → XCTest → bridge on one generation on Limrun (evidence); release-shaped runner, Xcode 26.6 (evidence).

Risks: the abandoned-read path has unit evidence only (#4241).