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. # conform the page web/binding/sink.html as a browser sink through WAI's conformance # runner (wai conform sink --suite browser, e2e/conform_sink.mjs): every call of # live-conformance/browser answered by the page's wasm and its side table, the # record signed and admitted, and a planted page that shows a refused frame as # verified refused at that case. # # 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: gate 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