feat: OTA relay, TLS serving, and wss migration for the realtime server #19

Merged
troed merged 20 commits from devel into main 2026-10-10 17:35:29 +02:00
Owner

OTA relay, TLS serving, and wss migration for the realtime server

Implements the computer-side of the OTA design
(docs/specs/2026-09-10-ota-design.md, crew repo) — plan 2 of 4
(crew, computer, communicator-esp32, communicator-sailfish).

What this adds

  • server.ota.schedule relay (spec 6.3): after session.created, for a
    device with an identity (device_id) and a pending firmware update, the
    server sends one {"type": "server.ota.schedule", "data": {"version", "url"}} event. The lookup is bounded to 2 s, never blocks the session, and
    a crew outage degrades to "no update event" — it can never break the
    session. The firmware image never transits this WebSocket.
  • firmware_version device identity (spec 6.2): the device query string
    gains ?device_id=<id>&device_type=esp32&firmware_version=<X.Y.Z>;
    DeviceRegistry stores and surfaces firmware_version on
    GET /internal/devices.
  • TLS serving on the same port (spec 6.1): --ws_tls_cert /
    --ws_tls_key on the realtime/websocket server (uvicorn +
    WebSocketStreamer); partial cert/key config degrades to plaintext.
  • wss client migration (spec 8): the PC client and all five test scripts
    gain --ca-bundle / STARFLEET_CA_BUNDLE, switching to wss:// with an
    ssl context pinned to the local CA (hostname validation preserved) when
    set; plaintext defaults unchanged.
  • Conformance-first (spec 6.4): the wire contract landed in
    CONFORMANCE.md and the conformance fake server gains the
    server.ota.schedule scenario before the router work.

Notes

  • local_audio_streamer.py is in the spec change matrix but needs no change
    (pure sounddevice path, no network code).
  • Two pre-existing infra fixes are bundled: a ruff-format fix in
    tests/test_realtime_client.py and a mypy annotation fix in
    src/computer/TTS/omnivoice_flashinfer.py.
  • Devices other than the ESP32 (PC, sailfish) do not send firmware_version;
    the field defaults to empty and has no effect.

Follow-ups in the design still to land: communicator-esp32
(partitions, ota module, wss + embedded CA, release tooling) and
communicator-sailfish (wss migration).

# OTA relay, TLS serving, and wss migration for the realtime server Implements the computer-side of the OTA design (`docs/specs/2026-09-10-ota-design.md`, crew repo) — plan 2 of 4 (crew, computer, communicator-esp32, communicator-sailfish). ## What this adds - **`server.ota.schedule` relay** (spec 6.3): after `session.created`, for a device with an identity (`device_id`) and a pending firmware update, the server sends one `{"type": "server.ota.schedule", "data": {"version", "url"}}` event. The lookup is bounded to 2 s, never blocks the session, and a crew outage degrades to "no update event" — it can never break the session. The firmware image never transits this WebSocket. - **`firmware_version` device identity** (spec 6.2): the device query string gains `?device_id=<id>&device_type=esp32&firmware_version=<X.Y.Z>`; `DeviceRegistry` stores and surfaces `firmware_version` on `GET /internal/devices`. - **TLS serving on the same port** (spec 6.1): `--ws_tls_cert` / `--ws_tls_key` on the realtime/websocket server (uvicorn + `WebSocketStreamer`); partial cert/key config degrades to plaintext. - **wss client migration** (spec 8): the PC client and all five test scripts gain `--ca-bundle` / `STARFLEET_CA_BUNDLE`, switching to `wss://` with an `ssl` context pinned to the local CA (hostname validation preserved) when set; plaintext defaults unchanged. - **Conformance-first** (spec 6.4): the wire contract landed in `CONFORMANCE.md` and the conformance fake server gains the `server.ota.schedule` scenario before the router work. ## Notes - `local_audio_streamer.py` is in the spec change matrix but needs no change (pure sounddevice path, no network code). - Two pre-existing infra fixes are bundled: a ruff-format fix in `tests/test_realtime_client.py` and a mypy annotation fix in `src/computer/TTS/omnivoice_flashinfer.py`. - Devices other than the ESP32 (PC, sailfish) do not send `firmware_version`; the field defaults to empty and has no effect. Follow-ups in the design still to land: `communicator-esp32` (partitions, ota module, wss + embedded CA, release tooling) and `communicator-sailfish` (wss migration).
URL-encoding the device_id (urllib.parse.quote, safe='') stops an
adversarial device_id like "../../registry" from escaping the path and
reaching /registry/firmware-pending through httpx dot-segment
normalization, which would bridge the unauthenticated WS endpoint into
the authenticated crew lookup.
asyncio.wait_for(... timeout=2.0) caps how long a hung crew can stall
session setup (default httpx timeout was 5s). The isinstance guard on
pending now lives inside the same try/except, so a truthy non-dict crew
return degrades to no schedule instead of raising AttributeError and
tearing down the session. server.ota.schedule is still sent only when
version+url are both str, once per connection, via transport.send_raw.
test(client): cover the ca_bundle wss path in websocket_client
All checks were successful
CI / Sanity check (ubuntu-latest) (pull_request) Successful in 4m50s
456ec17cf8
Drives websocket_client with a ca_bundle set to a real temp CA and a
fake connect, asserting the connect receives a wss:// URL and a non-None
ssl argument. This was the untested branch (build ssl context, flip URL,
pass ssl= to connect).
fix(realtime): report device firmware_version to crew on connect
All checks were successful
CI / Sanity check (ubuntu-latest) (pull_request) Successful in 4m28s
0de240955d
The OTA relay now passes the device's reported firmware_version and
device_type to crew's firmware-pending lookup, so crew persists the applied
version and the pending update clears.
troed merged commit 811f295ec6 into main 2026-10-10 17:35:29 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
starfleet/computer!19
No description provided.