name: wai-web reach builds # The reach builds of wai-web (wai/wai-web/reach/): the shipped build, L1 (legalised at # the JS interface), L2 (MVP-lowered and legalised) and J (the wasm2js translation), # each run on an engine line it serves and compared with the shipped build output for # output. # # On the linux runner, after a gate that reads the pull request's own diff, one leg per # package: # # record the package of record (reach/record/: the two files the site serves under # /wai/pkg/, built from the commit reach/manifest.json names). Both files must # have the manifest's SHA-256, and build.sh runs with WAI_REACH_REQUIRE_RECORD=1, # so every output must have the manifest's SHA-256 and size: the bytes the site # serves under /wai/pkg/reach/. The package is kept, not rebuilt here: the same # commit and recipe give different wasm on a Linux x86_64 host than on the # macOS arm64 host that built it (the glue is the same). # tree wai-web built from this tree, so a change to wai-web meets the reach builds # before the package of record moves to it. # # Each leg then: # # 1. derives every reach build with reach/build.sh (pinned binaryen and JS tools; J's # wasm2js is binaryen's WebAssembly build, the same on every host), which checks that # the translation and the glue parse as ECMAScript 5 and writes each output's SHA-256 # to a manifest; # 2. on current Node (reach/test/run-modern.mjs): the loader must choose the shipped # build; every rung and profile, forced, must give the shipped build's outputs over # the corpora; an engine stripped to ES5 must be given J and the same outputs; and # the demo verify scripts must pass against shipped, L1, L2 and J. On the record leg # those scripts, and the corpora they read, are the record's commit's # (--demo-commit): the package is held to the checks of the code it was built from, # and a check added since meets that code on the tree leg; # 3. on older engine lines, each in its own Node image (reach/test/conformance.js, # which is ECMAScript 5): the loader must choose the rung that line needs, by # feature probes alone, and every workload must equal the shipped build's output # from step 2: # node:0.12 no WebAssembly -> J # node:8.0.0 WebAssembly without the RegExp dotAll flag -> J (full), L2 (receipt) # node:8 MVP WebAssembly, no BigInt -> L2 # node:10 MVP WebAssembly, BigInt -> L2 # node:12 post-MVP features, no BigInt at the boundary -> L1 # node:14 post-MVP features, no BigInt at the boundary -> L1 # These are proxies: engine lines that real devices also ship (the README says # which device classes each stands for). No real old device runs here. # # The images run as `docker://` steps: each mounts this job's workspace, so the build # and the reference of steps 1-2 are read where they were written. They run no # JavaScript action, since their Node is too old to. # # Push filters match the push's delta, so every path a step reads is listed. # One run per branch at a time. A push supersedes the run before it; on main a newer # merge's tree contains the older one's. concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true on: pull_request: paths: - 'wai/wai-web/**' - 'wai/wai-rs/**' - 'wai/wai-quantum/**' - 'wai/neural-conformance/**' - 'wai/staged-conformance/**' - 'wai/staged-measure-conformance/**' - '.github/workflows/wai-web-reach.yml' schedule: - cron: '32 4 * * *' env: REACH: wai/wai-web/reach 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) and the # build runs only when it touches them. On a push to main it answers `true`. relevant: name: wai-web reach · 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/wai-web-reach.yml \ "${{ github.event.pull_request.base.sha }}" \ "${{ github.event.pull_request.head.sha }}") echo "this change touches the reach builds: $r" echo "run=$r" >> "$GITHUB_OUTPUT" reach: name: wai-web reach builds · ${{ matrix.name }} needs: relevant if: needs.relevant.outputs.run == 'true' runs-on: linux timeout-minutes: 90 strategy: fail-fast: false matrix: include: - leg: record name: the package of record, every output as recorded - leg: tree name: this tree's wai-web, every rung on its engine line env: DIST: wai/wai-web/reach/ci-dist-${{ matrix.leg }} steps: - uses: actions/checkout@v5 # The docker runner image ships no Rust. - name: Install Rust 1.98.0 with the wasm32 target if: matrix.leg == 'tree' 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 rustup target add wasm32-unknown-unknown --toolchain 1.98.0 echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" # Pinned to the wasm-bindgen version in wai-web/Cargo.lock: the CLI and the crate # must match exactly. - name: wasm-bindgen CLI 0.2.127 if: matrix.leg == 'tree' shell: bash run: cargo install --locked wasm-bindgen-cli --version 0.2.127 - uses: actions/setup-node@v4 with: node-version: '26' # The site's package is wasm-pack's web target with wasm-opt off # ([package.metadata.wasm-pack.profile.release]): cargo, then wasm-bindgen. It goes # outside the tree, where no package.json decides how Node reads the glue. - name: Build wai-web for wasm32 from this tree (web target) if: matrix.leg == 'tree' shell: bash working-directory: wai/wai-web run: | set -euo pipefail cargo build --locked --release --target wasm32-unknown-unknown wasm-bindgen --target web --out-dir "$RUNNER_TEMP/pkg" --out-name wai_web \ target/wasm32-unknown-unknown/release/wai_web.wasm # The package of record leaves the tree for the same reason, after its check. - name: The package of record (reach/record/), held to the manifest if: matrix.leg == 'record' shell: bash run: | set -euo pipefail node -e ' const fs = require("fs"), path = require("path"), crypto = require("crypto"); const [man, pkg] = process.argv.slice(1); const m = JSON.parse(fs.readFileSync(man, "utf8")); console.log(`the package of record: ${m.input.source}`); let bad = 0; for (const [n, e] of Object.entries(m.input.files)) { const b = fs.readFileSync(path.join(pkg, n)); const got = crypto.createHash("sha256").update(b).digest("hex"); console.log(`${got} ${n} (${b.length} B); the manifest records ${e.sha256} (${e.bytes} B)`); if (got !== e.sha256 || b.length !== e.bytes) bad++; } if (bad) { console.error(`reach: reach/record/ is not the package the manifest names (${bad} file(s) differ)`); process.exit(1); }' \ "$REACH/manifest.json" "$REACH/record" mkdir -p "$RUNNER_TEMP/pkg" cp "$REACH/record/wai_web_bg.wasm" "$REACH/record/wai_web.js" "$RUNNER_TEMP/pkg/" # The commit the package was built from (input.commit): its demo verify scripts # are the ones run-modern.mjs runs on this leg. The checkout is shallow. c=$(node -p 'JSON.parse(require("fs").readFileSync(process.argv[1], "utf8")).input.commit' "$REACH/manifest.json") git fetch --no-tags --depth=1 origin "$c" echo "RECORD_COMMIT=$c" >> "$GITHUB_ENV" # The build and the reference stay in the workspace: the engine images below # mount it, and nothing else. - name: Reach builds (reach/build.sh, pinned binaryen and JS tools) shell: bash run: | set -euo pipefail if [ "${{ matrix.leg }}" = record ]; then export WAI_REACH_REQUIRE_RECORD=1; fi WAI_REACH_TOOLS="$RUNNER_TEMP/reach-tools" bash "$REACH/build.sh" "$RUNNER_TEMP/pkg" "$DIST" cat "$DIST/manifest.json" # The demo verify scripts: on the record leg, the record's commit's, over that # commit's corpora; on the tree leg, this tree's. - name: Current Node — every rung forced, the demo verify scripts on every full build shell: bash run: | set -euo pipefail set -- if [ "${{ matrix.leg }}" = record ]; then set -- --demo-commit "$RECORD_COMMIT"; fi node "$REACH/test/run-modern.mjs" --dist "$DIST" --shipped "$RUNNER_TEMP/pkg" --out "$REACH/ci-modern-${{ matrix.leg }}" "$@" - name: node:0.12 (no WebAssembly) — J chosen and equal to the shipped build uses: docker://node:0.12 with: entrypoint: /bin/sh args: -c "set -e; node --version; for p in full verify_decode verify receipt; do node wai/wai-web/reach/test/conformance.js --dist wai/wai-web/reach/ci-dist-${{ matrix.leg }} --wai wai --profile $p --expect-rung J --reference wai/wai-web/reach/ci-modern-${{ matrix.leg }}/reference.json; done" - name: node:8.0.0 (WebAssembly, no RegExp dotAll) — J for the full build, the L2 receipt build uses: docker://node:8.0.0 with: entrypoint: /bin/sh args: -c "set -e; node --version; node wai/wai-web/reach/test/conformance.js --dist wai/wai-web/reach/ci-dist-${{ matrix.leg }} --wai wai --profile full --expect-rung J --reference wai/wai-web/reach/ci-modern-${{ matrix.leg }}/reference.json; node wai/wai-web/reach/test/conformance.js --dist wai/wai-web/reach/ci-dist-${{ matrix.leg }} --wai wai --profile receipt --expect-rung L2 --reference wai/wai-web/reach/ci-modern-${{ matrix.leg }}/reference.json" - name: node:8 (MVP WebAssembly, no BigInt) — L2 chosen and equal to the shipped build uses: docker://node:8 with: entrypoint: /bin/sh args: -c "set -e; node --version; for p in full verify_decode verify receipt; do node wai/wai-web/reach/test/conformance.js --dist wai/wai-web/reach/ci-dist-${{ matrix.leg }} --wai wai --profile $p --expect-rung L2 --reference wai/wai-web/reach/ci-modern-${{ matrix.leg }}/reference.json; done; node wai/wai-web/reach/test/conformance.js --dist wai/wai-web/reach/ci-dist-${{ matrix.leg }} --wai wai --rung J --reference wai/wai-web/reach/ci-modern-${{ matrix.leg }}/reference.json" - name: node:10 (MVP WebAssembly, BigInt) — L2 chosen and equal to the shipped build uses: docker://node:10 with: entrypoint: /bin/sh args: -c "set -e; node --version; node wai/wai-web/reach/test/conformance.js --dist wai/wai-web/reach/ci-dist-${{ matrix.leg }} --wai wai --profile full --expect-rung L2 --reference wai/wai-web/reach/ci-modern-${{ matrix.leg }}/reference.json" - name: node:12 (post-MVP WebAssembly, no BigInt at the boundary) — L1 chosen and equal to the shipped build uses: docker://node:12 with: entrypoint: /bin/sh args: -c "set -e; node --version; node wai/wai-web/reach/test/conformance.js --dist wai/wai-web/reach/ci-dist-${{ matrix.leg }} --wai wai --profile full --expect-rung L1 --reference wai/wai-web/reach/ci-modern-${{ matrix.leg }}/reference.json; node wai/wai-web/reach/test/conformance.js --dist wai/wai-web/reach/ci-dist-${{ matrix.leg }} --wai wai --rung L2 --reference wai/wai-web/reach/ci-modern-${{ matrix.leg }}/reference.json" - name: node:14 (post-MVP WebAssembly, no BigInt at the boundary) — L1 chosen and equal to the shipped build uses: docker://node:14 with: entrypoint: /bin/sh args: -c "set -e; node --version; node wai/wai-web/reach/test/conformance.js --dist wai/wai-web/reach/ci-dist-${{ matrix.leg }} --wai wai --profile full --expect-rung L1 --reference wai/wai-web/reach/ci-modern-${{ matrix.leg }}/reference.json"