# RUNBOOK — real-server tests for the streaming plane (WS-4)

The gateway logic, ticket verifier, cookie, control-lease CAS, revocation
ordering, and the worker input kill switch are all proven **offline** in
`tests/cloud_browser_streaming/` (25 tests, no infra). The assertions below need
**real Selkies + Xvfb + coTURN + headed Chromium** and cannot run in CI, so they
are operator steps. Run them against the standalone compose
(`deploy/docker-compose.streaming.yml`) with any headed Chromium worker — no
Browser Manager, no profile persistence.

Each step names: **what it proves**, **how to run it**, **expected**.

---

## Pre-flight

1. Generate a throwaway EC P-256 key and export it:
   ```bash
   openssl ecparam -name prime256v1 -genkey -noout -out stream.key
   export BROWSER_STREAM_TOKEN_SIGNING_KEY="$(cat stream.key)"
   export BROWSER_STREAM_TURN_SHARED_SECRET="$(openssl rand -hex 24)"
   export BROWSER_STREAM_ALLOWED_ORIGINS="https://aimatrx.com"
   ```
2. `docker compose -f deploy/docker-compose.streaming.yml up --build`
3. Confirm the worker's Selkies signalling port is **not** published on the host
   (`docker compose port worker 8080` returns nothing).

---

## R1 — Encoder is asleep until takeover (D-8 requirement)

- **Proves:** nothing streams by default; the encoder wakes on a claimed takeover
  and sleeps on return.
- **Run:** boot the worker; before any claim, inspect the worker process list
  (`docker compose exec worker ps aux`). Mint + claim a control ticket, observe
  the encoder process appear; release control, observe it exit.
- **Expected:** no encoder process before claim; present during the claimed
  session only; gone after release. CPU on the worker is idle pre-claim.

## R2 — Input dies at the worker the instant control is revoked

- **Proves:** the offline `test_revoke_kills_input_at_worker_immediately` against
  the REAL XTEST path.
- **Run:** claim control, drive the mouse (visible in a recorded frame). Call
  `revoke-control` (owner, confirm=true). Immediately push a WebRTC DataChannel
  input event from the old client.
- **Expected:** the post-revoke event produces **no XTEST event** on the display
  (verify with `xev`/an input logger on `:99`); input is dead in <1s; the media
  frames the ex-controller still sees are of a screen they can no longer touch.

## R3 — SDP / ICE carries no worker control port or private topology

- **Proves:** S4 §8 / §4.1 item 3 — the last row of the §10 table that needs real
  ICE.
- **Run:** capture the SDP offer/answer and the ICE candidate list on the client
  side of a real session (browser `chrome://webrtc-internals` or an SDP dump).
- **Expected:** no candidate exposes the worker's Selkies signalling port, the
  worker's private IP, or any address that reaches a control port. Only relay
  (coTURN) and the intended media path appear.

## R4 — TURN credentials are short-lived and TURN is not a revocation surface

- **Proves:** S4 §8.
- **Run:** mint a ticket, read `ice.turn_username` / `ice.turn_credential`. (a)
  Attempt a TURN allocation after `turn_expires_at` passes → refused. (b)
  Establish a relay, then let the credential expire while the media flows →
  the established relay CONTINUES (expiry gates obtaining, not keeping).
- **Expected:** (a) allocation refused post-expiry; (b) established relay
  unaffected — confirming revocation must be input-kill + media teardown + session
  close, never TURN expiry.

## R5 — Pin the coTURN REST digest (OPEN(turn-rest-hash))

- **Proves:** which HMAC digest the deployed coTURN build uses for REST.
- **Run:** `docker compose exec coturn turnserver -h | grep -i sha` and consult
  the deployed version's docs; test a `sha256`-computed credential if advertised.
- **Expected:** record the answer here and set `StreamingConfig.turn_rest_digest`
  accordingly. Default remains `sha1` (classic REST) until proven otherwise.
  - **Result (fill in):** `______`

## R6 — Selkies media policy is enforced at the source

- **Proves:** clipboard/mic/camera/file-transfer are OFF in Selkies itself, not
  just hidden in the UI (S4 first-release media rules).
- **Run:** with a live control session, attempt a clipboard sync, a mic/camera
  grant, and a file transfer through the Selkies client.
- **Expected:** each is refused by Selkies; only video (+ optional audio if the
  deployment enabled it) flows. A `view` session carries no audio.

## R7 — Reconnect within grace does not re-queue for control

- **Proves:** S4 §7.1 against real network drop.
- **Run:** claim control, drop the connection (kill the client), reconnect within
  `HANDOFF_RECONNECT_GRACE_SECONDS` by minting a fresh ticket.
- **Expected:** reconnect succeeds with a NEW `control_revision`; the user does
  not re-enter the Request-control queue; the old revision is dead.

## R8 — License review sign-off

- **Proves:** the PLAN.md gate. Confirm the running Selkies is the pinned
  unmodified upstream and its MPL-2.0 `LICENSE` is present in the image tree.
- **Run:** `docker compose exec worker sh -c 'pip show selkies; find / -iname "LICENSE*" -path "*selkies*"'`
- **Expected:** version matches `SELKIES_VERSION`; MPL LICENSE present; no local
  modifications. Record sign-off in `deploy/LICENSE-NOTE-selkies.md`.

---

## What stays offline (already green in CI-runnable tests)

Ticket single-use CAS, origin/user/expiry/grant/revision checks, the ten claim
checks, the cookie shape, control-lease CAS and two-controller race, the
revocation ordering and matrix effects, and the "no secrets in responses" grep —
all in `tests/cloud_browser_streaming/`. Re-run them with:

```bash
uv run pytest tests/cloud_browser_streaming/ -q
```
