feat(remote): proxy parity for Simulator observation (#2198 slice B) - #2351
Conversation
Size Report
Startup median (7 runs, lower is better):
|
…, cancellation, and version check #2198 slice B. Direct-daemon and proxy execution over the same deterministic Simulator fixture now publish the same responses, and the three places where they did not are closed: - A client that disconnects mid-request behind the proxy now cancels the daemon request. The proxy's upstream fetch is bound to its client's connection, so the daemon's own disconnect cancellation (`markRequestCanceled`) fires exactly as it does for a direct client. - The proxy forwards `GET /sessions/<session>/requests/<id>/diagnostics` (#1801), so a remote client behind it localizes a failure's diagnostics record instead of reporting `logPathUnavailable: HTTP 404`. GET only; the route still enumerates nothing. - The client's ADR 0006 health check reads the `upstream` link a proxy's /health already nests: a proxy whose daemon speaks another RPC protocol fails at health, before the command RPC. The provider scenario harness exposes its request boundary so a scenario daemon can sit behind a real HTTP server and proxy; the new parity suite runs one script direct and proxied and compares the published responses with transport identity removed, and proves two proxied clients contending for one device fail at claim admission before any lifecycle call.
f654a39 to
dfc9149
Compare
|
The forwarding changes look sound, but the validation is not complete yet. The new tests cover contention and explicit close/reopen; they do not exercise proxy-path heartbeat, lease expiry and reacquisition with fresh generation state, as required by #2198. Add that case and show it fails when stale state is retained. The controlled 0/20/80 ms RTT comparisons and the remaining validation listed in the PR are also still pending. Keep this behind #2329 in merge order. |
* codex/2198-runner-demand: feat(apple): release a speculative runner when the plan is proven observation-only refactor(daemon): move four pure leaves to their kits (#2347) fix(ios): stop charging every capture for a slow Simulator app discovery (#2331) refactor(commands): move commands-side rendering out of src/daemon and retire the doctor progress flag (#2349)
… a reacquired lease starts clean #2198 acceptance: lease expiry, session cleanup, and device reacquisition do not reuse prior capture or comparison state. The parity world takes a clock-driven LeaseRegistry; a leased session behind the proxy captures a diff baseline, its lease lapses past the proxy TTL, the next request is refused as UNAUTHORIZED/LEASE_NOT_FOUND with the session torn down, and a freshly allocated lease reopens and reports baselineInitialized on its first diff.
|
Lease expiry / reacquisition through the proxy is now covered at 20462bf ( This head also merges the slice A head that resolved the conflict with main (b36d2ce). Still outstanding here: the controlled 0/20/80 ms RTT bench against the #2189 baseline and the full |
|
At ddfbd9d, the proxy changes have no additional code finding, but the heartbeat-renewal case and controlled 0/20/80 ms RTT evidence requested above are still missing. This branch also lacks the latest parent benchmark correction from #2329; bring that in before final validation. #2329 remains the prerequisite, and Android smoke is still failing. |
* codex/2198-runner-demand: fix(ios): keep observation on the bridge while app discovery is pending and no runner is live bench(ios): press an unambiguous target on the catalog and Settings screens
Controlled-RTT proxy bench: base vs headTested commits: base 27a97ee, head 318d510 (this branch's head, which merges slice A e729321); Raw results and provenance:
Reading: the proxy path is unchanged within noise at every RTT point and execution mode (the largest move is −8 ms on a 93 ms cell), response size is identical, and no cell failed. The persistent-client wall tracks RTT at roughly 2.5–3 round trips per snapshot (36 → 93 → 233 ms), the fresh-process CLI adds its fixed process cost on top; neither slope changes between base and head, which is what a pass-through proxy with the same published payload should show. Reference profile: #2189's |
|
The controlled 0/20/80 ms RTT evidence at 318d510 closes the benchmark gap. The same-host base/head comparison shows no material regression; the older #2189 run is correctly identified as a reference profile, not a directly comparable baseline. The proxy heartbeat-renewal test, #2329 prerequisite and failing Android smoke remain the merge blockers. |
* codex/2198-runner-demand: fix(apple): queue a speculative-runner release behind a start that is still in flight
…e session's comparison state #2198 acceptance: lease heartbeat through the proxy. A leased session behind the proxy captures a diff baseline; an explicit lease_heartbeat RPC just before the lease's reported expiry moves the expiry forward; a request past the old expiry but inside the renewed window still finds the session and reports the baseline. Red without the renewing heartbeat (LEASE_NOT_FOUND).
|
Heartbeat renewal through the proxy is covered at f557142 ( This head also carries the parent's latest fixes (325343d: the deferred-start release race and the discovery-wait regression). The full |
|
|
|
Merged the parent at fdbc70d, which brings in the current main (#2360 moved the daemon client to |
|
Stacked on #2329 (slice A). Part of #2198; tracker #2188.
What this closes
Slice B of #2198: a client behind
agent-device proxygets exactly what a client on the daemon host gets. One deterministic Simulator acquisition fixture, one script, run direct and proxied, compared with transport identity removed. Three places where the proxied leg diverged are fixed:AbortSignal.timeout, so a remote client that gave up mid-request left the daemon request (and any runner work it owned) running to completion. The upstream request is now bound to the client's connection: when the client vanishes before the response ends, the upstream socket is dropped and the daemon's own disconnect path firesmarkRequestCanceledfor that one request, exactly as it does for a direct client. Response completion is guarded, so a normal end never aborts. Test: a daemon behind the proxy whose handler waits on its request signal; the client destroys its socket; the signal aborts. Red without the change (5 s timeout).GET /sessions/<session>/requests/<id>/diagnostics(A remote caller cannot read the diagnostics log the error names #1801), so every proxied failure came back withlogPathUnavailable: … HTTP 404while the direct client localizes the record intologPath. The proxy now forwards that one route, GET only, client-authenticated first, upstream-authenticated by the daemon as before; the route still enumerates nothing (/sessions/<s>/requestsstays 404). This forwards an existing daemon route the remote client already speaks; it adds no new endpoint semantics. Test: the parity suite (red without it: thelogPathUnavailableline reappears) plus a proxy unit test pinning method, path, auth header and the 401/404 edges.rpcProtocolVersion. The proxy already nests the daemon's health asupstream; the client now reads that link and refuses the chain at health when any link speaks another protocol, naming the skewed link (remoteService: agent-device-daemon). Additive per ADR 0006: the field is optional, payloads without it parse as before, request shapes are unchanged; the wire ledger carries the digests and acks. Tests: loopback health servers for compatible chain, skewed daemon behind a compatible proxy, and a plain daemon payload.Parity and lease evidence
test/integration/provider-scenarios/remote-proxy-parity.test.ts, over a scripted runner (repeatsnapshot) and scripted simctl:open→snapshot -i→snapshot --raw→diff snapshot→click @e404(typed failure) →appstate→close→open→diff snapshot→close, run direct through the harness request boundary and proxied through a real daemon HTTP server + proxy + the real remote client. Every response is deep-equal after removing request ids, diagnostic ids, timing (measuredAt,p50Ms…), the per-openrefsGeneration, and per-world temp roots. On failures, every envelope field the direct leg carries (hint,details,diagnosticId,logPath) is present on the proxied leg.diffafterclose/openreportsbaselineInitialized: trueon both legs.DEVICE_IN_USE+ hint at claim admission, with no simctl call beyondlistand no runner call recorded after the refusal.LeaseRegistry): a leased session captures a diff baseline; an explicitlease_heartbeatRPC just before the lease's reported expiry moves the expiry forward; a request past the old expiry but inside the renewed window still finds the session and its baseline (red without the renewing heartbeat:LEASE_NOT_FOUND).LeaseRegistry): a leased session captures a diff baseline; its lease lapses past the proxy TTL without a heartbeat; the next proxied request is refusedUNAUTHORIZED/LEASE_NOT_FOUNDwith a hint and the session is torn down; a freshly allocated lease reopens and its firstdiffreportsbaselineInitialized: true, so no capture or comparison state crosses the expired lease.The provider scenario harness exposes
handleRequestandtokenfor this; nothing else in the harness changes.Already covered, cited not re-added
awaitObservablechecks it between polls) and the runner client; suitessession-test-suite-command-cancellation,snapshot-command-runtime,request-execution-scope.lease-lifecycle.test.ts,provider-lease-expiry.test.ts,session-device-claims.test.ts.Non-goals honored
No new authentication model, lease model, public flag, or RPC redesign. Bridge state stays private: nothing about the runner or the observation probe crosses the proxy.
Validation
daemon-proxy.test.ts(13),daemon-client-health-compat.test.ts(3),daemon-client.test.ts(36),remote-request-diagnostics.test.ts,remote-proxy-parity.test.ts(4),remote-daemon-client.test.ts,remote-proxy-artifact-tenant.test.ts,test/wire-compat(39), test-file size ratchet. Planted-red for all three fixes recorded above, and for the expiry regression (clock not advanced → the "expired" request succeeds and the test fails).pnpm typecheckgreen.evidence/ios-snapshot(2198-slice-b-rtt-318d510769/). test(ios): establish snapshot convergence baselines and permanent evidence #2189'sios-snapshot-proxy-71fb2483fis the reference profile (same cells and RTT points, different Simulator and runtime).pnpm check:affected --runon the exact head: posted as a comment when it completes.