# 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 deployed production Browser Manager,
authenticated gateway, private browser worker, and TURN path. The standalone compose is
useful for image and network inspection, but it does not prove the production claim
ledger, cross-replica revocation, or real worker lifecycle and therefore **does not count
as P5**.

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.

## 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
```
