ADR-0005 — Phase D backend is Go only; no new production Python¶
Status¶
accepted
Context¶
ADR-0004 committed Stage 2 to
Option δ: a local shim backend that CoreDeviceService's pair-flow
nw_connection is redirected to. That shim has to speak the exact
RemoteXPC protocol CDS expects on the client side, and drive
pymobiledevice3 on the server side to translate between CDS's view
and the real iPhone over the tunnel.
When Phase C of Stage 2 finished and the shim became a concrete
implementation task, the first technically plausible path was to
reuse pymobiledevice3's own Python h2 / RemoteXPC codecs on the
shim side and embed the shim as another long-lived Python process.
That would have been the shortest path from "we know the protocol"
to "we have a working shim".
The reasons not to do that:
- iosmux is a Go project. CLAUDE.md opens with
**Language:** Go 1.26+. Every production component except thepymobiledevice3 remote tunneldsubprocess is Go. Introducing a second long-lived Python component doubles the Python surface we will later have to remove. - Removal debt compounds. ADR-0003's pymobiledevice3 patch overlay exists specifically to keep Python as a temporary dependency. A new production Python component works against that plan.
- Deployment cost. Every new Python dependency means a new piece of the embedded virtualenv, a new surface for version drift, a new thing the installer has to idempotently set up.
- The protocol is not complex enough to warrant Python. Phase
C's self-experiment confirmed the CDS-facing transport is plain
prior-knowledge HTTP/2 cleartext with RemoteXPC DATA frames.
golang.org/x/net/http2.Framerspeaks h2 directly, and porting pymobiledevice3'sXpcWrapper/XpcPayloadcodec to Go is a few hundred lines of mechanical translation with pymobiledevice3 as the reference implementation.
Decision¶
All new Phase D backend code is Go. The shim lives under
cmd/iosmux-backend/ and uses:
golang.org/x/net/http2for h2c (theFramerinterface, because RemoteXPC does not fitnet/http.Handlersemantics)- A hand-written
XpcWrapper/XpcPayloadcodec ported from pymobiledevice3'sremote/xpc_message.pyas reference howett.net/plistfor bplist decoding (extended if bplist16 is not already covered)
Python is confined to the existing pymobiledevice3 remote
tunneld subprocess, talked to over its HTTP API on loopback
exactly as today. No new long-lived Python processes are
introduced by Stage 2 or Phase D.
Consequences¶
Wanted:
- Production deployment stays as close to "one Go binary plus the existing tunneld subprocess" as possible.
- Every new protocol concern goes through Go's type system and test tooling, which is what the rest of the codebase already uses.
- The long-term goal of removing Python entirely becomes tractable: the only Python process is tunneld, and the patch overlay on pymobiledevice3 is explicitly scoped to be deleted.
Accepted as cost:
- Porting pymobiledevice3's
XpcWrapper/XpcPayload/ bplist16 logic to Go is work we could have avoided by reusing the Python implementation. We accept this cost because it is one-time and produces a testable Go library, whereas the alternative is permanent. - If pymobiledevice3 fixes a protocol bug upstream, we do not get it automatically in the shim — we have to re-port the fix. We mitigate this by keeping the Go codec's structure close to the Python original and citing the source file + line range in every translated function.
Evidence¶
CLAUDE.md→**Language:** Go 1.26+CLAUDE.md→ "Key Decisions: go-ios REJECTED ... → pymobiledevice3 tunneld as subprocess" (emphasis on "as subprocess", not "as library")research/protocol/s2c-self-experiment/FINDINGS.md— confirms the CDS-facing transport is plain h2c, sonet/http2is sufficient.plans/stage2.md→ "Phase D — Principle: new code is Go, Python stays where it already is" section.