Skip to content

investigate(android): separate ADB transport addressing from device-command payload #2617

Description

@thymikee

investigate(android): separate ADB transport addressing from device-command payload

Question and evidence

Can typed transport addressing eliminate repeated flattening/reparsing of ADB arguments while preserving local, provider and managed-device routing?

At PR #2611 a3a0ea216356813323b0008642c05f6f5dc57fcc, transport prefixes and device commands share one argv. Provider scope reads and strips serials, Limrun adds one again, managed-host wiring rewrites ports, and generic host copies relay device-shell provenance.

Evidence: kernel construction/relay, packages/platform-android/src/adb-provider-scope.ts, Limrun prefix, managed host normalization, and src/platform-runtime-host.ts / src/platform-runtime-operation-host.ts.

Investigation scope

This issue authorizes a bounded design trace and disposable prototype, not a production migration.

  1. Trace local device execution, provider-scoped forwarding, managed server-port substitution, generic host ingress and background spawn. Inventory accepted addressing forms and global commands; do not assume serial and serverPort represent the entire ADB grammar.
  2. Compare keeping the existing argv design with a narrow immutable command plus typed addressing. Illustrative shape only: { target, command }, where target carries validated transport selection and the shell command retains kernel-owned quoting/provenance. Identify where generic argv enters and where final argv is serialized.
  3. Demonstrate the smallest prototype across local, provider and managed routes. Preserve fix(device-shell): one typed device-shell boundary for adb, exec-out, and hdc #2611 refusal behavior, explicit shell fragments, device isolation, requested/managed-port rules, binary output and lazy loading. Preserve existing spawn behavior; do not claim fix(device-shell): one typed device-shell boundary for adb, exec-out, and hdc #2611 already guards every spawn.
  4. Enumerate every relay/parser removed, any new parser/adapter, and the remaining execution boundaries. Reuse existing owners; no global invocation registry or generic command framework.

Acceptance and stop criteria

  • Deliver a route diagram/table and concrete proposed API, with an explicit continue/stop recommendation and implementation cut set.
  • In the prototype, serial selection/port substitution operate on addressing without copying or reconstructing shell payloads. Show whether generic host copies can lose shell-specific relay knowledge; report residual exceptions honestly.
  • Characterize ordinary and shell/exec-out commands, explicit serial mismatch, global/host-preconfigured commands, managed forbidden commands, port precedence, provider forwarding and binary results against the current path. Use existing transport/provider tests, not only a new synthetic executor.
  • Report changes in independently authored routing/provenance decisions, production LOC and eager closure. Stop if complexity merely moves into an equivalent number of bridges, weakens enforcement, or needs unrelated lifecycle changes.
  • Record any released executor API affected and compatibility obligations before recommending migration. A production proposal must include exact validation lanes and unresolved native/provider evidence; do not mark this issue as a completed implementation.

Dependencies and exclusions

Blocked by: #2611 landing. Re-audit its merged transport shape first. #2026 owns quoting and the exhaustive current migration; this investigation must not broaden or block it. #2545 owns daemon/root closure, not ADB invocation representation. No HarmonyOS redesign, provider admission redesign, new enforcement framework or issue-driven production changes in this spike.

Effort: M for investigation; production migration likely L. Risk: medium–high for a later migration because routing and device isolation depend on normalization. No existing issue found for separating these representations.

Activity

  1. thymikee commented on Sep 15, 2026

    @thymikee
    MemberAuthor

    Design trace complete — production path taken in #2632

    The bounded trace, the prototype, and the migration all happened. #2632 is the production cut set. Per this issue's own instruction ("do not mark this issue as a completed implementation"), I am leaving it open for a maintainer disposition and recording the artifacts here.

    Route table (as shipped)

    Every row states who parses, who owns device selection, who owns the adb server, and where flat argv is finally produced. Flat argv is produced in exactly one function, serializeAndroidAdbInvocation, and consumed by exactly one lowering, lowerAndroidAdbInvocation.

    Ingress Parses Owns device selection Owns adb server Emits argv
    Local device route (createLocalAndroidAdbProvider().exec / .spawn, createDeviceAdbExecutor) parseAndroidAdbArgv the route's device id deviceServerPort: lease → built-with port → per-call option serializer + androidManagedAdbEnvironment
    Provider-scoped device command (ALS scope holding a provider) parseAndroidAdbArgv scope serial provider's own transport; caller argv minus the scope's -s pair (androidAdbPayloadWithoutSerial) provider decides
    Host command inside a lease (runAndroidHostAdb) parseAndroidAdbArgv scope serial adopted lease port, carried in the target serializer
    Ambient host command (no scope) parseAndroidAdbArgv caller's -s, kept as typed addressing caller's -P / inherited env serializer re-emits the remembered argv unchanged
    Background spawn (AndroidAdbSpawner, scoped background transport) parseAndroidAdbArgv as the device route same single channel host spawnAdb
    Limrun session builder, no parse tunnel serial ambient host port → serializer
    Root binding (src/platform-runtime-android-adb-host.ts) nothing (input already typed) target requireAndroidAdbServerPort serializer + env lowering

    API as built

    AndroidAdbInvocation = { target: { selector, server, waitFor?, hostGlobals? }, command, rawArgv? }. Grammar data (ADB_GLOBAL_OPTIONS arity, 20 wait-for[-TRANSPORT][-STATE] forms, managed-forbidden commands) is one table read by the parser, the managed policy, and their tests. Operations: parseAndroidAdbArgv, serializeAndroidAdbInvocation, androidAdbInvocation, androidAdbSerialTarget, androidAdbOwnedServerPort, requireAndroidAdbServerPort, applyManagedAndroidAdbServer, requireManagedAndroidAdbSerial|Command|Addressing, requireAmbientAndroidAdbSerial, androidAdbPayloadWithoutSerial, androidManagedAdbEnvironment.

    Relays removed, additions

    Removed: findAdbSerialIndex, readAdbSerial, stripAdbSerialArgs, withServerPort, scopedServerPort, adbInvocation (the -s/-P stitcher), assertManagedAdbCommand, transportMismatch, the inline ['-s', serial, ...args] / ['-P', String(port)] stitching at six call sites, LimrunAdbInvocation (structural type copy), and a second argv projection in the provider. Added: one parse/serialize pair, one port reconciliation, one payload projection. No registry, no framework, no new seam.

    Characterization against the current path

    Against pnpm test:unit and vitest --project provider-integration, including the 21-selector managed oracle and the provider recording/lifecycle scenarios. Three intentional deltas, each pinned by a test:

    1. Under a private server, global options that transport cannot restate (-t, -H, -L, -a, -d, -e, a second -s, a second wait-for) are refused with managed-device-transport-mismatch instead of being silently left behind or restated.
    2. Provider forwarding keeps the caller's argv (readiness tokens and globals included) instead of a rebuild.
    3. A per-call serverPort that disagrees with the port the route owns is refused; without a lease the built-with port wins, as before.

    Cost

    27 files, +1508/−350 gross. Production-only: 13 files, +726/−240, net +486 — the grammar table, the algebra, and the managed rules that were previously spread across five relay functions. Eager closure did not move: mechanics evaluates 177 modules as at the merge base, adb-host 1, provider-limrun/index unchanged; ADR-0019's no-growth gate passes with no budget row edited. Addressing lives inside adb-transport.ts precisely because a new module on a façade path would fail that gate.

    Released API

    The published agent-device/android-adb surface is untouched on this branch: AndroidAdbExecutor, AndroidAdbExecutorOptions (serverPort and detached retained), and its nine *WithAdb helpers keep their shapes, and src/__tests__/android-adb-public.test.ts needed no edit. Nothing published was removed, so no git tag --contains obligation arose.

    Recommendation

    Continue — the migration is worth what it cost and is done. Residual evidence still owed, not obtainable in this environment: a live managed-lease run against a real private adb server (no Simlock host/token here) and a live Limrun session (no API key). snapshot/press UI checks additionally need package:android-snapshot-helper assets this worktree lacks. Live coverage here was the ambient local route on emulator-5554.

  2. thymikee commented on Sep 16, 2026

    @thymikee
    MemberAuthor

    Maintainer disposition: closing. The investigation's deliverables (route table, proposed API, continue/stop recommendation, cut set) are in the comment above, and the production cut landed as #2632 (17eabc8fd1, iOS/macOS/Linux lanes green on main). The residual live evidence it names (managed-lease against a private adb server, a Limrun session) is #2632's obligation, and the serverPort type cleanup it identified is tracked as #2640.

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