Skip to content

Phase D.6.6-impl — Research Questions

Status: hypothesis — open empirical questions

These are open questions that Phase D.6.6-impl deploy + verify cycles will answer. Each row lists where the question arose, the empirical test that resolves it, and priority.

Per ADR-0006 each row stays in hypothesis status until an empirical artifact resolves it. When resolved, update the Status column with the citation, and add a Resolution log entry below.

Why this doc exists

The session-11 walkthrough of notes/session11-b-vs-c-and-doc-gaps.md (2026-04-27) surfaced a set of architectural predictions that read as facts in compressed form but are actually unverified hypotheses. Most already live in stage2.md Phase D.6.6-impl section. This document gives each question a stable ID, traceable origin, and explicit empirical test plan, so that future sessions do not re-derive the open list or silently promote predictions to established facts.

The questions are scoped to Phase D.6.6-impl. The architectural commitments in ADR-0009 §Decision (Option C, no force-writes, no Bonjour planting, no native iOS Pair-Setup) are out of scope for this doc — they are immutable decisions, not open questions. See "Out of scope" section below.

Open questions

ID Question Source Empirical test Priority Status
Q-D66-1 Sub-task 1 (lockdown handler at [::1]:50367) alone — sufficient to make CDS open SYN to that port? §8, §11-Q1 of session11 walkthrough; iter-12 shows CDS bails upstream of Services dict in SPIKE wide-pcap during devicectl device info details after sub-task 1 deploy; observe whether new SYN to :50367 appears blocking for sub-task 3 sequencing resolved (NO — alone insufficient) — see Resolution log
Q-D66-2 Per-action "static-success" Codable Output bytes — AcquireDeviceUsageAssertion, Pair, Unpair, Connect, Disconnect, plus the rest of the static-success set §8, §11-Q2; pair-button-and-cfnetwork.md "Internal fields UNKNOWN" sub-task 2: Mercury logging interpose (xpc_connection_send_message family + xpc_connection_set_event_handler) against the local CDS+device pair on havoc; capture redacted bytes; commit fixtures under internal/backend/fixtures/mercury/ blocking for sub-task 3 resolved (PairAction) — full Codable schema for PairActionDeclaration.Output = DeviceConnectionChangeResult empirically derived through 17 iterations on havoc (commits b808eae49c8f1e). See gambit-pair-action-schema.md for the complete byte-level spec. Other actions in the allow-list are open — they may share DeviceConnectionChangeResult (Connect/Disconnect/Unpair semantically alike) or have different Output types whose decoder errors will name them when their UI triggers fire.
Q-D66-3 Forward-through-tunnel actions (appinstall, createservicesocket, transferfiles, receivefiles, rsyncfiles, listfiles, fetchdyldsharedcachefiles, fetchmachodylibs, debug attach) work end-to-end through existing interpose chain once sub-task 1+3 land? §8 action verdict table; §9 Recommendation step 4 trigger each via xcrun devicectl device install … / … process launch … / xcrun devicectl debug list …; observe interpose chain and relay logs; one row of §8 table per successful action blocking for Phase D.7 productisation open
Q-D66-4 11 candidate DeviceInfo fields (transportType, platform, deviceType, reality, osVersion, osBuild, udid, authenticationType, developerModeStatus, bootState, isMobileDeviceOnly) — runtime-affect Xcode behavior? §10 gap 4; pair-button-and-cfnetwork.md explicitly UNVERIFIED set each one at a time, observe Xcode Devices window + devicectl device info details JSON nice-to-have open
Q-D66-5 pairingState=unpaired in DeviceInfo struct — blocks install/DDI/debug action set when _shadowUseAssertion.fulfilled is true Xcode-side? §8 conclusion; §11-Q3 trigger install / DDI mount / debug attach after sub-task 3 makes the Pair button vanish; observe whether action set is reachable despite the field staying unpaired blocking for Phase D.7 open
Q-D66-6 DeviceInfo.areDeveloperDiskImageServicesAvailable and preparednessState post-S2.A — derived dynamically by CDS, or need explicit set via the inject's setter chain? §11-Q4 smoke test post sub-task 1+3; observe devicectl device info details JSON across action invocation states nice-to-have open
Q-D66-7 S1.B passthrough trampoline at CDS+0xB896 — stays valid across macOS point updates? §11-Q5; code-audit-findings.md C1/C2 hardcoded-offset risk class re-verify the rel32 displacement at (p_b896+1) matches the expected original target on each macOS bump; current inject logs Original invoke target: %p (rel32=0x%x) per launch nice-to-have, ongoing open
Q-D66-8 Is there an Xcode-side caching layer that remembers "pair-eligible" decisions across sessions? §11-Q6 clean-state device pairing test under controlled apparatus reset (deferred — collides with the no-reboot memory rule and the warm-window constraint) nice-to-have, deferred open
Q-D66-9 Phase D.6.6-impl — requires a new pymobiledevice3 patch beyond 0001-iosmux-spike-tunneld-endpoint-override.patch? §10 gap 7; ADR-0003 patch-overlay discipline sub-task 1 implementation walk-through; if a new patch is needed, document it with a Go-Rewrite-Note per ADR-0003 nice-to-have open
Q-D66-10 Action identifier ↔ Mercury mangledTypeName mapping table §10 gap 8; coredevice-xpc.md vs action-interception-full-picture.md derive from the sub-task 2 capture; add an explicit table to coredevice-xpc.md or a new doc nice-to-have open
Q-D66-11 Phase D.6.6 apparatus warm-window flow runbook — precise procedure §9 step 5 document after the first successful sub-task 1 deploy under docs/runbooks/; constraints: random 5-digit ports, no /tmp for backups, no inline shell complexity nice-to-have open
Q-D66-12 internal/backend.Run pre-bind→close→rebind race window on the greeting listener (current shape in cmd/iosmux-backend/cmd/serve.go) §5 review of agent output during session 11 walkthrough — serve.go opens the greeting listener up-front to fail fast on a bad addr, then closes it so backend.Run can re-bind the same address; a stranger could theoretically steal the port in the gap stress-test concurrent port-stealer against the deployed backend; if observed in operation, refactor internal/backend.Run to accept a pre-bound net.Listener so the bind happens exactly once nice-to-have open
Q-D66-13 iPhone Services-dict port allocation — are entries (specifically com.apple.mobile.lockdown.remote.trusted) at stable well-known ports, or per-session-allocated? session 13 deploy attempt 1 (2026-04-28) — backend hardcoded iPhoneLockdownPort=50367 from iter-10 capture but current apparatus refused TCP at that port. Sub-task 1 cannot ship a stable build with a captured constant if the constant rotates. (a) empirical: query pymobiledevice3 remote rsd-info against current apparatus + compare to iter-10 capture for the same lockdown.remote.trusted service; (b) implementation: native Go RSD-greeting client that fetches the Services dict from [tunnel-address]:tunnel-port and discovers the port at backend startup blocking for sub-task 1 deploy resolved — see Resolution log
Q-D66-14 Even with PairAction returning success and DeviceConnectionChangeResult fully accepted by Apple's Codable decoder, Xcode UI shows a spinner ("Xcode has already started pairing… Follow the instructions on iPhone (iosmux) to complete pairing.") instead of transitioning to paired-and-connected. Apple's own log: Pairing attempt completed with error nil AND IMMEDIATELY after DVTDeviceOperation: skipping implicit preparation for device being ignored with reason: deviceIsUnpaired. So Xcode UI binds to a separate signal independent of PairAction Output. What is the binding? session 14 iter 17 milestone (2026-04-28) — first time UI ever transitioned out of "Pair button shown" state; new spinner state is unprecedented; pair-button-and-cfnetwork.md §"Two gates, not one" describes _shadowUseAssertion.fulfilled as the rendering gate for the Pair button itself, but the spinner gate is even further along. Path B (planned): emit a synthesised RemoteDeviceStateUpdatedEvent Mercury broadcast from the inject side after the PairAction reply, so Xcode's KVO observers see a state-changed broadcast (not just an action reply containing a snapshot). Alternative: dispatch AcquireDeviceUsageAssertion ourselves via the same GAMBIT route (already in allow-list), since _shadowUseAssertion.fulfilled is set by it succeeding. Diagnostic: probe DVTCoreDeviceCore disasm (Xcode-side) for the spinner state machine binding. blocking for Phase D.7 resolved — see Resolution log
Q-D66-15 After Phase 1 (per-action GAMBIT dispatch + AFM endpoint side-channel) deployed, Xcode UI still shows "Disconnected" label and "Failed to acquire assertion" error in the device row. Apple unified log shows Failed to acquire usage assertion ... CoreDeviceError(errorCode: 3) 9-13 ms after our action reply, independent of AFM presence/shape/identifier (D20 falsified all three byte-level hypotheses). Reclassified blocking 2026-04-29 (session 18 D19): every read-only devicectl device info* command fans out through com.apple.coredevice.action.acquireusageassertion and fails on this gate. Reopened 2026-05-01: cosmetic-accept REVERSED per user directive — full UI functionality is the architectural goal. session 16-19 (2026-04-29..30) — D20 falsified byte-level hypotheses. D19 falsified Q-D66-5. D21 (lldb dynamic) confirmed V2 verdict (post-reply side-channel teardown). D22 (static disasm) localised V2 to cache eviction by ActionConnectionCache.removeCachedXPCConnection (CD+0xdbe0). D23 (P-1a deploy) NOP-evict deployed (commit f591aa1) — XPCError 1001 on action-XPC eliminated; trigger exit code 0 + full device info returned; PARTIAL though, residual errorCode 3 still fires on a NEW path. D24 (post-D23 dynamic re-probe) localised residual to W4: SECOND XPC connection ("side channel peer", distinct from action-XPC + ActionConnectionCache) invalidates → Mercury XPCSideChannel handler delivers XPCError 1001 → CD's [useassertion] subscriber posts to same assertionq dispatcher D21 saw, just at different internal RetPCs (___30eb0+1910, ___324a0+56). Dispatcher is COMMON FUNNEL for AUA failures. See aua-side-channel-mechanism.md §"D24 update". D30 (2026-04-30 evening): AFM peer keep-alive deployed (retain peer in g_afm_retained_peers global NSMutableArray; no cancel, no release) — FAILED, errorCode 3 still fires; XPCError 1001 path eliminated (no peer[2019] 1001 line in unified log) but errorCode 3 has a third uncatalogued input source. Peer lifetime is NOT the gate. D31 (2026-05-01, lldb dynamic probe): input #3 localised — IS peer[1013] (CDS↔devicectl primary side-channel, distinct from peer[2019] AFM peer that D30 retained); same ___30eb0/___3bc80/___324a0 dispatcher as D24 input #2 (current build offsets +0x776 → +0x3d → +0x38). Construction at CoreDeviceUtilities.CoreDeviceError.init(code:userInfo:) (CDU+0xbabf0) called from CoreDevice ___30230 (PC +0x368 inner / +0x3cd outer wrap with NSLocalizedDescription). Race condition empirically confirmed: Run #2 captured SUCCESS (success-resume thread won race), Run #3 captured FAILURE (side-channel observer drain pump won). Race IS deterministically winnable. D32 (2026-05-01, lldb attach to live CDS PID 1013): peer[1013] invalidation source localised — IS NOT in CDS application code. Cancel chain has zero CD/CDU/Mercury/iosmux frames; entirely inside libxpc.dylib responding to a kernel-delivered MACH_NOTIFY_NO_SENDERS notification fired when devicectl drops its last mach send right on the connection's underlying mach port. Trigger lives in devicectl process, not CDS. CDS-side cause-side suppression is empirically impossible. D33 (2026-05-01, lldb attach to launching devicectl): three concrete questions closed before D34 implementation. Q1 — DYLD_INSERT_LIBRARIES injection FEASIBLE (devicectl has library-validation flag but no hardened runtime; ad-hoc-signed probe dylib loaded successfully; no insert_dylib LC_LOAD_DYLIB patching needed for D34). Q2 — Option 1 hook target REFRAMED: peer[] release in devicectl is libxpc-internal Mach-event reaction (passive), not app-code call; the Swift-level hookable target is Mercury.SystemXPCPeerConnection.__deallocating_deinit at Mercury+0x4ea10, BUT this fires AFTER libxpc has already released the connection — Option 1 cannot achieve "prevent release", it can only modify post-release Swift cleanup. Q3 — Option 2 hook target viability CONFIRMED: CoreDeviceError.init(code:userInfo:) (CDU+0xbabf0), xpcError.getter (CDU+0xc2940), and Mercury XPCError getter (Mercury+0x57b40) are all NAMED Swift symbols dlsym-resolvable; drain pump unnamed symbols at CDU+0x55510/+0x54ab0/etc reachable via slide arithmetic. Recommended: Option 2 for D34 (both options are symptom-suppression after empirical reframing of Option 1; Option 2 has named symbols, lower complexity, no refcount/deinit hazards). D34 (next, planned): implementation brief for iosmux_devicectl_inject.dylib — DYLD_INSERT_LIBRARIES injection into devicectl, dlsym-resolved hooks on CoreDeviceError.init(code:userInfo:) + xpcError.getter (Mercury & CDU variants), filter code == 1001 AND AUA-invocation-context-on-stack discriminator, suppress/rewrite the AUA-specific 1001→errorCode 3 path. CDS-side D23 NOP-evict at CD+0xdbe0 stays active (covers a different failure path). blocking for full UI functionality

Resolution log — see archive files

Per-question Resolution log entries have been archived into dedicated history files to keep this main table readable:

  • d66-history-q1-q14.md — Q-D66-1 (lockdown handler reachability, RESOLVED), Q-D66-2 (Mercury Codable Output bytes — RESOLVED for PairAction, other actions OPEN), Q-D66-13 (lockdown port per-session, RESOLVED), Q-D66-14 (Xcode spinner gate — kvoCache_isPaired + monotonic, RESOLVED).
  • d66-history-q15-d21-d33.md — Q-D66-15 D21 V2 verdict + D22 P-1a static disasm + D23 P-1a deploy
  • D24 W4 common funnel + D25..D29 FORGE invalidation + D30 AFM peer keep-alive failed + D31 peer[1013] race + D32 kernel-driven invalidation + D33 devicectl-injection feasibility.
  • d66-history-q15-d34-d40.md — Q-D66-15 D34/D35a Heisenbug + D36 host-side disasm + D37-C/D/E/F/G focused probes + D37-H baseline + D38 G-4/G-1/G-1' falsifications
  • D39 Xcode-side H#2 deploy + recovery + D40 state-aware retrospective.

Other Q-D66 entries in the table above don't have dedicated Resolution logs because they are either OPEN-pending-empirical-test or covered elsewhere in the docs (e.g. Q-D66-13 cross-references commit b2da3f6 in architecture/connection.md §"Backend self-bootstrap chain").

Out of scope

The following are NOT open questions — they are immutable decisions per ADR-0009 §Decision and must not re-enter as research items:

  • Re-introducing pairingState=.paired force-write (Option A — REJECTED, S2.A removed it as "lying about device state")
  • Native iOS Pair-Setup against the real iPhone (Option B — IMPOSSIBLE, gate held by iPhone secure enclave per iter-16)
  • Planting _remotepairing._tcp.local Bonjour adverts / authTag derivation / Ed25519 challenge synthesis
  • Writing to the iPhone (read-only Bonjour observation only, per repeated user instruction)
  • xcrun devicectl manage pair against the synthetic device (FALSIFIED end-to-end by iter-18)

Any proposal that re-opens any of the above is rejected by reference to ADR-0009 with no further argument required.

See also