Repository navigation
Apple platform + runner: simplify, shrink, and preserve correctness (tracking) #2803
Description
Apple runner: simplify, shrink, and preserve correctness
This initiative removes unnecessary Apple runner/platform-apple production code while preserving device-proven behavior. Correctness and regression proof come first; abstractions must earn their cost by removing decisions, state or mechanisms. Cleaner file layout alone is not success.
Source baseline: 5712a3552e209c9c853bc1f42cb3e007f1213139, including #2911 isolation and #2961 placeholder propagation. GitHub main 8c89891b215c8504d1881eef06031d1d5f223726 was also checked: its SDK exports do not alter these implementation scopes. Every executor must check drift before editing. Findings here are source-verified; this audit did not rerun simulators or physical devices.
Core success criterion: less production machinery
This is part of #2803's simplify-and-shrink initiative. Before production simplification, name the production mechanism, duplicated decision, state, fallback, forwarding layer, or public/internal interface this change removes. Compare deletion and inlining against extraction. Moving the same state into another file is not a deletion case.
Every PR reports baseline/final SHAs; added, deleted and net production lines; test/fixture/documentation changes separately; and the exact mechanisms removed. Use a rename-aware diff (git diff --numstat -M BASE...HEAD) and account for all affected production paths, including destination packages. Moving tests out of production files, generated output, formatting churn, and moving code elsewhere are not production-code deletion. Preserve readable code; no compressed formatting to hit a number.
Default for simplification work: net production reduction. A required behavior-preserving size split may be line-neutral. Growth needs an explicit, quantified explanation of the correctness or type guarantee it buys, why deletion/inlining cannot achieve it, and what old mechanism is removed; it is not automatically acceptable because ownership looks cleaner. If the only benefit is relocating fields or adding a wrapper, defer the change. Required regression tests and a minimal correctness fix are allowed to grow without inventing unrelated deletion to offset them.
The initiative must report its cumulative production delta and the mechanisms actually eliminated. Separate correctness/type-safety growth from cleanup savings; the cleanup tranche should shrink. A positive overall production delta is not “simplify-and-shrink complete” without an explicit maintainer decision to rescope the goal. Historical removals before this follow-through baseline do not count as new savings. Do not set an arbitrary deletion quota before measurement, and do not delete necessary recovery just to make the total negative.
Architecture constraints
Keep one host session/resource owner and one accounting owner. Separate command exchange only enough to satisfy the owning-module and size rules. Keep the existing main-actor target state; preserve distinct native-work, penalty, warmup and private-AX lifetimes. Shared native-build mechanics should shed snapshot-specific dependencies. Swift/TypeScript presenters retain shared semantic proof. No new actor, retry, cache, or generic service framework is part of this initiative; an actor experiment must independently demonstrate a deletion/coordination benefit before becoming implementation work.
The earlier concrete scheduler and penalty extractions are retained as deferred candidates, not required end-state modules.
Execution order and status
Verified 2026-09-25: #2911 is merged; #2963 remains open. This tracking update does not merge either PR. Complete/rebase overlapping host cleanup onto #2963's exact merged head (or explicitly declare a stack on its published head); do not block independent classification/tests on unrelated host files.
| Order | Work | Gate / success |
|---|---|---|
| 1 | Finish the in-flight deletion in #2963; preserve merged #2911 | Establish the overlapping host baseline; do not redo landed isolation work |
| 2 | #2965 accounting model and correctness fix | Decide command-identity settlement, then smallest fix; minimal required size extraction only |
| 3 | #2972 typed AX; #2974 differential; #2970 native-build seam | Delete redundant classification/plumbing; add decisive regression proof; native-build move needs an actual deletion case |
| 4 | #2967 session/exchange split → #2971 payload types | Required split targets neutral/negative lines; type-safety growth reported as such |
| 5 | #2968 characterization → conditional #2969 | Characterization must establish a real gap or deletable repeated logic before ownership migration |
| Deferred | #2966 scheduler owner; #2973 penalty owner | No implementation until redundant state/coordination can be deleted or a concrete correctness need is shown |
| Measurement track | Outer transport resend; private AX | Establish whether larger paths can be removed; keep or delete based on working control evidence |
Run shared runner Swift work (#2966/#2968/#2969/#2972/#2973, and any overlapping native harness edits) one issue at a time, rebasing after each merge. Disjoint TS/test research may proceed independently; devices and full host gates remain exclusive. Do not infer scheduling readiness from an old paragraph or an unchecked box: child labels and the scope gates above must agree.
Status 2026-10-08: first wave done, remaining children folded here
Done: #2963, #2965, #2967, #2970, #2974, plus #2994 and #3082. Since 9/25 the remaining refactor children have had no activity. None shows a defect, and each one is gated on a deletion case it hasn't established, so they are closed as separate tickets and tracked here. Each closed issue keeps its full contract (deletion-first acceptance, scope gate, evidence, invariants); read it before starting the item.
- Session/exchange payload types: spec in refactor(apple-runner): type outgoing commands by payload family #2971 (order 4)
- Target transitions: characterize first, spec in test(apple-runner): characterize target transitions and cache lifetimes #2968, then the conditional ownership migration, spec in refactor(apple-runner): own target identity and reset transitions #2969 (order 5). Run refactor(apple-runner): own target identity and reset transitions #2969 only if test(apple-runner): characterize target transitions and cache lifetimes #2968 finds a real gap or deletable repeated logic.
- Deferred: bounded main-thread work and occupancy owner, spec in refactor(apple-runner): encapsulate bounded main-thread work and occupancy #2966
- Deferred: XCTest-channel penalty owner, spec in refactor(apple-runner): encapsulate XCTest-channel penalty state #2973
Still open as their own issues: #2972 (typed AX failure classification; a real breach of the typed-reason rule, ready) and #2976 (actor comparison; needs a run-or-drop decision before the deferred items).
Measure before deleting: explicit workstream
These are research deliverables, not authorization to remove fallbacks speculatively. A measured “retain” result is valid and must name the concrete capability/failure it still serves.
- Outer read-only transport resend. Owner: this umbrella,
packages/platform-apple/src/runner/runner-client.ts. Current source distinguishes three transport attempts from eightRUNNER_BUSYattempts; do not treat them as one redundant loop. Trace which lower layer already retries/recycles, prove both paths reachable, and compare transport retries enabled versus a one-attempt experimental control. Record command IDs, native execution count, transport send count, final outcome, elapsed budget and cancellation behavior for initial connection failure, recoverable post-send loss, startup failure, busy/draining and stale-session recovery. Use the existing fake-runner wiring plus simulator/physical lanes relevant to the claimed removal. Deliver a retain/delete/narrow verdict, exact production paths/lines removable and required regression cases. Coordinate A wait timeout during runner start stops the runner, so the retry pays the start again #2894/ios(runner): a 1 s readiness preflight abandons a busy runner command and the next connect stalls behind it, failing waits on cold hosts #2475; do not silently reduce the busy drain window or replay mutations. Temporary diagnostics must be removed or justified as an owning diagnostic. - Runner private-AX tier. Owner: this umbrella, with ADR 0004 and iOS: skip private-AX deep extension on observation-only captures (settle, stabilization, corroboration) #1626 as related existing work. Inventory which consumers still require custom actions, unavailable/disabled host bridge, occupied XCTest recovery, raw/depth semantics, real-device/provider support and differing quality/viewport facts. Prove the proposed substitute actually serves each required case with matching fidelity and budget before any deletion. Include bridge-circuit-open and hostile-screen controls; a success on the healthy simulator path is insufficient. Deliver a capability/evidence matrix, retain/delete/partial-reduction verdict, net implementation delta including any new bridge features, and precise unsupported cases. The earlier ~1.4k-line estimate is historical, not a guaranteed saving; count current reachable code. Do not expand iOS: skip private-AX deep extension on observation-only captures (settle, stabilization, corroboration) #1626's shallow-acquisition task into tier removal.
Curl-through-simctl disposition (2026-10-02): refactor(ios-runner): one response decoder, a session state enum, and a verdict on the curl-through-simctl transport #2662 is closed as not planned for its remaining instrumentation/deletion investigation. Retain the reachable simulator curl fallback and its scoped-set, timeout and cancellation semantics; no measured redundancy or correctness defect currently justifies the campaign. This is a priority decision, not a measured retain/delete result. Reopen only for a concrete transport incident or a measured removal case. The consumer retry-safety contract shipped as typeddetails.dispatchedin feat(errors): disclose whether a failed interaction reached the device #3071/fix(apple-runner): restart without resending a command written then lost #3082/refactor(daemon): apply one dispatch-disclosure rule to returned and thrown failures #3099; no seconddispatchOutcomefield is planned.
Each research result is recorded here or on its named owner with baseline SHA, conditions, artifacts, counterexample coverage, and deletable production scope. Any implementation decision becomes a bounded child contract before coding; a research result alone does not change a device support guarantee.
Before/after scorecard
Every implementation PR records its baseline SHA, final SHA, actual commands/results, and the following applicable measures:
| Dimension | Before | Required after |
|---|---|---|
| Behavioral correctness | Existing successes plus each named gap | Existing suite remains green; bug/proof work includes a demonstrated failing case or planted policy mutation |
| Ownership | Direct state writers and mixed responsibilities listed in child | One authoritative writer/transition seam; no mirrored registry, counters, compatibility facade, or public mutable cache |
| Contract strength | Optional payload bag / prose-based inference / hidden test exclusions | Negative type or classification cases rejected; new differential arms actually execute |
| Change locality | List concrete files needed for one representative policy change | Policy and its tests colocated; callers express intent without reconstructing ordering or reset subsets |
| Code removed | Record production additions/deletions/net delta and old mechanisms | Cleanup shrinks; mandatory splits may be neutral; correctness/type growth explicitly justified; no moved-code savings |
| Code structure | Record touched-module/test line counts | Source and matching tests split together where required; no new >1,000-line files or forwarding-only layers |
| Runtime behavior | Matched fixture outputs, retry counts, transport sends, busy/handoff and cache outcomes | Exact preservation except #2965's accounting correction and #2972's code-only AX classification correction; genuine native AX paths retain their outcome; unchanged deadlines, fallback order and wire omissions |
| Performance | No new measurements were collected by this audit | No claimed speedup; investigate any observed regression with matched device, toolchain, backend and fidelity |
For a suspected timing regression, use scripts/ios-snapshot-benchmark/README.md: at least 10 matched cold/first-interaction samples and 20 warm/relaunch samples, retaining raw results and backend/quality facts. These sample counts support comparison, not a universal latency promise or a new invented percentage threshold. Do not demand device timing for a type-only or pure-classification change.
Fresh worktrees require pnpm install --frozen-lockfile && pnpm build. Follow docs/agents/testing.md, docs/agents/device-verification.md, and each child's focused checks; run pnpm check:affected --run on the final diff. Stage new files before trusting tracked-file layering gates. Do not hide a module split with eager-closure allowlists or bulk Fallow baseline regeneration. Selected native/device lanes are obligations; distinguish local fake-runner proof, simulator proof, and physical-device proof. Mark unavailable device evidence pending.
Existing owners, preservation rules, and exclusions
- Apple runner: make off-main access to main-owned capture state fail the runner gates (RunnerMainOwnedState) #2882 owns static main-actor enforcement. Keep the already-landed
RunnerMainOwnedStateisolation and compile canary; coordinate new owner files with its remaining source guards. - refactor(ios-runner): one response decoder, a session state enum, and a verdict on the curl-through-simctl transport #2662 delivered response decoding and the session-state enum. Its remaining curl investigation is closed as not planned, with the existing fallback retained. Public retry safety now uses the dispatch-disclosure contract from feat(errors): disclose whether a failed interaction reached the device #3071/fix(apple-runner): restart without resending a command written then lost #3082/refactor(daemon): apply one dispatch-disclosure rule to returned and thrown failures #3099.
- Snapshot membership step 5: inventory suppressNode ejections and give them typed dispositions #2754 remains the owner of broader membership attribution/ejection dispositions and capture surveys. The linked differential child owns only its still-missing runner-stage proof.
- Preserve independent launch/retry/recorded-failure traits, shared synthesized tap fallback, reported/derived/missing viewport facts, deadline-exhausted capture disqualification, and physical-iOS relaunch retention.
- Keep native main-thread occupancy distinct from host command completion. Terminal journal status is not proof that abandoned native work has drained.
- Keep target transition reset subsets distinct until characterization proves otherwise. In particular process refresh rearms warmup; external relaunch currently does not.
- Keep the Swift bundle-and-expiry XCTest penalty, private-AX accepted-depth cache, warmup exemption, and host app-generation bridge circuit separate.
- Screenshot/recording helpers presently located in
RunnerTests+Lifecycle.swiftare an opportunistic colocation step inside the target-transition child only when needed to isolate that owner. No standalone file-shuffling project. - Do not replace both snapshot presenters with one universal engine, rewrite the entire runner to actors/async, invent a generic retry/circuit-breaker framework, delete private-AX/curl fallbacks without measurement, or expand protocol/device support.
Non-blocking actor follow-up
#2976 compares a command-control actor with an equivalent concrete synchronized coordinator after the first cleanup wave (#2963, #2965, #2972, #2974, #2970). It requires preserved ordering/timeout behavior and a measured deletion/complexity case. It does not block this umbrella's completion and does not authorize a production actor migration. Run the experiment before reconsidering the deferred scheduler/penalty extractions or expanding target ownership; keep shared Swift edits serial. A negative result is a valid completion.
Closure
Close only after checking active child states and merged PRs, reporting the cumulative production delta and removed mechanisms, and recording retain/delete decisions for the measurement track. Deferred #2966/#2973 and conditional #2969 do not force low-value implementation: explicitly retain their deferral or close them with rationale after review. A positive production delta requires an explicit scope decision rather than a shrinkage claim. A design gate is not complete merely because it was written down.
Historical audit
Purpose
A tracking issue for a simplification and correctness audit of the Apple side: packages/platform-apple (about 32k lines of production TS) and apple/runner (about 17.5k lines of Swift plus ObjC bridges), at 01328de411.
Method:
- Four read-only audits applied community Swift skills: AvdLee and twostraws Swift Concurrency and Swift Testing, the Swift API Design Guidelines, and jazzychad's ios-code-audit.
- A second model cross-reviewed every finding against the code.
- The top claims were spot-checked by hand.
Findings refuted in review are not filed here.
Verdict: the core machinery earns its complexity: runner lifecycle, lease detach/adopt, recycle ledger, readiness preflight, command journal, three-tier snapshot plan, and the private-AX ladder pinned by conformance fixtures. The waste sits at the edges. Removing it cuts about 1–1.5k production lines, and moving the inline tests (#2792) takes about 3.9k lines out of production Swift files.
Each child issue is one PR (or a small series of pure-move PRs) inside one module group.
Original audit status (verified 2026-09-25)
The three previously outstanding issues are now closed: #2788 / #2865, #2796 / #2858, and #2797 / #2876. All three PRs are merged. The prior audit's checklists follow; these are historical results, not new improvement measurements.
Bugs (original audit)
- fix(ios-runner): snapshot capture plan reads and writes main-owned target state off the main thread #2781 Snapshot plan data race on main-owned target state — fix(ios-runner): keep snapshot-plan target state on main and bound the query sweep by its slice #2836
- fix(ios-runner): runMainThreadWork reports MAIN_THREAD_TIMEOUT for work that finished at the timeout boundary #2782 MAIN_THREAD_TIMEOUT reported for finished work — fix(ios-runner): return work that finished at the timeout boundary and bound recorder frame capture #2837
- fix(ios-runner): non-interactive query sweep outlives its 1s tier slice and causes RUNNER_BUSY #2783 Query sweep outlives its 1 s slice → RUNNER_BUSY — fix(ios-runner): keep snapshot-plan target state on main and bound the query sweep by its slice #2836
- fix(ios): pass the scoped simulator set to the AX bridge and fold
simctl spawncalls #2784simctl spawnmissing--set(AX bridge, fold) — fix(ios): spawn the AX bridge and fold helper inside the scoped simulator set #2818 - fix(ios): physical-device install lost devicectl hints; delete the dead install/uninstall/push paths #2785 Physical install lost devicectl hints; dead install/uninstall/push — fix(ios): keep devicectl hints on physical install and delete the dead install paths #2857
- fix(ios-runner): one synthesize-then-XCTest-fallback helper; align sequence tap with standalone tap #2788 Sequence tap and standalone tap fall back differently; one fallback helper — fix(ios-runner): one synthesize-then-fallback helper; sequence taps use the standalone tap policy #2865
- fix(ios): visionOS taps request synthesis the runner cannot perform #2789 visionOS taps request impossible synthesis — fix(ios): request runner synthesis only on platforms that synthesize #2835
- fix(ios): runtime clang builds must not use -Werror; cache the fold helper build #2796 Runtime clang
-Werror; fold recompiles every call — fix(ios): drop -Werror from runtime clang builds and cache the fold helper build #2858; physical/Duo evidence remains scoped to the implementation report
Cuts (ready-for-agent)
- refactor(ios-runner): Swift retry decisions keyed on message text → typed codes #2786 Swift retry on message text → typed codes — refactor(ios-runner): key runner retry decisions on typed failures, not message text #2831
- refactor(ios-runner): TS runner retry rules keyed on self-produced message text → typed reasons #2787 TS retry on self-produced message text → typed reasons — refactor(ios-runner): key TS runner retry rules on typed connect-failure reasons #2820
- chore(ios-runner): delete the unreachable synthesized
.continuousdrag profile #2790 Dead.continuousdrag profile — chore(ios-runner): delete the unreachable synthesized continuous drag profile #2825 - chore(ios-runner): remove env knobs nobody sets, the dead
backwire command, and unreachable paths #2791 Dead env knobs,backcommand, unreachable paths — chore(ios-runner): remove unset env knobs, the dead back wire command, and unreachable paths #2828 - refactor(ios-runner): move ~3.9k lines of inline unit tests out of production Swift files; split CommandExecution under 1,000 lines #2792 Move inline Swift unit tests out; split CommandExecution under 1,000 lines — refactor(move): move the runner's inline unit tests into UnitTests/ #2854, refactor(move): split RunnerTests+CommandExecution.swift along command families #2855
- refactor(platform-apple): collapse the runner client forwarding chain and merge the TS command manifest/traits #2793 Runner client forwarding chain; one TS trait table — refactor(platform-apple): collapse the runner client chain and merge the command trait table #2822
- refactor(platform-apple): one simctl --set builder and one simulator state parser #2794 One simctl
--setbuilder and state parser (after fix(ios): pass the scoped simulator set to the AX bridge and foldsimctl spawncalls #2784) — refactor(ios): one simctl --set builder and one simulator state parser #2824 - refactor(platform-apple): dedupe xctrace logic between perf.ts and perf-xctrace.ts; split perf.ts under 1,000 lines #2795 xctrace dedupe; split perf.ts — refactor(platform-apple): dedupe xctrace logic behind perf-xctrace and split perf.ts #2823
- docs(ios-runner): RUNNER_PROTOCOL.md points at the wrong file and omits recovery and busy codes #2797 RUNNER_PROTOCOL.md drift — docs(ios-runner): point RUNNER_PROTOCOL.md at the current declarations and document recovery and busy codes #2876
Decided 2026-09-23 (codex cross-check + code verification), now ready-for-agent
- refactor(cli): drop the inert --device-hub option #2798 Drop the inert
--device-hub(plain removal, not treated as breaking) — refactor(cli): drop the inert --device-hub option #2821 - fix(macos): reject --fullscreen for macOS desktop/menubar helper surfaces #2799 Reject
--fullscreenfor macOS desktop/menubar helper surfaces; keep it for app sessions — fix(macos): refuse --fullscreen on every helper-captured surface #2849 (widened to every helper-captured surface, includingfrontmost-app) - refactor(ios-runner): restrict the tap-witness synthesized type route to newline #2800 Restrict the tap-witness type route to
'\n'— refactor(ios-runner): restrict the tap-witness synthesized type route to the bare submit key #2845 - fix(ios-runner): bound recorder frame capture through runMainThreadWork #2801 Bound recorder frame capture through
runMainThreadWork; screenshots are already bounded — fix(ios-runner): return work that finished at the timeout boundary and bound recorder frame capture #2837 - refactor(ios-runner): delete Swift-side snapshot warning text; keep the tree pin #2802 Delete the Swift warning text (TS already renders every fact); keep the
treepin — refactor(ios-runner): drop the runner's snapshot warning text; the verdict carries every disclosure #2834
Already tracked elsewhere
#2662 is closed: response decoding and session state shipped; curl transport removal is not planned and the existing fallback is retained. See its disposition comment for the public dispatch-disclosure contract.
Historical measurement candidates (now promoted above)
- The outer 3× read-only retry in
runner/runner-client.ts:42-56. It predates the recycle ledger (Runner resilience under agent-abuse command patterns #1105). - The runner's private-AX tier, about 1.4k lines. It could go only if the host bridge learns to read custom actions, and removing it weakens recovery while the bridge circuit is open.
Activity
[-]Apple platform + runner: simplification and correctness audit (tracking)[/-][+]Apple platform + runner: simplify, shrink, and preserve correctness (tracking)[/+]