open-standards/joule-code/joule-code-rs/crates/joule-moq-room/README.md
Transaction Science 9be13006a1 joule-moq-room: every role holds the seek power
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-19 11:49:00 -04:00

4.2 KiB

joule-moq-room

A room service for WAI live rooms (wai/extensions/live-rooms.md) over joule-moq-relay. It holds one room's authority and clock keys and:

  • issues grants. A visitor's page makes an Ed25519 key and asks for a grant for it (POST /grant). The service signs a short-lived WRG1 grant: a publisher's (its own audio and video, one of the room's places) or a viewer's (the room to watch). A grant is public, so it is useless without its key: the relay, configured with the authority's key ([[auth.authorities]]), honours it only beside a proof of possession over the relay's own challenge (§10.5), which only the page that holds the key can make. Grants are bounded per client address and overall, and publishers per room. Every role, and so every grant, holds the seek power (live-rooms §3.3, bit 4): a visitor may fetch a track below its live edge (§10.6), from the relay's cache or its DVR window, as a late joiner does for the groups before it joined. A relay refuses such a fetch to a grant without it.
  • authors the lifecycle (§7.2). The service's clock is a session on the relay. It watches each publisher's video track (a SUBSCRIBE that asks for no objects): when the track appears the clock signs join and publish for the publisher on its events track (live-events §8.1), and when the relay ends the track (the publisher's session closed) it signs leave. Pages author none of it.
  • serves the room. GET /room gives the signed descriptor, the composition descriptor and the publishers' grants, which a page verifies the room with (live-rooms §9).

A room lasts while anyone publishes in it. When the last publisher has left, the next one starts a new room: a new descriptor at a new t0, under the same authority and clock, so each room's grants and chain hold one stretch of company.

Running it

joule-moq-room keygen <keys.toml>       new keys (mode 0600, never over a file); prints the
                                        relay configuration lines that trust them
joule-moq-room relay-lines <keys.toml>  those lines again
joule-moq-room --config <room.toml>     serve (room.example.toml shows every field)

The keys file must be readable by its owner alone; the service refuses it otherwise. Keys has no Debug, so that no log prints a seed.

The relay needs, beside its listener and limits, the lines relay-lines prints:

[[auth.authorities]]
key = "<authority>"

[[auth.grants]]                  # the clock's events track, in any room
prefix = ["*", "<clock>", "events"]
token_sha256 = "<SHA-256 of the clock's token>"
publish = true
unlimited = true                 # no session or ingress limit for the clock's session

[[auth.grants]]                  # the clock watches the publishers
prefix = []
token_sha256 = "<SHA-256 of the clock's token>"
subscribe = true

and a door that holds each publisher's media to its own key:

[[receipts.door]]
prefix = ["*", "*", "video"]
trust_field = 1

HTTP

Request Answer
GET /, GET /health {"ok", "relay": the clock's session is open, "room"}
GET /room the room: room, t0_us, descriptor, compose, grants (hex), present, publishers, max_publishers
POST /grant {"principal": <64 hex>, "role": "publisher" | "viewer"} {"grant", "digest", "exp", "role", "room": <as GET /room>}; 409 full or busy; 429 rate with retry_after_s; 503 relay while the clock has no session

Every answer is JSON with Cache-Control: no-store. The client's address is CF-Connecting-IP, else the first X-Forwarded-For, else the connection's: the service listens on loopback behind the front that sets them.

Tests

cargo test (this crate is a workspace of its own, with its own lock file, since it builds on wai-rs): the keys file, the grants checked against the descriptor and read by the relay's reader, the places, the limiter, and tests/room_service.rs, the service against a relay on loopback: a grant and its proof open a session and the clock announces the publisher, the grant alone opens none, the room verifies, a third publisher is refused, clients are bounded, and a new room starts when the last publisher has left.

Licence

Apache-2.0.