open-standards/.github/workflows/joule-moq-web.yml
Transaction Science a70795d740 joule-moq-web: the remote page makes its key and asks the room service for its grant
remote.js no longer reads a key file: RemoteViewer takes the grant the room
service issued for the key the page made, reads its control power from that
grant and from each exchange of it the clock signs, and sends nothing without a
grant, before the clock records it joining, or of a class its grant in effect
does not give. app.js enters a remote room through the service (#join asks to
control, #watch to view) and reads the room's grants again on an exchange. The
browser scenario runs against the room service: the host agent by invitation, a
controller's page and a viewer's, and the sessions the host and the clock hold
verified by remote_desktop_verify.py.

Also the room service's README, example and workflows for [remote].

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-26 08:47:24 -04:00

218 lines
12 KiB
YAML

name: joule-moq-web browser client
# The browser MoQ client (joule-code/joule-code-rs/crates/joule-moq-web) against
# joule-moq-relay on loopback, in the host's installed browser, driven by the automation
# package of e2e/package.json, with a fake camera and microphone
# (crates/joule-moq-web/e2e/run.sh):
#
# compose live-conformance/compose in the page: every layout, mix, descriptor and
# composition case through wai-live-web's wasm build, and wai-rs's own
# corpus check run in the browser;
# webtransport two participants and the room's clock over WebTransport: media both ways,
# every object verified against its receipt (the relay's door checks each
# too), the composed canvas's tiles equal to the host's layout for the same
# descriptor and set, the mix in an AudioWorklet, captions with their origin
# label, and a late joiner served earlier groups by FETCH;
# websocket the same over the WebSocket binding, forced;
# goaway the relay shut down with GOAWAY naming a second: every page moves and
# closes its old session before the timeout;
# service a room service (joule-moq-room) keeps the room, as in production: pages
# make their own keys and enter from their buttons, the service grants and
# its clock authors the lifecycle, the relay (WebSocket only, behind a
# same-origin proxy) honours the grants by the service's authority key;
# age a room that admits by age (WAI age-assurance §5.6): pages answer the room
# service's Privacy Pass challenges with tokens they obtain by blind issuance
# from a test issuer over the corpus's published keys; a viewer at 16 is served
# the audio and refused the video its performer rates 18 (below_threshold), a
# viewer at 18 is served both, a token of 13 is refused by the service, a token
# spent twice is refused, and the admission receipt verifies and suppresses;
# e2ee an end-to-end encrypted room (WAI confidentiality) kept by a room service:
# pages key through the service's log, seal every frame and open every object
# before it is decoded; a viewer outside the group opens nothing, every change
# a relay could make is refused with its reason, a late joiner opens nothing
# sealed before its join, a member that stops publishing is removed and opens
# no later epoch, and the relay's DVR store keeps nothing of the room;
# co a room with co-performance (WAI co-performance) kept by a room service behind a
# relay that holds the floor and the audience's inputs: a lead starts turns from
# its page, a performer out of turn is marked by the clock and, under a schedule
# that refuses, held back by the relay; an AI participant's lines show ahead of
# when they play, one kept and one not; a late object is marked; the audience
# votes and sends prompts from the page, the relay withholding each input the
# rules refuse; the clock commits and selects, credited; and the room with the
# clock's log of objects verifies the same in the page and on the host.
# ageco a room that admits by age and keeps co-performance: the room's clock, the
# room's own service by its proof of the room's keys (age-assurance §5.5),
# logs both performers' rated tracks and marks an object out of turn, while a
# viewer at 16 is still refused the video rated 18.
# origin the endpoints a link may name: a relay or a room service on another origin
# is refused and nothing connects; a GOAWAY naming another origin is refused
# and the session ends; and every page of the service scenario connects to its
# own origin alone.
# binding the browser sink (WAI browser-sink): live-conformance/browser in the page,
# a wasm-route decode verified against its receipts with every frame bound,
# the binding through clone, transfer, copyTo and re-encode, the user agent's
# decoder's frames bound through the side table, no codec string outside the
# WebCodecs Codec Registry put to the user agent, a measured figure per decode
# beside powerEfficient, and a room whose tiles show verified, bound frames.
# cowatch a watch room (WAI co-watch) kept by a room service: three viewers check the
# source by its content hash and enter by a proof of their keys; a play, a
# pause and a seek applied by every page at their instants, a late joiner
# brought to the room's position, the clock's skew statements in sync within
# the bound, and the room verified by co_watch_verify.py.
# remote a remote desktop room (WAI remote-desktop) the room service keeps: the host
# agent enters with an invitation, a page makes its key and is granted control,
# decodes the host's screen, asks for the floor, clicks and types, and signs
# latency records; a viewer's page sends nothing, and an input sent past its
# guard is refused by the host; and the sessions the host and the service's
# clock hold verified by remote_desktop_verify.py.
#
# Before the browser, wai-live-web's own tests: the room's records a page reads, co-performance's
# among them, each statement read and the corpus verified through the crate.
#
# The macos runner is the host with the browser installed; the tests drive that browser
# (its executable named by BROWSER_BIN), not a bundled one. The automation package comes
# from e2e/package-lock.json with install scripts off. Each browser runs under the host's
# memguard where it exists. Nothing listens beyond loopback.
#
# Push filters match the push's delta, so every path the job reads is listed.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
on:
push:
branches: [main]
paths:
- 'joule-code/joule-code-rs/crates/joule-moq-web/**'
- 'joule-code/joule-code-rs/crates/joule-moq/**'
- 'joule-code/joule-code-rs/crates/joule-moq-mls/**'
- 'joule-code/joule-code-rs/crates/joule-moq-relay/**'
- 'joule-code/joule-code-rs/crates/joule-moq-room/**'
- 'joule-code/joule-code-rs/crates/jwp/**'
- 'joule-code/joule-code-rs/Cargo.toml'
- 'joule-code/joule-code-rs/Cargo.lock'
- 'joule-code/joule-code-rs/vendor/**'
- 'wai/wai-live-web/**'
- 'wai/wai-rs/**'
- 'wai/wai-ops/**'
- 'wai/live-conformance/compose/**'
- 'wai/live-conformance/presentation/**'
# The page's Privacy Pass client test reads RFC 9578 A.2's vector from the age corpus, and
# the age scenario's issuer its published test keys.
- 'wai/live-conformance/age/**'
- 'wai/live-conformance/coperformance/**'
- 'wai/live-conformance/browser/**'
- 'wai/wai-rs/tests/vectors/int_video/**'
# The page's co-watch module is tested against the co-watch corpus, and the cowatch
# scenario verifies its room with the corpus's second implementation.
- 'wai/live-conformance/cowatch/**'
- 'wai/live-conformance/co_watch_verify.py'
# The page's remote desktop module is tested against the remote corpus, and the remote
# scenario runs the host agent and verifies its session with the corpus's second
# implementation.
- 'joule-code/joule-code-rs/crates/joule-moq-remote/**'
- 'wai/live-conformance/remote/**'
- 'wai/live-conformance/remote_desktop_verify.py'
- '.github/workflows/joule-moq-web.yml'
pull_request:
paths:
- 'joule-code/joule-code-rs/crates/joule-moq-web/**'
- 'joule-code/joule-code-rs/crates/joule-moq/**'
- 'joule-code/joule-code-rs/crates/joule-moq-mls/**'
- 'joule-code/joule-code-rs/crates/joule-moq-relay/**'
- 'joule-code/joule-code-rs/crates/joule-moq-room/**'
- 'joule-code/joule-code-rs/crates/jwp/**'
- 'joule-code/joule-code-rs/Cargo.toml'
- 'joule-code/joule-code-rs/Cargo.lock'
- 'joule-code/joule-code-rs/vendor/**'
- 'wai/wai-live-web/**'
- 'wai/wai-rs/**'
- 'wai/wai-ops/**'
- 'wai/live-conformance/compose/**'
- 'wai/live-conformance/presentation/**'
# The page's Privacy Pass client test reads RFC 9578 A.2's vector from the age corpus, and
# the age scenario's issuer its published test keys.
- 'wai/live-conformance/age/**'
- 'wai/live-conformance/coperformance/**'
- 'wai/live-conformance/browser/**'
- 'wai/wai-rs/tests/vectors/int_video/**'
# The page's co-watch module is tested against the co-watch corpus, and the cowatch
# scenario verifies its room with the corpus's second implementation.
- 'wai/live-conformance/cowatch/**'
- 'wai/live-conformance/co_watch_verify.py'
# The page's remote desktop module is tested against the remote corpus, and the remote
# scenario runs the host agent and verifies its session with the corpus's second
# implementation.
- 'joule-code/joule-code-rs/crates/joule-moq-remote/**'
- 'wai/live-conformance/remote/**'
- 'wai/live-conformance/remote_desktop_verify.py'
- '.github/workflows/joule-moq-web.yml'
jobs:
# Forgejo matches `paths:` against the push delta, so a rebase fires this workflow
# whatever the pull request changes. This job reads the pull request's own diff against
# the paths above (scripts/pr-touches-paths.py, which fails open).
relevant:
name: joule-moq-web · does this change touch it
runs-on: linux
outputs:
run: ${{ steps.gate.outputs.run }}
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
- id: gate
run: |
set -euo pipefail
r=$(python3 scripts/pr-touches-paths.py .github/workflows/joule-moq-web.yml \
"${{ github.event.pull_request.base.sha }}" \
"${{ github.event.pull_request.head.sha }}")
echo "this change touches the browser client: $r"
echo "run=$r" >> "$GITHUB_OUTPUT"
e2e:
name: joule-moq-web · browser end-to-end (macos)
needs: relevant
if: needs.relevant.outputs.run == 'true'
runs-on: macos
timeout-minutes: 90
steps:
- uses: actions/checkout@v5
# launchd's PATH omits cargo on the macOS host runner.
- name: Put cargo on PATH
shell: bash
run: echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
# Pinned to the wasm-bindgen both crates pin: the CLI and the crate must match.
- name: wasm32 target and wasm-bindgen CLI 0.2.127
shell: bash
run: |
set -euo pipefail
rustup target add wasm32-unknown-unknown
cargo install --locked wasm-bindgen-cli --version 0.2.127
# The page's codec is joule-moq built without its runtime; this is the build that
# holds the feature split.
- name: joule-moq without its runtime
shell: bash
working-directory: joule-code/joule-code-rs
run: nice -n 10 cargo check --locked -p joule-moq --no-default-features
# The page's records library, natively: its co-performance reading checked against the
# corpus live-conformance/coperformance (src/co.rs) beside its own cases.
- name: wai-live-web, its own tests
shell: bash
working-directory: wai/wai-live-web
run: nice -n 10 cargo test --locked --lib
- name: The browser end-to-end tests
shell: bash
env:
BROWSER_BIN: /Applications/Google Chrome.app/Contents/MacOS/Google Chrome
MOQ_WEB_OUT: ${{ runner.temp }}/moq-web
run: |
set -euo pipefail
test -x "$BROWSER_BIN" || { echo "::error::this runner has no browser at $BROWSER_BIN"; exit 1; }
mg=/Volumes/macos_4TB_external_1/wai-build/bin/memguard
if [ -x "$mg" ]; then export MEMGUARD="$mg"; fi
nice -n 10 bash joule-code/joule-code-rs/crates/joule-moq-web/e2e/run.sh 2>&1 | tee "$RUNNER_TEMP/moq-web-e2e.log"
grep -qE '^room e2e: 1 of 1 runs passed; ' "$RUNNER_TEMP/moq-web-e2e.log" \
|| { echo "::error::the browser end-to-end run did not pass"; exit 1; }
if grep -q '^FAIL' "$RUNNER_TEMP/moq-web-e2e.log"; then echo "::error::a check failed"; exit 1; fi