name: joule-code conformance gate # Enforces the Joule Code conformance contract on every change: the reference # library must build and pass its tests, and the published C1–C4 wire vectors # must round-trip (SPEC §8). "Conformant = round-trips the vectors" — checked, # not asserted. # One run per branch at a time. A push supersedes the run before it. # # There is one `macos` runner and it is the dev Mac, so every macOS job in this # repo queues on it. With no concurrency group, a session pushing four versions # of a branch queued four complete sets of runs and every one of them had to # drain: on 2026-10-05 a PR's 21 contexts settled none in 70 minutes behind a # backlog of 25 runs, and the queue grew faster than it cleared. Only the newest # commit's verdict is wanted, so the older runs are cancelled. # # Verified on this forge (Forgejo 16.0.5) in `ci-test` before being added here: # the key parses, and a newer push moves an older run to `cancelled` even while # it is still queued, which is the case that matters. # # `cancel-in-progress: true` also applies to `main`. That is deliberate and # safe here: main advances by merge commits whose tree contains the commits # before them, so the newer run covers the older one's changes. concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true on: pull_request: paths: - 'joule-code/**' # joule-moq's receipt tests pin leaves, roots and signatures copied from WAI's # jwp-conformance corpus, and run every leaf, group and object vector in it: # a change there must re-run them. - 'wai/jwp-conformance/**' # joule-moq's tests/staged_moq_trace.rs runs the MoQ trace of # wai/staged-conformance/moq/ over the envelopes of one of its cases. - 'wai/staged-conformance/**' # joule-moq-ingest's tests/room_corpus.rs holds its reading of room descriptors # and grants to the live-rooms corpus. - 'wai/live-conformance/rooms/**' # joule-moq's tests/secure_objects_vectors.rs and joule-moq-mls's tests/corpus.rs # read the end-to-end encrypted rooms corpus, and the latter that extension's §13. - 'wai/live-conformance/e2ee/**' - 'wai/extensions/confidentiality.md' - '.github/workflows/joule-code-conformance.yml' push: branches: - main paths: - 'joule-code/**' # joule-moq's receipt tests pin leaves, roots and signatures copied from WAI's # jwp-conformance corpus, and run every leaf, group and object vector in it: # a change there must re-run them. - 'wai/jwp-conformance/**' # joule-moq's tests/staged_moq_trace.rs runs the MoQ trace of # wai/staged-conformance/moq/ over the envelopes of one of its cases. - 'wai/staged-conformance/**' # joule-moq-ingest's tests/room_corpus.rs holds its reading of room descriptors # and grants to the live-rooms corpus. - 'wai/live-conformance/rooms/**' # joule-moq's tests/secure_objects_vectors.rs and joule-moq-mls's tests/corpus.rs # read the end-to-end encrypted rooms corpus, and the latter that extension's §13. - 'wai/live-conformance/e2ee/**' - 'wai/extensions/confidentiality.md' - '.github/workflows/joule-code-conformance.yml' jobs: conformance: name: Build + round-trip C1-C4 vectors runs-on: linux steps: - name: Checkout uses: actions/checkout@v4 # Bootstrap rustup: the docker runner image ships no Rust, and # dtolnay/rust-toolchain cannot help — it calls `rustup`, which is absent. - name: Install Rust 1.98.0 run: | export PATH="$HOME/.cargo/bin:$PATH" command -v rustup >/dev/null 2>&1 || \ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain 1.98.0 rustup toolchain install 1.98.0 --profile minimal rustup default 1.98.0 echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" - name: Cache cargo uses: actions/cache@v4 with: path: | ~/.cargo/registry ~/.cargo/git joule-code/joule-code-rs/target key: joule-code-${{ runner.os }}-${{ hashFiles('joule-code/joule-code-rs/Cargo.lock') }} # joule-code-remediate's formal gate shells out to `z3`, and the runner # image has no solver. Its tests assert in both branches. With a solver # installed here, JOULE_FIX_EXPECT_Z3=1 makes its absence a failure, so # this gate runs the real-solver branch rather than the fail-closed one. - name: Install z3 shell: bash run: | SUDO=""; [ "$(id -u)" -ne 0 ] && SUDO="sudo" $SUDO apt-get update -qq $SUDO apt-get install -y -qq z3 z3 --version # joule-moq-relay and joule-moq-ingest run in jobs of their own below, on # linux-arm64: they need nothing x86-specific, and this runner is the one there is # least of. - name: Reference library tests working-directory: joule-code/joule-code-rs env: JOULE_FIX_EXPECT_Z3: '1' run: cargo test --workspace --exclude joule-moq-relay --exclude joule-moq-ingest # joule-moq's QUIC transport and its example compile only with `quic`. Named # here so they stay built and tested whatever the workspace's other members # happen to enable; this also runs tests/wai_jwp_corpus.rs, every leaf, group # and object vector of wai/jwp-conformance through the crate. - name: joule-moq with the QUIC transport, every target working-directory: joule-code/joule-code-rs run: cargo test -p joule-moq --features quic --all-targets # End-to-end encrypted rooms (WAI extensions/confidentiality.md). joule-moq's sealed # objects, named so they stay built and tested whatever the workspace's other members # enable: RFC 9605's and draft-ietf-moq-secure-objects-01's published vectors, and # the receiving checks. Then joule-moq-mls: real MLS groups (a room of three each # sending, a removal, a late joiner, a change of committer, and every misbehaviour # refused), and the corpus of wai/live-conformance/e2ee regenerating byte for byte # with every vector's outcome, and members behind a log that orders their messages # (confidentiality §15.2). The relay's own job runs tests/sealed_objects.rs. - name: Sealed objects and the room's MLS binding working-directory: joule-code/joule-code-rs run: | set -e cargo test -p joule-moq --features secure-objects --lib secure_objects cargo test -p joule-moq --features secure-objects --test secure_objects_vectors cargo test -p joule-moq-mls cargo run -q -p joule-moq-mls --bin joule_moq_e2ee_vectors verify ../../wai/live-conformance/e2ee # The browser member's surface (joule-moq-web, feature e2ee), on the host: its # tests run only with the feature. cargo test -p joule-moq-web --features e2ee --lib # joule-moq's draft-ietf-moq-transport-22 vectors, read by a second decoder # written from the draft's text rather than from the crate. The step above # (tests/draft22_vectors.rs) holds the crate to the same corpus and fails if # the committed corpus is not what its generator makes. This one must read # every good vector as stated and refuse every negative one as stated; it # fails on an empty corpus. - name: Ensure python3 is available shell: bash run: | if ! command -v python3 >/dev/null 2>&1; then SUDO=""; [ "$(id -u)" -ne 0 ] && SUDO="sudo" $SUDO apt-get update -qq $SUDO apt-get install -y -qq python3 fi python3 --version - name: joule-moq draft-22 vectors, independent decoder run: python3 -I joule-code/joule-code-rs/crates/joule-moq/tests/vectors/draft-22/moqt22_check.py - name: Round-trip the v1 conformance pack (C1-C4) working-directory: joule-code/joule-code-rs run: | set -e out="$(cargo run -q -p joule-code-conformance -- verify ../conformance/v1)" echo "$out" # The pack must pass with zero failures. The summary line reads # "N passed, F failed, S skipped"; anchor on it, so that "10 failed" can never # read as "0 failed", and require at least one pass, so an empty pack fails. echo "$out" | grep -Eq '^[1-9][0-9]* passed, 0 failed, ' || { echo "::error::conformance pack reported failures"; exit 1; } - name: L3 JouleClaw binding example builds + tests working-directory: joule-code/examples/l3-jouleclaw run: cargo test # The MOQT draft-22 relay. Every test is on loopback: two publishers and three # subscribers over native QUIC, WebTransport over HTTP/3 and the WebSocket fallback; a # room's sealed objects, forwarded byte for byte and checked at the door on their # receipts over the sealed bytes, opened by members and by no one else; # a slow subscriber whose late layers time out while every receipt still arrives; a # late joiner served by FETCH and a fill from the cache, and a FETCH forwarded # upstream; GOAWAY on shutdown; tokens by alias; Request IDs and the stream limit; # fan-out order under load. `--all-targets` runs the unit tests and the binary's # build as well. moq-relay: name: joule-moq-relay, every target runs-on: linux-arm64 timeout-minutes: 45 steps: - name: Checkout uses: actions/checkout@v4 - name: Install Rust 1.98.0 shell: bash run: | export PATH="$HOME/.cargo/bin:$PATH" command -v rustup >/dev/null 2>&1 || \ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain 1.98.0 rustup toolchain install 1.98.0 --profile minimal rustup default 1.98.0 echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" - name: joule-moq-relay, every target working-directory: joule-code/joule-code-rs shell: bash run: cargo test --locked -p joule-moq-relay --all-targets # The relay's WebTransport layer, wtransport 0.7.2 vendored with a session close # (vendor/wtransport/VENDORED.md): its unit tests, the WT_CLOSE_SESSION capsule's bytes # against the layout of draft-ietf-webtrans-http3-16 §6 among them. It is not a member # of the workspace, so `--all-targets` above does not reach it. - name: The vendored wtransport's unit tests working-directory: joule-code/joule-code-rs shell: bash run: cargo test --locked -p wtransport --lib # The WHIP and WHEP gateway. Every test is on loopback, against a relay that checks # every receipt at its door: a WHIP publisher (str0m as the client) of H.264 and Opus # whose frames a relay subscriber receives whole with every receipt verified, and DELETE # ending the tracks; a WHEP player that gets the same frames from the group it joined; # VP8, VP9 and AV1 through WHIP; the refusals of RFC 9725 and RFC 6750; trickle ICE and # an ICE restart with the media carried across it; and the room reader against the # live-rooms corpus. `--all-targets` runs the unit tests and builds the binary. moq-ingest: name: joule-moq-ingest, every target runs-on: linux-arm64 timeout-minutes: 45 steps: - name: Checkout uses: actions/checkout@v4 - name: Install Rust 1.98.0 shell: bash run: | export PATH="$HOME/.cargo/bin:$PATH" command -v rustup >/dev/null 2>&1 || \ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain 1.98.0 rustup toolchain install 1.98.0 --profile minimal rustup default 1.98.0 echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" - name: joule-moq-ingest, every target working-directory: joule-code/joule-code-rs shell: bash run: cargo test --locked -p joule-moq-ingest --all-targets