Skip to content

test(e2e): prove the gateway happy path over the CLI and HTTP (ADR 0005) - #132

Open
V3RON wants to merge 1 commit into
claude/adr-0005-119-drain-reconnectfrom
claude/adr-0005-gateway-smoke-e2e
Open

test(e2e): prove the gateway happy path over the CLI and HTTP (ADR 0005)#132
V3RON wants to merge 1 commit into
claude/adr-0005-119-drain-reconnectfrom
claude/adr-0005-gateway-smoke-e2e

Conversation

@V3RON

@V3RON V3RON commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Stacked on #131. Four smoke tests for the seam nothing covered: a real gateway process, real worker processes on a real uplink, and a client reaching the fleet the way a user does.

Why

src/daemon/gateway-fleet.e2e.test.ts already proves lease routing and a real device.exec across two worker processes — but through the daemon's own dispatch(), in-process. Everything between that dispatcher and a user was unproven for gateway mode: the CLI's gateway/worker branch, the HTTP lease and exec routes against a gateway, SSE streaming of proxied output, and exit-code propagation.

The one out-of-process lease that existed (e2e/gateway-fleet.test.ts) deliberately went straight to a worker's own socket, with a comment saying fleet routing belonged to a later PR than the one that wrote it. That PR has since landed and nothing came back to close the gap.

The tests

  1. CLI through a gatewaysimlock lease gives a grant carrying its worker block and a worker-named id; the worker's own status shows §27a's namespaced requester (gw:<instance>:<agent>); simlock adb takes the gateway branch (device.exec rather than a local spawn), streams both streams, and exits with the tool's own code; release.
  2. HTTP through a gateway — lease request to granted, exec streaming SSE with a real exit code, a silent long-running command answered while it still runs, and a pre-process refusal keeping its own 422 PASSTHROUGH_REFUSED instead of a committed 200.
  3. Fleet-wide FIFOREQUESTER_ALREADY_LEASED (13), --no-waitNO_CAPACITY (11), a waiter reporting queued at position 1 and served on release.
  4. Renew/release — an over-cap --ttl refused gateway-side (§15), and the gateway-issued id round-tripping through status and lease renew.

These are smoke tests: they prove the layers are wired to each other, not that the routing policy, queue ordering or refusal lists are correct. src/gateway's own suites own that.

Two new fake-driver flags, for one assertion

--fake-exec-silent and --fake-exec-sleep=<ms> exist together to pin ADR §19a's started push end to end.

What started buys a transport is timing, not status: http/app.ts races the command settling against started, so a command that merely succeeds reaches the same committed 200 either way — just not until it exits. Only a command that is both silent and slow separates them. Every existing fixture writes its argv and would let a first-chunk fallback pass identically.

That assertion took three attempts, and the first two are why the third is worth having. Billing the 422 refusal as "the only end-to-end test of started" was false — it passes with the gateway's relay ripped out, as does a short silent command. Neutering FleetLeaseCoordinator#exec's onStarted now fails exactly one assertion: expected 4049 to be less than 2000, the response head arriving only after the command exited.

One harness subtlety worth knowing

The CLI test passes input: "", modelling simlock adb ... < /dev/null. That is load-bearing rather than tidiness: against a worker the CLI spawns the tool with inherited stdio and never reads stdin, but against a gateway it must read stdin to EOF before it can send the one stdin string device.exec carries. The e2e harness spawns with a pipe it never closes, so without this the CLI waits for an EOF that never comes — exactly what a real caller handing simlock an open pipe would see.

Verification

  • typecheck, e2e typecheck, lint, format — clean
  • 1810 unit tests pass
  • 4/4 smoke tests pass against the pushed code (136s)
  • device-exec and lease-environment-passthrough re-run and unaffected by the fake-driver change

🤖 Generated with Claude Code

https://claude.ai/code/session_01MA98m7ua7qvDFZjxFaww6Z


Generated by Claude Code

Four smoke tests for the seam nothing covered: a real gateway process, real
worker processes on a real uplink, and a client reaching the fleet the way a
user does.

`src/daemon/gateway-fleet.e2e.test.ts` already proved lease routing and a real
`device.exec` across two workers, but through the daemon's own `dispatch()`
in-process. Everything between that dispatcher and a user was unproven for
gateway mode: the CLI's gateway/worker branch, the HTTP lease and exec routes
against a gateway, SSE streaming of proxied output, exit-code propagation.
The one out-of-process lease that existed went deliberately to a worker's own
socket, because fleet routing belonged to a later PR than the one that wrote
it -- that PR has landed and nothing came back to close the gap.

1. CLI: `simlock lease` against a gateway, the grant's `worker` block and
   worker-named id, the worker's own view showing §27a's namespaced
   requester, then `simlock adb` taking the gateway branch (`device.exec`
   rather than a local spawn), streaming both streams and exiting with the
   tool's own code.
2. HTTP: lease request to granted, exec streaming SSE with a real exit code,
   a silent long-running command answered while it still runs, and a
   pre-process refusal keeping its own 422 instead of a committed 200.
3. Fleet-wide FIFO: `REQUESTER_ALREADY_LEASED`, `--no-wait` `NO_CAPACITY`, a
   waiter reporting `queued` at position 1 and served on release.
4. Renew/release: an over-cap `--ttl` refused gateway-side (§15), the
   gateway-issued id round-tripping through `status` and `lease renew`.

`--fake-exec-silent` and `--fake-exec-sleep` are new on the fake driver's
passthrough program, and exist for one assertion. §19a's `started` push buys
a transport *timing*, not status: `http/app.ts` races the command settling
against `started`, so a command that merely succeeds reaches the same
committed 200 either way -- just not until it exits. Only a command that is
both silent and slow separates them. Every existing fixture writes its argv
and would let a first-chunk fallback pass identically.

That assertion took three attempts, and the first two are why the third is
worth having. Billing the 422 refusal as "the only end-to-end test of
`started`" was simply false -- it passes with the gateway's relay ripped out,
as does a short silent command. Neutering
`FleetLeaseCoordinator#exec`'s `onStarted` now fails exactly one assertion,
`expected 4049 to be less than 2000`: the response head arriving only after
the command exited.

The CLI test passes `input: ""`, which models `simlock adb ... < /dev/null`.
That is load-bearing, not tidiness: against a worker the CLI spawns the tool
with inherited stdio and never reads stdin, but against a gateway it must
read stdin to EOF before it can send the one `stdin` string `device.exec`
carries. The harness spawns with a pipe it never closes, so without it the
CLI waits for an EOF that never comes -- as a real caller handing simlock an
open pipe would.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MA98m7ua7qvDFZjxFaww6Z
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants