Skip to content

feat(input): a lease's input socket - #1034

Merged
V3RON merged 30 commits into
task/1016from
task/1017
Oct 27, 2026
Merged

V3RON merged 30 commits into
task/1016from
task/1017

Conversation

@V3RON

@V3RON V3RON commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Closes #1017

Status

Implement: done Review: passed (round 2 and a fix-check) Hardware: n/a Gate: waiting for the stack

Done when

  • pnpm test passes, including src/daemon/input-lease.e2e.test.ts and src/ports/uplink-websocket.test.ts unchanged — input-lease.e2e.test.ts (wiring through a gateway and on a standalone worker), src/input/worker.test.ts, src/input/gateway.test.ts; the uplink test file is not touched.
  • With capabilities.input.enabled true on a standalone worker, an agent token's upgrade to its own lease's /v1/leases/{id}/input gets 0x83 then 0x82 {"width":1,"height":1,"orientation":"portrait","inputUnavailable":true}; another token gets 404 — input-lease.e2e.test.ts "opens WS /v1/leases/{id}/input…" (standalone() cases).
  • pnpm openapi /tmp/openapi.json writes a document listing GET /v1/leases/{id}/input with 101, 401, 404, 426 and 503 (INPUT_UNAVAILABLE, WORKER_UNREACHABLE) — src/input/definition.ts http, pinned by src/http/openapi.test.ts.
  • The three website pages under Modules touched describe the input socket, capabilities.input.enabled and INPUT_UNAVAILABLE — watching-the-screen.mdx (new "Send input to your own lease"), configuration.mdx (row), access-control.mdx (agent row).

Assumptions

  • Assumption: the ambient-identity rule (decided in delivery) — a proxy-signed identity needs Sec-Fetch-Site: same-origin on a WebSocket upgrade as on a change, 403 FORBIDDEN; src/http/auth.ts, proved in input-lease.e2e.test.ts against a daemon with http.proxyAuth and a real JWT; ADR 0033 section 5 and src/http/AGENTS.md say so. The OpenAPI document also lists the 403 the route can answer.
  • Assumption: where the router is imported from — src/daemon/main.ts takes createUpgradeRouter from ports/uplink-websocket.ts (a re-export), because the past-index baseline only shrinks and the ports barrel must not load ws.
  • Assumption: the 426 for a plain GET answers code BAD_REQUEST with Upgrade: websocket — the existing code closest to "wrong kind of request".
  • Assumption: a device input reports its current screen config to onConfig at subscribe time, then on each change — the port lists four members and no config getter.
  • Assumption: when is 0x83 sent? — after the device input has opened, so a client never sends input to a device that is not ready.
  • Assumption: the ws message bound — the upgrade router reads up to 1 MiB per message so the module can drop one over 64 KiB and keep the socket (ws's own limit would close it).
  • Assumption: how does a stopping worker's 0xF1 {1001} reach the client? — as 1011 Worker <id> stopped answering, since the table lists a worker that stops (uplink lost) as 1011 and 1001 as the answering daemon stopping.
  • Assumption: which scroll and key fields are read? — scroll deltas bounded to -1..1, and key messages carry only type and usage (serve-sim's key and shifted are not read); the Android and iOS tasks can widen the message type.
  • Assumption: what does 0x10 get back? — 0x90 {requestId?, ok:false, error:"This device cannot change its pose"}.
  • Assumption: where do the timed close tests run? — the close-reason timing tests (10 s head, 2 s 0xF1 wait, 10 s failed-open memory, 5 s heartbeat) in src/input/*.test.ts with a fake clock, and the real-socket wiring (pong, daemon stop, uplink drop, lease end through a gateway) in the e2e file.

Review

Spec review: 1 blocking, 1 fixed, 1 notes. Code review: 6 blocking, 5 fixed, 0 notes. Claims review: 0 blocking, 0 fixed, 0 notes.
Rounds: 2. Tokens: 19.5M.
Runs: 16. Cost USD: 15.3251. Agent seconds: 6204.
Assessment: complete

Rejected:

  • code: ADR 0033 §5 is amended, which conflicts with an accepted ADR — the same-origin rule for the input upgrade was decided in delivery, pending the maintainer [intentional]

Written by an agent.

V3RON added 22 commits October 26, 2026 19:28
…y refuses a worker that takes no input with 503 INPUT_UNAVAILABLE (#1017)
…sages and the screen config cross the gateway (#1017)
…est answered {ok:false}, and the socket stays open (#1017)
…ect within 10 s of a failed open is refused without a new open (#1017)
… device input closes them 1013, and a failed open is remembered for 10 s (#1017)
…son, and a socket that opens after it is closed too (#1017)
…001 when it sees the lease go, and 1001 when the daemon stops (#1017)
@V3RON
V3RON added this pull request to stack #1027 October 26, 2026 19:00
@V3RON

V3RON commented Oct 26, 2026

Copy link
Copy Markdown
Contributor Author

Decision taken

Question: WS /v1/leases/{id}/input is opened with a GET upgrade. ADR 0033 §5 requires Sec-Fetch-Site: same-origin for a proxy-signed (ambient) identity, but only on methods other than GET, HEAD and OPTIONS. As built, any web page a proxy user visits could open this socket with their cookie and drive their leased device. Browsers do not apply CORS to WebSocket handshakes.

Options:

  1. Refuse an ambient upgrade without Sec-Fetch-Site: same-origin with 403 FORBIDDEN (taken). Agents and T3 Code use bearer tokens and see no change. A console on the same origin is unaffected.
  2. Leave it open, as the GET exemption reads. Any page a signed-in proxy user opens can drive their device.

Why 1: this is the attack §5 exists to stop, it changes nothing for token users, and it is easy to reverse.

Reverting: drop the check from the input route and its Tests row.

Folded into #1017's Tests, marked pending the maintainer.

Written by an agent.

…s the daemon, and the abandon paths are proven (#1017)
@V3RON

V3RON commented Oct 26, 2026

Copy link
Copy Markdown
Contributor Author

Review notes

Not blocking, not verified. Each is one reviewer's claim.

  • spec: src/input/gateway.test.ts:99-103 the relay-end 1011 case repeats the e2e worker-gone 1011 case [testing 7]

Written by an agent.

@V3RON

V3RON commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor Author

Review ledger

Round 1: 7a0f715
Round 2: 6afe780
Round fix-check: 635b721
Round fix-check: 2cca75a

Runs: 18. Cost USD: 15.5029. Agent seconds: 6283.
Tokens input 914, output 354437, cache read 51534629, cache write 1691037.
- build implementer: claude-sonnet-5-5 2111 s $5.7155
- build implementer: claude-sonnet-5-5 111 s $0.3038
- audit implementer: claude-sonnet-5-5 864 s $1.2064
- review 1 reviewer: claude-sonnet-5-5 579 s $0.426
- review 1 spec: claude-opus-5-5 178 s $1.3357
- review 1 code both: claude-opus-5-5 463 s $2.092
- review 1 claims: claude-sonnet-5-5 133 s $0.5225
- fix 1 implementer: claude-sonnet-5-5 225 s $0.5622
- review 2 reviewer: claude-sonnet-5-5 506 s $0.5299
- review 2 code both: claude-opus-5-5 365 s $1.1507
- review 2 claims: claude-sonnet-5-5 23 s $0.105
- review 2 spec: claude-sonnet-5-5 45 s $0.261
- fix 2 implementer: claude-sonnet-5-5 121 s $0.2307
- review fix-check reviewer: claude-sonnet-5-5 304 s $0.2311
- review fix-check code-reviewer both: claude-opus-5-5 156 s $0.5727
- review fix-check claims-reviewer claims: claude-sonnet-5-5 20 s $0.0799
- fix ci implementer: claude-sonnet-5-5 37 s $0.0834
- review fix-check reviewer: claude-sonnet-5-5 42 s $0.0944
Finding outcomes: {"fixed":6,"intentional":1}. Classes: {"last-socket close races a joining socket":1,"opened resource not released on failed upgrade":1,"spec vs rule":1,"testing 2":1,"testing 7":1,"testing 9":1,"unhandled stream error crashes the process":1}.
{"findings":[{"id":"R1","role":"code","class":"last-socket close races a joining socket","outcome":"fixed","path":"src/input/sessions.ts","round":"1","fix":"6afe7802a9d634fd0dd9a72bd5ed4870cae0e959"},{"id":"R2","role":"code","class":"opened resource not released on failed upgrade","outcome":"fixed","path":"src/input/gateway-link.ts","round":"1","fix":"6afe7802a9d634fd0dd9a72bd5ed4870cae0e959"},{"id":"R3","role":"spec","class":"testing 7","outcome":"fixed","path":"src/input/gateway.test.ts","round":"1","fix":"6afe7802a9d634fd0dd9a72bd5ed4870cae0e959"},{"id":"R4","role":"code","class":"spec vs rule","outcome":"intentional","path":"docs/internal/adr/0033-a-sign-in-proxy-is-a-second-way-to-sign-in-and-auth-is-one-module.md","round":"1"},{"id":"R5","role":"code","class":"unhandled stream error crashes the process","outcome":"fixed","path":"src/ports/upgrade-router.ts","round":"2","fix":"635b72184986ef77ec04207e4d41a1b71e17dc1b"},{"id":"R6","role":"code","class":"testing 2","outcome":"fixed","path":"src/ports/upgrade-router.test.ts","round":"2","fix":"635b72184986ef77ec04207e4d41a1b71e17dc1b"},{"id":"R7","role":"code","class":"testing 9","outcome":"fixed","path":"src/ports/upgrade-router.ts","round":"2","fix":"635b72184986ef77ec04207e4d41a1b71e17dc1b"}]}

Written by an agent.

@V3RON
V3RON merged commit 5d93b70 into main Oct 27, 2026
48 of 50 checks passed
@V3RON
V3RON deleted the task/1017 branch October 27, 2026 21:13
V3RON added a commit that referenced this pull request Oct 27, 2026
Closes #1019

### Status

Implement: done Review: passed (round 2) Hardware: unavailable (disk
14.8 GiB free, the lane needs 15) Gate: waiting for the stack
Next: needs-hardware. Run Done when line 3 on a machine with disk space
and an iOS simulator

## Assumptions

- Assumption: where the iOS device input lives —
`src/drivers/ios/input/` (an `index.ts` and `device-input.ts`, as
Android's `input/`) instead of the spec's single `input.ts`, so its test
goes through an `index.ts` (testing rule 6).

## Done when

- [x] The two Tests lines and the `byte-pipe` test pass in `pnpm test`,
and `src/source-hygiene.test.ts` passes.
- [x] `pnpm build:capture` builds `simlock-screen-capture` with the
input mode where Xcode is selected.
- [x] On a simulator, `e2e/slow-agent-device.test.ts` opens a lease and
the Appduct fixture through agent-device, opens `WS
/v1/leases/{id}/input`, gets 0x83 and 0x82, taps the centre of
`tab-status` as agent-device's snapshot places it, and the next snapshot
shows the Status tab's screen (slow lane, iOS simulator). — slow lane on
2ffb9ae, 2026-10-27: passed (18 passed, 17 Android cases skipped)
- [x] `watching-the-screen.mdx` lists what input does on iOS: touch,
scroll, Home, keys on iOS 26 runtimes; not the app switcher, Lock,
rotation or keys on iOS 27 (#1024).
- [x] An agent token streams its own lease through a gateway and through
a standalone worker, and gets 404 for anyone else's. — #1015 (PR #1025),
`src/daemon/stream-gateway.e2e.test.ts`
- [x] A test client that follows T3 Code's message order (waits for
0x83, reads 0x82, sends touches and keys) drives a faked device through
a gateway, in the fast lane. — #1017 (PR #1034),
`src/daemon/input-lease.e2e.test.ts`
- [x] A real iOS simulator and an Android emulator take a tap through
the input socket: a tap on a known control opens the next screen, as
agent-device's snapshot shows (slow lane). A person confirms a tap on an
Android phone (slow lane, person at the phone). — simulator and emulator
taps passed on the slow lane 2026-10-27; the phone tap
(`e2e/slow-android-physical-manual.test.ts`) was confirmed by the
maintainer at the phone
- [x] Ending the lease ends both the stream (with a 0x07 frame) and the
input socket (close 4001). — #1015 (`src/stream/create.test.ts`), #1017
(`src/daemon/input-lease.e2e.test.ts`)

## Review

Spec review: 0 blocking, 0 fixed, 0 notes. Code review: 1 blocking, 1
fixed, 0 notes. Claims review: 0 blocking, 0 fixed, 0 notes.
Rounds: 2. Tokens: 28.5M.
Runs: 11. Cost USD: 6.0555. Agent seconds: 2652.
Assessment: complete

_Written by an agent._
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.

Input socket for a lease

1 participant