name: WAI HTTP delivery # extensions/http-delivery.md: the falsification, the differential and the cache rules, # beside the corpus checks in wai-conformance.yml. # # falsify — the HTTP corpus tool and its independent verifier must catch defects. Each # patch in wai/http-conformance/falsify/ breaks one function of wai-rs (the content # hash's domain, the root flag, the left-subtree rule, the chunk counter, the component # digest check, the Content-Range and content-coding checks, ASCII-only numbers, the # union, layers' place in it and their dispatch, the wai prefix's case, duplicate # capabilities, a record beside a fallback, the trailing-byte rule, the missing-digest # plan, the class merge, the base-only threshold, refused refinement responses, stale # bytes after a mismatch, an unanswered base, the selection digest test, the component # conformance that withholds an envelope's refinements; and on the # playlist side adjacency, rendering, bandwidths, durations, the classical master, the # playlist receipt and its chain, listed cids, and the adaptation policies; and on the # receipt side the receipt file's name, legacy groups, the parent walk and its bound, # the caps on files, indexes and their items, fetching each name once within budgets # of files and bytes, what a client holds of an object, the items a claim attaches # through, §3.1 step 6 for every class and the declarations it reads by name, the # ancestor rule, its recovery and the publisher key it uses, a claim's parent, # anchoring from the origin only and the request that anchors, cached logs, grants # files and sets held by their pins); it # is applied to one worktree of the head, built once and rebuilt incrementally per # patch under the `falsify` profile (overflow checks and debug assertions on, as in the # tests), and `wai_http_vectors verify` must fail naming the cases on the patch's first # line; the patch is then reversed and the tree must be clean. Each `http_verify.py # --mutate` must exit non-zero naming the cases falsify/mutations.txt lists for it; a # crash, or a non-zero exit that names none, is a failure. Both run in shards (falsify/run.sh --patches I/N, --mutations # I/N), each printing what it ran and failing unless that is exactly what was assigned # to it; `shards` checks the shards the matrix declares cover every patch and every # mutation once, so none is dropped silently. # # differential — the corpus verified both ways, and the differential: wai-rs writes # random envelopes' covered prefixes and head proofs, the Python verifier accepts every # one, and refuses exactly the ten it changes one chaining-value byte in. # # cache-conformance — the cache rules (§9). wai/cache-conformance/run.sh stages the # corpus's objects behind the reference origin (`wai http origin`) and runs driver.py # against the origin, which must pass every MUST test, and against four negative-control # proxies, which must each fail the tests their defect reaches. Labelled caches run only # where the environment provides them (/opt/wai-cache); this repository holds none, and # the job summary says when none ran. The reports are uploaded as an artifact. # # No workflow_dispatch: it does not start runs on this forge. A weekly schedule keeps the # jobs exercised between changes. concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true on: pull_request: paths: - 'wai/wai-rs/src/http_binding/**' - 'wai/wai-rs/src/container.rs' - 'wai/wai-rs/src/jwp_hash.rs' - 'wai/wai-rs/src/bin/wai.rs' - 'wai/wai-rs/src/bin/wai_http_vectors.rs' - 'wai/wai-rs/src/bin/wai_cli/http.rs' - 'wai/wai-rs/src/bin/wai_cli/http_vectors.rs' - 'wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs' - 'wai/wai-rs/src/bin/wai_cli/http_vectors_receipts.rs' - 'wai/wai-rs/src/bin/wai_cli/http_playlists.rs' # Receipts, keys, grants and sets over HTTP (§8.1–§8.5) are checked by the receipt, # claim, key-log and pin code, and on the Python side by their own verifiers. - 'wai/wai-rs/src/jwp_delivery.rs' - 'wai/wai-rs/src/claim_receipt.rs' - 'wai/wai-rs/src/claim_receipt/**' - 'wai/wai-rs/src/key_log.rs' - 'wai/wai-rs/src/pin.rs' # ...and by what those read with: JCS, signer identities, step 6's figures and # classes, and the OER receipts an energy claim names. - 'wai/wai-rs/src/jcs.rs' - 'wai/wai-rs/src/signer_id.rs' - 'wai/wai-rs/src/energy_class.rs' - 'wai/wai-rs/src/energy_figure.rs' - 'wai/wai-rs/src/energy_binding.rs' - 'wai/wai-rs/src/energy_report.rs' - 'wai/wai-rs/src/claim_text.rs' - 'wai/wai-rs/src/oer.rs' - 'wai/jwp-conformance/jwp_verify.py' - 'wai/claim-conformance/claim_verify.py' - 'wai/signer-conformance/signer_verify.py' - 'wai/oer-conformance/oer2_verify.py' # The playlists render through hls_render and are sealed by wai_video_manifest. - 'wai/wai-rs/src/hls_render.rs' - 'wai/wai-rs/src/wai_video_manifest.rs' # Playlist receipts verify Ed25519 through the shared strict rule, in Rust and Python. - 'wai/wai-rs/src/sig.rs' - 'wai/claim-conformance/_ed25519.py' - 'wai/pack-conformance/requirements-verify.txt' - 'wai/http-conformance/**' - 'wai/cache-conformance/**' - 'wai/claim-conformance/_blake3.py' # http_verify.py reads staged_verify.py (and through it int_payload_verify.py), and # the corpus carries the staged ladder's base and layers. - 'wai/staged-conformance/staged_verify.py' # staged_verify.py's component rules (SPEC §7.1) read the capability registry. - 'wai/staged-conformance/cases/audio_flac_rate_ladder/**' - 'wai/int-payload-conformance/int_payload_verify.py' - '.github/workflows/wai-http.yml' schedule: - cron: '17 4 * * *' jobs: # The path filters above gate the trigger, which on this forge is the push delta: a # rebase fires them all. This gates the work, from the pull request's own diff, reading # the path list out of this file (scripts/pr-touches-paths.py); a push to main or the # schedule has no pull-request context and runs everything. relevant: name: WAI HTTP · does this change touch it runs-on: linux-arm64 outputs: run: ${{ steps.gate.outputs.run }} falsify: ${{ steps.gate.outputs.falsify }} steps: - uses: actions/checkout@v5 with: fetch-depth: 0 - id: gate run: | set -euo pipefail base="${{ github.event.pull_request.base.sha }}" head="${{ github.event.pull_request.head.sha }}" r=$(python3 scripts/pr-touches-paths.py .github/workflows/wai-http.yml "$base" "$head") echo "this change touches WAI HTTP: $r" echo "run=$r" >> "$GITHUB_OUTPUT" # The falsify shards (about an hour and a half of runner time) prove that the corpus # tool and its verifier catch defects: they matter when a pull request changes the # code the patches in http-conformance/falsify/ break, the corpus, its verifier or # this file. Any other change to the list above runs the corpus jobs but not the # shards. The nightly schedule, having no pull request, runs them all. Fails open: # a diff that cannot be read answers true. f=true if [ -n "$base" ] && [ -n "$head" ] && names=$(git diff --name-only "$base...$head"); then if grep -qE '^(wai/wai-rs/src/http_binding/|wai/http-conformance/|wai/wai-rs/src/bin/wai_http_vectors\.rs$|wai/wai-rs/src/bin/wai_cli/http|wai/wai-rs/src/(container|sig|jwp_hash|jwp_delivery|hls_render|wai_video_manifest)\.rs$|\.github/workflows/wai-http\.yml$)' <<< "$names"; then f=true else f=false fi fi echo "this change touches what the falsify shards break: $f" echo "falsify=$f" >> "$GITHUB_OUTPUT" # Every patch and every mutation runs in exactly one shard: the one whose index mod N # it is, in the sorted list. The shards job reads the list below; keep it on one line. falsify: name: HTTP corpus · falsify ${{ matrix.shard }} needs: relevant if: needs.relevant.outputs.falsify == 'true' runs-on: linux-arm64 strategy: fail-fast: false matrix: shard: ['patches 0/3', 'patches 1/3', 'patches 2/3', 'mutations 0/3', 'mutations 1/3', 'mutations 2/3'] steps: - uses: actions/checkout@v5 - name: Install Rust 1.98.0 if: startsWith(matrix.shard, 'patches') 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" - uses: actions/setup-python@v5 with: python-version: '3.11' # http_verify.py checks the receipt and key cases through the corpora's receipt, # claim and key-discovery verifiers, which check Ed25519's equation with this # package; its mutations reach those cases. - name: Install the Ed25519 library shell: bash run: python -m pip install --require-hashes -r wai/pack-conformance/requirements-verify.txt - name: Every patch or mutation of this shard is caught shell: bash env: CARGO_TARGET_DIR: ${{ runner.temp }}/falsify-target SHARD: ${{ matrix.shard }} run: | git config --global --add safe.directory "$GITHUB_WORKSPACE" set -- $SHARD bash wai/http-conformance/falsify/run.sh "$RUNNER_TEMP/falsify" "--$1" "$2" # The shards the matrix above declares cover every patch and every mutation exactly # once: a shard dropped from the list, or a total that disagrees, fails here. shards: name: HTTP corpus · every patch and mutation in exactly one shard needs: relevant if: needs.relevant.outputs.run == 'true' runs-on: linux-arm64 steps: - uses: actions/checkout@v5 - shell: bash run: | set -euo pipefail line=$(grep -E "^ +shard: \[" .github/workflows/wai-http.yml) mapfile -t shards < <(printf '%s\n' "$line" | grep -oE "'(patches|mutations) [0-9]+/[0-9]+'" | tr -d "'") bash wai/http-conformance/falsify/run.sh "$RUNNER_TEMP/falsify" --check-shards "${shards[@]}" differential: name: HTTP corpus · verified both ways, and the differential needs: relevant if: needs.relevant.outputs.run == 'true' runs-on: linux-arm64 steps: - uses: actions/checkout@v5 - 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" - uses: actions/setup-python@v5 with: python-version: '3.11' - name: Install the Ed25519 library shell: bash run: python -m pip install --require-hashes -r wai/pack-conformance/requirements-verify.txt - name: The corpus, both ways; the differential shell: bash env: CARGO_TARGET_DIR: ${{ runner.temp }}/falsify-target run: | set -euo pipefail git config --global --add safe.directory "$GITHUB_WORKSPACE" (cd wai/wai-rs && cargo build --profile falsify --no-default-features --features http,video_manifest,key_discovery --bin wai_http_vectors) "$CARGO_TARGET_DIR/falsify/wai_http_vectors" verify wai/http-conformance (cd wai/http-conformance && python3 -I http_verify.py .) bash wai/http-conformance/falsify/run.sh "$RUNNER_TEMP/falsify" --differential cache-conformance: name: cache rules · controls and labelled caches needs: relevant if: needs.relevant.outputs.run == 'true' runs-on: linux-arm64 steps: - uses: actions/checkout@v5 - 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" - uses: actions/setup-python@v5 with: python-version: '3.11' - name: Build the reference origin working-directory: wai/wai-rs shell: bash run: cargo build --release --no-default-features --features http --bin wai # The verdict: run.sh exits non-zero when the origin fails a MUST test, a control is # not caught, or a labelled cache fails one. - name: The origin passes, the controls are caught, labelled caches report shell: bash run: WAI="$GITHUB_WORKSPACE/wai/wai-rs/target/release/wai" bash wai/cache-conformance/run.sh "$RUNNER_TEMP/cache" # The evidence in the log, whatever becomes of the upload: each report's path and # SHA-256, and the summary. - name: The reports, in the log if: always() shell: bash run: | cd "$RUNNER_TEMP/cache/reports" 2>/dev/null || { echo "no reports were written"; exit 0; } for f in *; do printf '%s %s\n' "$(sha256sum "$f" | cut -d' ' -f1)" "$RUNNER_TEMP/cache/reports/$f"; done cat summary.md 2>/dev/null || true # An upload this forge cannot take must not decide the job: the verdict is the step # above it. - name: Upload the reports if: always() continue-on-error: true uses: actions/upload-artifact@v3 with: name: cache-conformance-reports path: ${{ runner.temp }}/cache/reports