Skip to content

Session 8 — Five Research Questions

Date: 2026-04-12 Triggered by: Stage 0 deploy revealing behavior change in devicectl list devices (timeout → "No devices found.") plus a new 127.0.0.1:62078 fetch failure visible in CDS log.

This document captures the answers to five parallel research questions that ran in session 8. The findings invalidated multiple existing assumptions and produced plan-stage1-rebuild.md.

Q1 — Who issues the HTTP GET to 127.0.0.1:62078?

Answer: Our own inject.

  • inject/iosmux_xpc_proxy.m:557-602iosmux_build_device_properties() calls NSData dataWithContentsOfURL:"http://127.0.0.1:62078/device-info" to assemble the XPC dict installed into OS_remote_device._properties.
  • inject/iosmux_md_proxy.m:30-44fetch_device_properties() does the same thing for the MDRemoteServiceSupport proxy path.
  • cmd/iosmux-relay/main.go:20 — Go relay listens on 127.0.0.1:62078 and serves /device-info from RSD peer_info via tunneld.

Port 62078 is the iOS-side lockdown port; on macOS, nothing in Apple's stack listens there. The "Connection refused" we see means the Go relay is not running on the macOS VM at the moment CDS is being injected.

The downstream consequence chain:

fetch fails (ECONNREFUSED)
  → property dict has only LocationID=0
  → installed into OS_remote_device._properties
  → MDRemoteServiceDeviceLoadBaseProperties reads ProductType/UDID/etc
  → all missing
  → "Failed to load remote device properties"
  → "Failed to allocate RSD device" (AMD error -402653181)
  → developer mode resolution gives Unknown
  → wrapper init still returns a pointer but with half-broken state

Better fix than starting the relay: the 46-key RSD Handshake Properties dict is already parsed and held in process memory as g_rsd_handshake_properties by the time iosmux_build_device_properties runs. Build the consumer dict from the in-memory copy. Removes the relay dependency entirely and fixes a class of startup races.

Q2 — Why did devicectl behavior change without code change to Step 19?

Answer: Step 19 still blocks check-in. The change in surface behavior (timeout → "No devices found." in 2s) is devicectl's own client-side handling, not a real success.

Live trace in coredevice-xpc-protocol.md:283-316 shows the actual message flow:

T+0.004  ProvisioningProvidersListRequest    → "Ignoring" (not action)
T+0.026  DeviceManagerCheckInRequest          → "Handling..." → "Published...Complete"

Both messages traverse Mercury's XPCMessageDispatcher and both hit invoke(anyOf:usingContentsOf:) on entry as the ActionDeclaration prefilter. Our mov al, 1; ret patch makes both look "handled", so the typed dispatcher never runs and the reply is never built.

In session 8, the system log still shows NO Handling DeviceManagerCheckInRequest and NO Published Complete. The "No devices found" is devicectl giving up on its own (not a 15s XPC timeout). The hook is unchanged so the symptom is unchanged at the CDS level — only devicectl's rendering of the failure differs, possibly because the inject's other Stage 0 changes affected how fast the peer connection invalidates.

Decisive grep: presence of Handling DeviceManagerCheckInRequest in CDS log = check-in past prefilter; absence = still blocked. Currently absent.

Q3 — Step 19 (CDS+0xB896): why was it added, do we still need it?

Answer: Added in commit 6501219 to mask a SIGSEGV in PairAction's NULL self handler. The real root cause of that SIGSEGV was the SDR+104 read overflow that we have since fixed in commit 59ca5cd (Stage 2 phase, before session 8). Step 19 is now masking a bug that no longer exists, while blocking check-in.

Variant analysis: - Current (mov al, 1; ret): Always claims handled. Blocks all check-in. Bad. - Passthrough (movabs r10, orig; jmp r10) — preferred: Functionally equivalent to no hook. Restores natural Mercury flow. Acts as a canary: if the original SIGSEGV returns, we learn the SDR fix wasn't sufficient. - Always false (xor al, al; ret): Worse than passthrough. Real ActionDeclaration messages would also fall through to typed handlers that don't recognize them. - Conditional on envelope shape: Requires Mercury wire format capture. Not viable in Stage 1.

Mercury dispatcher's "invoke on every message" claim is itself hypothesis (from a log string + a live trace), not from disasm. Worth verifying directly if S1.B doesn't restore check-in as predicted.

Q4 — developerModeStatus = Unknown: does it matter?

Answer: Probably not for the current target functionality.

Type definition in coredevice-representation.md:398:

enum DeveloperModeStatus {
    case enabled(UInt64)      // payload: timestamp/token
    case disabled
    case unsupported
    case unknown(CoreDeviceError)
}

No runtime gate on Enabled was found in the research: - Devices window does NOT gate on it (device already shows with Unknown) - Pair button gates on pairingState, not developerMode - acquireusageassertion has no documented dependency on it - App install / debug gates on DDI mount, which is a separate Action

The developerModeEnabledIfRequired bit in our preparednessState=0xF should already satisfy preparedness gates.

If we later need to set it explicitly, the setter symbol is: $s19CoreDeviceProtocols0B4InfoV20developerModeStatusAA0bcD0OSgvs (indirect via RDI, payload buffer construction needed for the enabled(UInt64) case).

Deferred to a later stage. Not on the Stage 1 critical path.

Q5 — Why does devicectl list devices return empty despite SDR hook?

Answer: Our hook is on the wrong code path. devicectl list devices does not call serviceDeviceRepresentations(forDeviceIdentifiedBy:).

The actual flow:

client → CDS:  DeviceManagerCheckInRequest { identifier }
CDS → client:  DeviceManagerCheckInCompleteEvent {
                   checkInRequestIdentifier,
                   initialDeviceSnapshots: [DeviceStateSnapshot],
                   serviceFullyInitialized
               }

The device list comes from initialDeviceSnapshots. That array is populated by ClientManager.publish(event:) which reads internal CDS state derived from managedServiceDevices. Our handleDiscoveredSDR publishes events but does NOT add anything to managedServiceDevices (see iosmux_inject.m:982 comment).

serviceDeviceRepresentations(forDeviceIdentifiedBy:) is the PairAction UUID-indexed lookup, completely separate from list-devices. Our hook works for actions, but actions never get to run because: (a) check-in never completes (Step 19 blocking, see Q2/Q3), and (b) even if check-in completed, the device wouldn't be in the snapshot list to be selectable by the user.

Right fix: make the device actually present in managedServiceDevices, so the natural DeviceStateSnapshot publication picks it up. We already call updateIdentifier(devId, sdr, sdm) at iosmux_inject.m:1011 and the inject log confirms it ran — but it apparently doesn't put the SDR into the managed set. Stage S1.C investigates why and fixes the registration path properly.

Possible interception points (in increasing order of "shortcut"): 1. Drive the canonical browser-callback registration path 2. Call the right Swift function with the right ABI 3. Hook ClientManager.publish(event:) to inject snapshots

We choose (1) or (2) as the correct/grounded approach. (3) is a last resort.

Cross-cutting takeaway

Each of the three big findings (Q1, Q3, Q5) is a layer built on top of a wrong assumption. Removing each layer produces a smaller, more correct system rather than a larger, more complex one. This is the unwinding work that plan-stage1-rebuild.md schedules.