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]>
260 lines
16 KiB
YAML
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
|