ADR-0009 — iosmux is a bridge that synthesises an iOS device; native iOS Pair-Setup against the real device is out of scope¶
Status¶
accepted
(This ADR was first filed in commit 74363ac and immediately removed
in commit 404b9e0 because the original draft only restated facts
already in connection.md and stage2.md. The present version is
filed because the underlying architectural commitment IS still
load-bearing and IS still uncodified — but only after iter-12,
iter-16, and iter-18 produced the empirical evidence that warrants
an ADR. The value this ADR adds over the existing connection.md /
stage2.md text is the explicit rejection of three concrete
candidate paths that were repeatedly re-litigated across sessions.)
Context¶
By session 11 (2026-04-27) the project had cycled through three
candidate paths for getting the synthetic device's pairingState
from unpaired to paired. Each was tried and each is grounded in
specific empirical evidence in docs/research/protocol/:
- Option A — force-write
pairingState=.pairedon the syntheticDeviceInfofrom inside the inject. Removed in Stage S2.A (commit18331ca) as "lying about device state". Re-proposed multiple times since; each re-proposal disregarded the S2.A rationale documented ininject/iosmux_inject.maround thestate_setterblock. The user has explicitly rejected this path in session 11. - Option B — let CoreDeviceService run a real RemoteXPC pair-setup
ceremony against the synthetic device. This presupposes that
CDS will open an
nw_connectionfor pair-flow that we can serve from a local backend. iter-09 (D.6.3) and iter-12 (D.6.5-control) empirically showed CDS opens zero SYNs to any service port in SPIKE mode regardless of trigger; the trust gate fires upstream of the connection. iter-13/14/15 falsified every wire-level Handshake Properties identity-field bisection (UDID alone, then UDID+Serial+ECID together). iter-16 localised the gate toremotepairingd's in-memoryCUPairedPeerregistry matched against live_remotepairing._tcp.localBonjour adverts plus an Ed25519 challenge-response anchored in the iPhone's secure enclave. iter-17 attempted to plant a fake Bonjour advert and was abandoned mid-flight (wrong TXT keys; rotatingauthTagis a keyed hash with non-extractable key material). iter-18 ranxcrun devicectl manage pairend-to-end and the iPhone-side Trust dialog never appeared because CDS bailed before reaching any iOS Pair-Setup endpoint. - Option C — synthetic device owns its own pair-state surface;
iosmux backend serves the lockdown-over-TCP
:50367advertised in our Handshake undercom.apple.mobile.lockdown.remote.trusted, plus a Mercury Codable interceptor for static-success actions (AcquireDeviceUsageAssertion above all). All RSD service connections Xcode actually needs (install / DDI / debug / app-launch) flow through the existingremote_service_create_connected_socketinterpose chain ininject/iosmux_inject.mand onto the iPhone via the pymobiledevice3-tunneld utun. iter-11 (D.6.5) specified the protocol (RSDCheckin/QueryType/GetValue, 3 verbs). iter-18 explicitly named this as "the only remaining way forxcrun devicectl manage pairto succeed against a device routed through this VM" (iter-18-devicectl-pair-falsified/findings.md§"Why this falsifies 'transparent proxy' architecture").
The session-11 research agent's evidence matrix is in
docs/research/scratch/session11-b-vs-c-and-doc-gaps.md (temporary;
not committed long-term). It cites file:line evidence for every
claim above.
Decision¶
iosmux is a bridge: it stands up a synthetic iOS device inside the macOS VM's CoreDevice subsystem and serves every host-side trust / pair-state / lockdown surface for that synthetic device itself. iosmux does not orchestrate native iOS Pair-Setup against the physical iPhone on behalf of the macOS VM, and Xcode is not expected to drive a Pair-Setup ceremony that would touch the device's secure enclave.
The three candidate paths are each given a final disposition:
- Option A is rejected. No
DeviceInfo-field force-writes that bypass the natural code path. Stage S2.A's removal stands. - Option B is rejected. No attempts to make CDS run a native RemoteXPC or RemotePairing pair ceremony against the synthetic device, no Bonjour-advert planting, no authTag derivation, no Ed25519 challenge synthesis. The cryptographic gate is held by the iPhone's secure enclave; iosmux cannot satisfy it from the Mac VM and is not expected to.
- Option C is the path. Phase D.6.6 implementation work
(lockdown handler + Mercury Codable interceptor + existing
service-socket interpose chain) is the canonical forward
direction. See
stage2.mdPhase D.6.6 for the work plan.
Consequences¶
Wanted
- Future sessions stop re-litigating the same three options. Any proposal to re-introduce force-writes, run native Pair-Setup, or plant Bonjour adverts is rejected by reference to this ADR with no further argument required.
- Phase D.6.6's scope is bounded: lockdown 3-verb handler + Mercury Codable interceptor for the small "static-success" action set (Pair, Unpair, AcquireDeviceUsageAssertion, ListUsageAssertions, Connect, Disconnect, Tags, GetTrainName, DarwinNotificationObserve, DarwinNotificationPost, EnableDDIServices, DisableDDIServicesAction, FetchDDIMetadata, UpdateHostDDIs, RemoveHostDDIs) and the existing RSD service-socket interpose chain handles everything else (AppInstall, CreateServiceSocket, TransferFiles, ReceiveFiles, RsyncFiles, ListFiles, FetchDyldSharedCacheFiles, FetchMachoDylibs, FileNodeDetails, plus debug-attach via service-socket open).
- The active
iosmux_inject.dylibremains load-bearing — the patchedCoreDeviceServicehasLC_LOAD_DYLIBon/Library/Developer/CoreDevice/iosmux_inject.dyliband that file's presence is required for CDS to launch (verified in iter-18 Run B: stub disabled the synthetic device entirely anddevicectl list devicesreported "No devices found").
Accepted as cost
- Operations that genuinely require iPhone-side Mac-identity enrolment (first-time provisioning of a new development machine against an iPhone whose secure enclave is the source of truth) remain out of scope. The user has stated repeatedly that the iPhone is read-only with respect to iosmux, so this is not a loss.
- The Mercury Codable Output byte layout for at least
AcquireDeviceUsageAssertionActionDeclaration.Outputand the other static-success actions is currently undocumented; D.6.6 implementation work has to capture this before encoding any reply. Tracked as a documented work item instage2.mdPhase D.6.6. - The 88-key
GetValuelockdown dictionary content lives in a held-local pcap (/home/op/backups/iosmux/pcaps/, gitignored); D.6.6 needs the dict committed as a redacted fixture or re-captured at backend startup against stock-tunneld. Tracked similarly.
Evidence¶
../research/protocol/s2c-self-experiment/iter-12-spike-control/findings.md§"SYN destination table" — zero SYN to any advertised service port in SPIKE underdevicectl device info detailstrigger, confirms CDS bails upstream of Services dict consultation.../research/protocol/s2c-self-experiment/iter-13-hs-props-diff/findings.md— Properties bytes-equal except in 5 zeroed identity fields and 5 iOS point-version drift fields; the trust signal is not in the Handshake we control.../research/protocol/s2c-self-experiment/iter-14-udid-patch/findings.md— UDID-only patch falsified single-field hypothesis.../research/protocol/s2c-self-experiment/iter-15-multifield-patch/findings.md— UDID+Serial+ECID multi-field patch falsified the combined wire-level identity hypothesis.../research/protocol/s2c-self-experiment/iter-16-pairstate-survey/findings.md§"What this changes in the D.6.x causal model" — trust gate localised toremotepairingdpeer registry + Bonjour advert + Ed25519 challenge response anchored in iPhone secure enclave.../research/protocol/s2c-self-experiment/iter-18-devicectl-pair-falsified/findings.md§"Why this falsifies 'transparent proxy' architecture" — both inject loadouts (full and stub) fail to drive a native Pair-Setup against the synthetic device.../research/protocol/s2c-self-experiment/iter-11-lockdown-decode/findings.md§"D.6.6 implementation plan (Option A)" — lockdown-over-TCP 3-verb protocol identified; one Go handler unlocks 40 endpoints in the Services dict.../research/protocol/s2b-pair-attempt-log.md§"Ground-truth verification of the tunnel" — pymobiledevice3 Python clients (rsd-info,mounter list --tunnel,developer dvt ls /) all succeed against the same tunnel CDS fails on; the tunnel is a working iOS-services channel even while CDS cannot speak its dialect.../research/coredevice-internals/pair-button-and-cfnetwork.md§"Pair button gating" — Xcode UI gates the Pair button on_shadowUseAssertion.fulfilled, set by a successful localCoreDevice.RemoteDevice.acquireDeviceUsageAssertion(...), which dispatchesAcquireDeviceUsageAssertionActionDeclarationthrough Mercury to CDS — answerable by a synthetic responder inside iosmux without invoking iOS Pair-Setup.
References¶
- ADR-0001 — pymobiledevice3 as the iOS-side protocol source-of-truth.
- ADR-0002 — tunneld inside the VM, which is what makes a VM-side synthetic device feasible.
- ADR-0004 — Option δ as the canonical Stage 2 architecture; this ADR clarifies what \"the shim's responsibilities\" actually cover end-to-end.
- ADR-0005 — Go-only backend; this ADR's Option C work
lands inside
cmd/iosmux-backend/. - ADR-0006 — empirical-only discipline; the iter-13/14/15 falsifications cited above are the discipline applied.
docs/architecture/connection.md"Phase S2.B note" anddocs/plans/stage2.md"Phase B realization" already framed this architecturally; this ADR adds the formal rejection of Options A and B alongside the affirmation of Option C, which was previously implicit.