Skip to content

refactor(apple-runner): type outgoing commands by payload family #2971

Description

@thymikee

Type Apple runner outgoing commands by their payload family

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

Scheduling prerequisite: #2967 lands first; retain needs-triage until then. This strengthens compile-time payload guarantees and may legitimately grow type declarations. Report that growth honestly, remove whole-bag consumers/casts and redundant payload projection where applicable, and avoid per-command factories or a schema framework. Do not classify added type lines as a code-removal win.

Purpose and readiness

Child of #2803. Queued after #2967; no semantic dependency on its behavior. This is an internal type migration with unchanged wire behavior. Coordinate edits to runner-contract.ts with other active work; the scheduling gate above prevents overlapping ownership. Neither #2965 nor the session/exchange extraction is a behavior dependency.

Audited at 1206495222385424d96b11bd9c9be25eb267455e; rechecked against 5712a3552e209c9c853bc1f42cb3e007f1213139, with no intervening TS runner-contract changes. Include current main's appState command rather than using the older checkout's command list.

packages/platform-apple/src/runner/runner-contract.ts:42 defines every command name followed by roughly forty optional payload fields. This admits snapshot-only options on status requests and makes alert, gesture, recording, selector, and lifecycle payload ownership implicit. The exhaustive runner-command-traits.ts table already owns host retry/readiness policy; #2900 already verifies production-built requests against fixtures consumed by TS and Swift. Extend those strengths rather than replacing them.

Required change

Replace the outgoing RunnerCommand bag with a discriminated union whose command selects a named payload shape. Share genuinely common envelope fields, such as optional commandId, and only share family fields where their meaning agrees. Keep a single declaration of the command-name vocabulary, derived from the union, and retain exhaustive trait-table coverage. Command-specific readers must narrow the discriminant; remove whole-bag field access instead of restoring it through casts.

An example of the intended distinction is a status request with statusCommandId versus a snapshot request with capture options. statusCommandId can be required for a production status request because native executeStatus already refuses its absence. This is not permission to make every optional native field mandatory. Inventory current producers and native handlers before deciding membership and optionality: preserve omitted alert action, default snapshot options, valid selector/coordinate alternatives, optional app targeting, and commands that rely on existing native defaults. Attach a compact mapping of command families to their payload declarations in the PR description.

Keep the JSON field names, values, omission behavior, defaults, command IDs, and Swift decoder/validation semantics unchanged. Arbitrary JSON still receives native validation; this task strengthens trusted host construction. Adapt shared readers and existing builders directly. Do not add a parallel legacy command type, runtime schema framework, generated protocol DSL, wrapper factory per command, or an index signature/any escape hatch. The host and Swift trait tables describe different policies and must remain distinct.

Completion criteria

  • Before: every command exposes unrelated optional payload fields. After: switching on command narrows the permitted payload; host construction cannot pass snapshot options to status, alert actions to a gesture, or recording options to app lifecycle commands.
  • All outgoing production constructors and provider callbacks compile against the union without new broad as RunnerCommand assertions or permissive compatibility aliases. Derive field-specific aliases from their owning member when current consumers index the whole bag.
  • Compile-time negative cases prove cross-family fields are rejected and a required status target is enforced, including pre-bound variables and object spreads rather than only fresh object literals. Ordinary excess-property checking is insufficient: explicitly exclude known foreign payload keys on union members (for example with optional never properties), keeping that rule type-only and local to the command declaration. Positive cases prove accepted omitted/defaulted fields remain representable. Use the existing typechecking setup, not a new test lane.
  • The complete command-trait table, including appState and payload-dependent alert traits, remains exhaustive. No command is added, removed, reclassified, or routed differently.
  • Existing production-built request goldens remain unchanged; TS constructors and Swift decoding both verify the same fixtures. Any unexpected fixture diff blocks completion until explained as a test correction, not accepted as part of this type migration.

Validation

For a fresh worktree run pnpm install --frozen-lockfile && pnpm build. Read docs/agents/testing.md and inspect the affected dependency report for runner-contract.ts. Run pnpm typecheck, focused runner-request/command-trait/provider/sequence tests and the owning production-constructor suites. Verify negative type assertions become errors when their expectation directive is removed. Run repository-wide pnpm format, then pnpm check:affected --run on the final diff. Preserve selected TS-to-Swift fixture/native obligations; if Swift source changes unexpectedly, stop and rescope rather than silently broadening the migration. Report structural type checks separately from device evidence; no performance improvement is claimed.

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