open-standards/.github/workflows/joule-moq-web.yml
Transaction Science beb4679dc6 Merge #493 (wai/messaging-calls @01f3aac1): wai.live.message and wai.live.call, conversations and calls on live rooms
Merged last, as asked. Conflicts, all adjacent additions, both sides kept:
- joule-moq-room: Cargo.toml's wai features (live_world, remote_desktop and
  live_messaging); lib.rs's modules (messaging, msgstore, remote), Config's
  remote, messaging and call, Service's remote, messaging and calls, the epoch
  through remote::epoch with the conversation and calls opened beside it, and
  settings()'s refusals (#482's world room, #493's conversation and call); the
  README's world, conversation and call sections.
- joule-moq-web: app.js asks a remote room's grant as #489 does and takes a
  given grant as #493 does (a remote page's fallback to viewer only when it
  asked); run.sh runs room.e2e.mjs (now with --remote) and messaging.e2e.mjs,
  each its own --only scenarios; the usage names every scenario.
- workflows: joule-moq-room.yml's and joule-moq-web.yml's accounts;
  wai-conformance.yml's WAI_FEATURES gains live_messaging (#477's union) and
  the first step's test build names live_messaging beside the train's targets.
- wai-live-web's wai features (stream_termination, live_world, live_messaging),
  wai-rs lib.rs's modules, registry.rs's capability list, REGISTRY.md's rows;
  CHANGELOG, #493's entry on top.
- capabilities.json and v2.ttl regenerated (registry export, then --check).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-26 12:23:34 -04:00

260 lines
16 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.
# termination stream termination (WAI stream-termination, capability-constraints §4):
# live-conformance/termination in the page; then a receiving page driven over
# the DevTools protocol alone, so its tab really goes to the background: after
# the grace it signs a termination as hidden and cancels its video subscription
# while the audio goes on, the log verifies on the host in wai-rs and in the
# Python verifier, a segment receipt replayed after the termination is refused,
# the video is subscribed again under a new log once the tab is in front, a
# smaller screen is declared again with each receipt's match, and a canvas out
# of the viewport ends the video as occluded.
# 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.
# world a room with a shared world (WAI live-worlds): two pages converge through the
# relay on one session hash, a late page fetches every world track from group 0,
# a viewer acts in nothing, the relay refuses what a grant does not allow, and
# its door drops a group receipted by another key.
# privateworld a private world (an end-to-end encrypted room): sealed world objects, the
# members' carries bring later members the world, a page outside the group
# opens nothing, and a member removed opens nothing after.
# messaging a conversation (WAI live-messaging) kept by a room service with a history store
# (e2e/messaging.e2e.mjs): three pages join and key through the service's log,
# message one another and open what the others seal; one page goes offline, the
# service restarts from its store, and the page catches up, the thread, the
# reaction, the edit and the retraction shown as verified; acknowledgements; a
# member removed opens nothing written after; the store holds no plaintext.
# call a call between two pages: the callee's inbox checks the invitation before it
# rings, the call is accepted and both rooms decode the other's audio and video;
# a call never answered is missed, signed by the room's clock.
#
# 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/**'
# The termination scenario runs this corpus in the page, and verifies the page's logs on
# the host with its second implementation, which reads the strict Ed25519 rule beside it.
- 'wai/live-conformance/termination/**'
- 'wai/live-conformance/stream_termination_verify.py'
- 'wai/claim-conformance/_ed25519.py'
- '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'
# wai-live-web's own tests run the live-worlds corpus's accepted world through the page's path.
- 'wai/live-conformance/worlds/**'
- '.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/**'
# The termination scenario runs this corpus in the page, and verifies the page's logs on
# the host with its second implementation, which reads the strict Ed25519 rule beside it.
- 'wai/live-conformance/termination/**'
- 'wai/live-conformance/stream_termination_verify.py'
- 'wai/claim-conformance/_ed25519.py'
- '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'
# wai-live-web's own tests run the live-worlds corpus's accepted world through the page's path.
- 'wai/live-conformance/worlds/**'
- '.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) and its stream logs against
# live-conformance/termination (src/termination_tests.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; }
grep -qE '^messaging e2e: 1 of 1 runs passed; ' "$RUNNER_TEMP/moq-web-e2e.log" \
|| { echo "::error::the messaging and call 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