Skip to content

docs(adr): 0031 a screen stream is an optional module, and a driver films the device - #594

Closed
V3RON wants to merge 10 commits into
mainfrom
feature/593-adr
Closed

V3RON wants to merge 10 commits into
mainfrom
feature/593-adr

Conversation

@V3RON

@V3RON V3RON commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

ADR 0031 for #593 (live screen streams of leased devices), accepted and matching #576. It amends ADR 0028 §4's relay deadline.

It decides:

  • stream is an optional module, wired by daemon/main.ts (ADR 0030 is not merged). One name everywhere.
  • GET /v1/stream/leases/{leaseId} is the only start, closing it the only stop. Body: the length-tag envelope, meta first, tags in src/core/screen.ts.
  • A capture registers before it starts and its open can be stopped; the lease is re-read after the meta frame; the lease end waits for opens to settle and captures to close before reclaim. 30 s to the meta frame, then 30 s silence.
  • One optional Driver.screenCapture. iOS runs the vendored serve-sim-derived tool that pnpm build builds on the host. Android runs our client against the vendored scrcpy 5.0.1 jar over adb smart sockets, with alive frames on a still screen. Physical devices refused.
  • Gateway: relay target stream, ownership proved twice, bytes unparsed, worker answers checked, relay failures answered 503. Every relay: 330 s deadline to the head, then a 330 s idle timer.
  • No business events; protocol +1 for the config contract. Third-party code only under vendor/.

Stays open below #576 until it merges.

Status

  • ADR corrected to the built design, index row updated
  • Accepted — not yet implemented

Assumptions

None.

Written by an agent.

…ilms the device

Records the decisions behind #593, from what PoC #576 found: the stream
route and envelope, close-is-stop, captures bounded by their lease, one
optional driver capability for iOS and Android, the gateway relay with a
head-only deadline, no business events, and vendored code under vendor/.
V3RON added 2 commits October 15, 2026 12:04
…ver capability as built

The relay's 330 s deadline covers the channel and the response head; an idle
timer of 330 s bounds every relayed body. The Android client reaches scrcpy
through adb's smart-socket protocol, so no adb forward exists. The lease-end
hook waits for captures to close before reclaim. Driver.screenCapture is
declared in src/core/screen.ts; the iOS capture program's path can be
overridden by drivers.ios.screenCaptureCommand.
The module is wired by the composition root, not ADR 0030's host. iOS's
capture tool is built by pnpm build on the host; the scrcpy jar is copied
after its digest check. Android sends an alive frame on a still screen,
drops nothing and needs no reclaim sweep. The tag table gains seed and
alive; the gateway's header bounds, 502 and relay-failure answers match
checkedMeta and refusal.ts. No dispose hook; the log records opened and
refused.
V3RON added 7 commits October 15, 2026 13:53
…, protocol bump

A driver's open takes a stop signal and the lease end waits for an open
in progress. Android's alive frames start after the first key frame.
The gateway maps relay failures to 503 and defines its 502. bitrateKbps
starts at 100; available carries no reason; the config contract bumps
the protocol. Captures per lease are unbounded in v1.
…leased capture the lease end still waits for
# Conflicts:
#	docs/internal/adr/README.md
V3RON added a commit that referenced this pull request Oct 15, 2026
…whoever is looking at it (#576)

Closes #593

A leased iOS simulator's or Android emulator's screen, live in the
console and at `GET /v1/stream/leases/{leaseId}`, on a worker and
through a gateway. Design: ADR 0031 (#594). One PR, one review.

- **Stream module** (`src/stream/`): no platform names; opens a screen
through the driver's `screenCapture`, registers before it starts,
re-reads the lease after the first frame, stops every capture when its
lease ends.
- **iOS**: serve-sim's capture, vendored and patched to Simlock's device
set; `pnpm build` builds it on macOS.
- **Android**: our own TypeScript client in the Android driver for the
vendored, SHA-pinned scrcpy 5.0.1 server; adb smart socket, no `adb
forward`; an `alive` frame keeps a still screen open.
- **Gateway**: the relay's 330 s deadline now bounds only the head; a
body is bounded by 330 s of silence on the Clock port (amends ADR 0028
§4). A worker gone or serving no stream answers 503.
- **Module**: an optional module on ADR 0030's contract, under
`capabilities.stream`; the worker context gains a registry read and the
drivers' screens. No protocol change.

### Status
Implement: done Review: 2 rounds done; the move onto ADR 0030 is not
reviewed yet Hardware: live check passed on both platforms; slow lane
not green yet Gate: not run
Next: review round 3 on the move to the module contract, slow lane, the
gateway check on real devices, then merge after #594

### Done when

- [x] Show screen on a leased iOS simulator, single worker: live in the
console, iPhone 17 / iOS 27, 330×720 h264
- [x] The same on an Android emulator: Pixel 9, 320×720, first frame 4.1
s
- [ ] The same through a gateway, both platforms (unit:
`src/stream/gateway-link.test.ts`, `src/stream/create.test.ts`; not run
live)
- [x] Each Examples row answers its status and code, worker and gateway
(`src/stream/*.test.ts`)
- [x] Closing the request ends the capture: no capture process; on
Android no server and no jar (checked live with `pgrep -x` and `adb
shell`)
- [ ] Releasing a watched lease ends the stream and leaves nothing,
including mid-open: the open is stopped and reclaim waits for it (unit:
`worker-link.test.ts`, `captures.test.ts`, `session.test.ts`; slow lane
pending)
- [x] A still Android screen stays open past 30 s, and one that never
paints does not (`session.test.ts`; slow lane holds 45 s and requires
`alive`, pending)
- [x] `capabilities.stream.enabled: false`: no route, capability `off`;
on: `available` (`e2e/console/screen.spec.ts`, 5/5 Chromium)
- [x] `pnpm build` checks the scrcpy SHA-256 (a mismatch fails it) and
builds the iOS program on macOS (`src/build-capture.test.ts`)
- [x] Vendored code carries license texts and a `NOTICE.md`
(`src/source-hygiene.test.ts`)
- [ ] `scripts/slow-e2e.sh e2e/slow-ios-stream.test.ts
e2e/slow-android-stream.test.ts` passes (first run failed at lease with
the disk nearly full)

## Assumptions

- Assumption: physical phones are refused `STREAM_NOT_SUPPORTED` on both
platforms in v1.
- Assumption: no business events; each stream opened or refused is
logged.
- Assumption: viewers per lease are not capped in v1; one capture each
(#602 shares one capture).

## Review

Spec review: 7 blocking, 3 fixed, 3 notes. Code review: 15 blocking, 15
fixed, 3 notes. Claims review: 8 blocking, 8 fixed, 9 notes.
Rounds: 2. Tokens: 0.11M.

Fixed in round 1 (`12943e48`): the console gave up on a stream's headers
after 10 s, before a worker's own refusal; it now waits 90 s (ADR 0031
records the exception to ADR 0013). The rest were tests that could not
fail and wording. Round 2 (`3d387864`): tests for the 30 s defaults and
wording; Stop now also cancels a starting stream.

Rejected:
- spec: the slow-lane tests have not passed — proven by the hardware
stage, not by review
- spec: the gateway check on real devices is not done — a maintainer
check on hardware
- spec: (round 2) the slow-lane Android test has not passed — same as
above
- spec: (round 2) the gateway check on real devices — same as above

*Written by an agent.*

---------

Co-authored-by: Apex <noreply@callstack.com>
@V3RON V3RON closed this Oct 15, 2026
@V3RON
V3RON deleted the feature/593-adr branch October 15, 2026 18:11
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.

1 participant