ADR-0004 — Stage 2 uses Option δ (CDS → local shim), not α/β/γ¶
Status¶
accepted
Context¶
Stage 2 of the project is "make the Pair button in Xcode actually work for a device surfaced by our CoreDeviceService inject". Four options were on the table at the start of Stage 2 planning:
- Option α — let CoreDeviceService open its own
nw_connectiondirectly to the tunnel RSD endpoint thatpymobiledevice3 tunneldadvertises, and rely on the real pair flow completing naturally. - Option β — intercept at the Xcode UI level
(
_shadowUseAssertion/hasConnection) so the Pair button never asks for a pair to begin with. - Option γ — intercept at the AMDevice layer, translating individual AMDevice pair calls into pymobiledevice3 equivalents.
- Option δ — intercept CDS's outgoing
nw_connectionto the tunnel RSD endpoint and route it through a local shim backend that speaks the protocol CDS expects, using pymobiledevice3 on the back side to do the real work.
Stage S2.A removed four DeviceInfo force-writes that had been
bypassing the pair flow in Stage 1 ("lies about device state"). With
the inject honest for the first time, Stage S2.B ran a Pair-button
smoke test and produced a course-correcting finding:
- CDS opens
nw_connectiondirectly to the tunnel RSD endpoint. - The connection receives an immediate TCP RST.
- The tunnel itself is fully functional — pymobiledevice3 Python
clients (
remote rsd-info,mounter list, DVT filesystem listing) all succeed against the same tunnel in parallel. - The impedance mismatch is therefore inside the VM, between Apple's native CDS client and pymobiledevice3's tunneld transport.
This falsified Option α empirically: letting CDS speak to the tunnel naturally is not going to work because the two sides do not speak the same transport. Options β and γ were re-evaluated in light of S2.B and both turned out to be degenerate versions of the real problem (β hides the symptom without solving pair; γ duplicates logic that pymobiledevice3 already has). Option δ — stand up a local shim that CDS trusts and that pymobiledevice3 drives — became the only option that actually solves the root cause.
Decision¶
Stage 2 implements Option δ: CoreDeviceService's outgoing pair-flow connection is interposed, and redirected to a local backend that speaks the exact wire protocol CDS expects. The backend drives pymobiledevice3 internally to translate between CDS's RemoteXPC requests and the pymobiledevice3 tunnel transport.
Options α, β, and γ are explicitly rejected.
Consequences¶
Wanted:
- We own the CDS-facing side of the protocol completely, so we can make the exact bytes CDS wants appear on the wire.
- pymobiledevice3 stays in its working configuration and does not need to be forced to speak a different transport.
- The same interpose pattern already used by our
MDRemoteServiceSupportproperty-query hooks generalizes naturally to the pair flow.
Accepted as cost:
- We have to characterize the full CDS ↔ iPhone RemoteXPC dialog empirically before the shim can be implemented. This is what Phase C of Stage 2 exists to do.
- Every protocol change Apple ships in a future CDS update is potentially our problem. We accept this because the alternative (Option α) is already broken today.
- The shim is a new production component. Its language and implementation constraints are set by ADR-0005.
Evidence¶
research/protocol/s2b-pair-attempt-log.md— Phase B deliverable. Full capture of the Pair-button smoke test that falsified Option α.research/protocol/s2c-self-experiment/FINDINGS.md— Phase C blockers X / Y / Z closed. Shows that Shape B (fd passing from inject to shim) is architecturally viable.plans/stage2.md"Phase B realization" and "Option δ" sections.