Skip to content

refactor(apple-runner): own target identity and reset transitions #2969

Description

@thymikee

Make runner target identity transitions atomic behind the existing main-owned state

Deletion-first acceptance

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.

Priority and scope gate

Readiness: conditional, not automatically enabled when #2968 lands. Blocked by #2968's executable matrix AND a positive go/no-go decision identifying a real correctness gap or a concrete deletion opportunity. The implementation must remove the repeated reset/identity orchestration it replaces. If it only introduces a target object with the same scattered decisions, defer it. Run alone in the shared Swift files, based on the exact merged prerequisite.

Parent: #2803. Readiness: blocked. Blocked by: #2968 (target-transition characterization). Implement only after its explicit transition/cache matrix and executable evidence land. Evidence baseline: 5712a3552e209c9c853bc1f42cb3e007f1213139; start from the prerequisite's exact merged head or declared dependent branch.

Problem and evidence

#2911 correctly introduced @MainActor RunnerMainOwnedState, but its fields remain writable as independent properties (RunnerMainOwnedState.swift:6–11). Lifecycle code assigns identity and invokes reset hooks at RunnerTests+Lifecycle.swift:195–198,216–219,249–255,385–389. The compiler prevents the wrong executor; it does not prevent an incomplete target transition on the right executor.

Text-entry witness, log deduplication and stabilization currently live separately on RunnerTests.swift:65,86–93. Adding target-related state therefore requires knowing which transition paths must change. This issue closes that ownership gap while preserving the characterized semantics, including unusual ones.

Intended interface and state ownership

Extend or replace the existing concrete main-owned module; do not layer a second target store beside it. Identity is read through one value containing the application handle, optional bundle and optional process identifier, and written only through named transitions: host binding, application binding, observed process replacement and invalidation. External relaunch remains an explicit transition distinct from ordinary invalidation. Exact method names follow the landed matrix; do not expose generic setters or a public bag of reset booleans.

Move state with a proved matching lifetime behind those transitions: the one-shot text witness, target log markers and stabilization anchor are candidates. Snapshot accessibility health and post-snapshot delay retain their characterized lifetime and dedicated operations; do not reset them merely because their container changes. SnapshotCaptureTarget remains the immutable identity handed to off-main capture.

The warmup exemption remains its existing lock-protected owner because lifecycle writes on main and capture consumes off-main. Channel penalty and private-AX depth memory also remain separate owners with their existing bundle/PID/expiry rules. The transition implementation invokes their existing operations where the matrix requires; callers must not reconstruct the reset sequence. Coordinate this interface with the separate penalty-owner issue without making a global cache or shared generation counter.

Observable completion and hard-won constraints

Before: four production paths assign identity properties and separately maintain reset order. After: production identity assignment exists only inside the target module; consumers receive read-only identity and use the named transitions. Demonstrate this with source inspection and a focused ownership gate only if the repository already has an appropriate owner declaration; any new gate needs a planted violation.

The prerequisite's matrix and tests must pass unchanged in expected behavior. Preserve A→B→A lookup behavior, positive-PID refresh requirements, one-shot witness consumption before lookup, modal serving without adopting the modal as session target, retained-runner external relaunch, deferred main writes, and existing stabilization timing. No caller-visible wire, activation, launch or retry change is permitted.

Validation and scope

Carry source-matched lifecycle/target tests to the new owner; retain CommandDispatchTests, SnapshotCaptureTargetTests, AXRecoveryConformanceTests and text-witness integration consumers. Re-run the prerequisite's reset mutations against the new interface. Run pnpm check:xctest-selection, unit-enabled iOS/macOS builds and selected XCTest cases, pnpm check:packaged-runner-swift, and pnpm check:affected --run on the final head. Include simulator relaunch/activation evidence and state honestly whether physical retained-runner behavior was exercised.

Only if needed to isolate transitions, move screenshot/recording helpers from Lifecycle.swift into their existing matching modules with their tests. No separate cosmetic reorganization, whole-runner @MainActor, scheduler rewrite, actor-guard duplication (#2882), universal reset, or altered cache lifetime belongs here.

Source paths in this issue are rooted at apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/; extension names abbreviate the RunnerTests+ prefix where stated. Tests live in its UnitTests/ directory. For a fresh worktree, run pnpm install --frozen-lockfile && pnpm build before the focused checks. Read docs/agents/testing.md and docs/agents/device-verification.md for native lane selection and build freshness.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions