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 b808eae … 49c8f1e). 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[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=.pairedforce-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.localBonjour adverts / authTag derivation / Ed25519 challenge synthesis - Writing to the iPhone (read-only Bonjour observation only, per repeated user instruction)
xcrun devicectl manage pairagainst 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¶
- ADR-0009 — iosmux is a bridge, not a proxy
- ADR-0006 — Empirical-only discipline
- stage2.md Phase D.6.6-impl
notes/session11-b-vs-c-and-doc-gaps.md(gitignored, working tree only) — original walkthrough that surfaced these questions