name: wai command line release # On a tag wai-v (the version wai/wai-rs/Cargo.toml states), builds the quickstart # archive (wai/quickstart/build.sh) on each runner and attaches it to the forge release of that # tag, each archive with its SHA-256. An archive holds bin/ (wai with the feature quickstart, # wai-relay, wai-room, wai-ingest, wai-remote-host, wai-pack, wai-quantum), share/wai/web/ (the # room client) and share/wai/studio/ (WAI Studio, which `wai dev` serves at /_wai/), both built # once, on the macOS host, which has the WebAssembly toolchain and Node: # macOS aarch64-apple-darwin, and x86_64-apple-darwin on the Intel Mac (macos-x86_64); # Linux x86_64- and aarch64-unknown-linux-musl, static, so any distribution runs it; # Windows x86_64-pc-windows-msvc, as a zip. # The first job checks the tag against the crate's version and the installer's, and that each # project file schema (wai/schema/) is served at its $id as committed: every project file the # release writes names its schema there, so a tag does not build until the site serves it. # Before it is attached, each archive runs the quickstart as a new user runs it, from the # archive alone on a fresh home directory and a PATH of the system's own directories # (wai/quickstart/test/clean-machine.sh; on Windows its PowerShell twin, clean-machine.ps1): # the archive checked against its SHA-256 and unpacked, what it holds checked (every binary, # each of which runs, the room client and WAI Studio), then wai hello, wai verify, # wai create my-room --template video-room and wai dev --check in the project, Studio at /_wai/ # among what it checks; and on arm64 macOS, the project's page joined in two browser # pages, each decoding the other. A cross-built archive, whose binaries this host cannot run, # is not run. `wai doctor --json` from the unpacked archive, its bin first on PATH, must pass # every install and stack check: this wai, the relay and the room service beside it and of its # version, the room client's and Studio's files (wai/quickstart/test/archive_doctor.py). The # ports and browsers depend on the runner: they are printed and not required. On macOS this # runs where the triple is the host's. # A last job writes the Homebrew formula and the Scoop manifest for the archives attached and # attaches them too; they go into the tap and the bucket by hand. # # Beside the command line, the release carries WAI for the languages and for containers: # C libwai--.tar.gz from each Linux job, for x86_64- and # aarch64-unknown-linux-gnu: lib/libwai.a (wai-rs, feature native_sink), the # committed include/wai.h and lib/pkgconfig/wai.pc (wai/quickstart/build-libwai.sh), # attached once a C program has compiled, linked and run against it through pkg-config; # Swift WaiFFI-.xcframework.zip and wai-sink-swift-.zip, the package whose # Package.swift names that zip by URL and checksum (job swift); # Kotlin wai-sink-.aar, and science.transaction.wai:wai-sink: in the forge's # Maven registry (job android); # OCI transaction-science/wai-relay: and transaction-science/wai-room: in # the forge's container registry (job images, on a runner labelled oci). # The forge's job token cannot write the organisation's packages (it reads them only), so the # Maven publication and the images need the owner's setup, and run only once it exists: # 1. the repository secret WAI_PACKAGES_TOKEN, a forge token with the scope write:package: the # Maven publication and the images use it; # 2. a runner labelled oci, whose jobs reach a container engine; once it serves, the # repository variable WAI_OCI_RUNNER set to true: the images job runs only then. # Job setup says which exists (a job's `if:` cannot read a secret): without the secret the # Android library is still built, checked and attached, its publication step says so in one notice # and succeeds, and the images job is skipped; without the variable the images job is skipped, # never queued for a runner that does not exist. Forgejo evaluates the images job's `if:` on the # server once its needs are done, with the `vars` context and their outputs. # # The release also carries WAI's own conformance results, wai-conformance-.tar.gz: each # suite's record (conform-result/1) of WAI's implementations run through the conformance runner # at the tag, signed with WAI's key and admitted against the tag's corpus, which the WAI site's # list of conforming implementations admits (job conformance). It runs once the owner's # repository secret WAI_CONFORMANCE_SEED holds WAI's conformance key, a 32-byte Ed25519 seed in # hexadecimal; job setup reports it as for the token, and without it the job is skipped. # # Uploads go to the forge directly over the private network (FORGE_API), never through the # public tunnel. A runner that is down leaves its platform's archive missing: the release lists # what each build attached. concurrency: group: ${{ github.workflow }}-${{ github.event_name == 'push' && github.sha || github.ref }} cancel-in-progress: true on: push: tags: ['wai-v*'] permissions: contents: write env: CARGO_TERM_COLOR: always FORGE_API: http://100.64.224.0:3400/api/v1 FORGE_REPO: Transaction-Science/open-standards PUBLIC: https://git.transaction.science/Transaction-Science/open-standards jobs: version: name: the tag is the crate's version runs-on: linux outputs: version: ${{ steps.v.outputs.version }} steps: - uses: actions/checkout@v5 - id: v shell: bash run: | set -euo pipefail v="${GITHUB_REF_NAME#wai-v}" crate="$(sed -n 's/^version = "\(.*\)"$/\1/p' wai/wai-rs/Cargo.toml | head -1)" [ "$v" = "$crate" ] || { echo "::error::the tag says $v, wai/wai-rs/Cargo.toml says $crate"; exit 1; } grep -q "WAI_VERSION:-$v}" wai/quickstart/install.sh || { echo "::error::wai/quickstart/install.sh does not install $v"; exit 1; } # Every project file, lock and build record this release writes names its schema by URL # ($schema), which editors and agents fetch: each committed schema is served at its $id, # as committed, before a release writes a file that names it (wai/schema/README.md). for f in wai/schema/*.schema.json; do python3 - "$f" <<'PY' || exit 1 import json, sys, urllib.request f = sys.argv[1] want = json.load(open(f)) url = want["$id"] try: with urllib.request.urlopen(urllib.request.Request(url, headers={"User-Agent": "wai-release"}), timeout=30) as r: got = json.loads(r.read()) except Exception as e: sys.exit(f"::error::{url} does not serve {f} ({e}): deploy the site with the schema before the release") if got != want: sys.exit(f"::error::{url} serves a schema other than {f}: deploy the site with the schema before the release") print(f"{url} serves {f}") PY done echo "version=$v" >> "$GITHUB_OUTPUT" # The room client and WAI Studio, once: the same files on every platform. web: name: the room client and WAI Studio needs: version runs-on: macos timeout-minutes: 60 steps: - uses: actions/checkout@v5 # launchd's PATH omits cargo and node on the macOS host runner. - name: Put cargo and node on PATH shell: bash run: | echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" echo "/opt/homebrew/bin" >> "$GITHUB_PATH" - name: Build and attach them shell: bash env: FORGE_TOKEN: ${{ github.token }} run: | set -euo pipefail rustup target add wasm32-unknown-unknown command -v wasm-bindgen >/dev/null && wasm-bindgen --version | grep -q ' 0\.2\.127$' || cargo install --locked wasm-bindgen-cli --version 0.2.127 v="${{ needs.version.outputs.version }}" bash wai/quickstart/build-web.sh "$RUNNER_TEMP/web" bash wai/quickstart/build-studio.sh "$RUNNER_TEMP/studio" mkdir -p "$RUNNER_TEMP/dist" tar -C "$RUNNER_TEMP" -czf "$RUNNER_TEMP/dist/wai-web-$v.tar.gz" web studio ( cd "$RUNNER_TEMP/dist" && shasum -a 256 "wai-web-$v.tar.gz" > "wai-web-$v.tar.gz.sha256" ) bash wai/quickstart/publish.sh "$GITHUB_REF_NAME" "wai $v" "$RUNNER_TEMP"/dist/* macos: name: macOS · ${{ matrix.triple }} needs: [version, web] # Each archive on a Mac of its own architecture, so its own `wai` runs before it is # attached (the checks below run when the triple is the host's): x86_64 on the Intel Mac. runs-on: ${{ matrix.runner }} timeout-minutes: 120 strategy: fail-fast: false matrix: # `browser`: whether the host has the browser and the automation package the # quickstart's two-page step drives (the arm64 runner does, as wai-examples.yml uses them; # the x86_64 one is not set up for it). include: - { triple: aarch64-apple-darwin, runner: macos, browser: true } - { triple: x86_64-apple-darwin, runner: macos-x86_64, browser: false } steps: - uses: actions/checkout@v5 # launchd's PATH omits cargo and node on the macOS host runner, and the browser step below # installs its automation package with npm. GITHUB_PATH's last line comes first on the # PATH: rustup's toolchain stays ahead of anything Homebrew holds. - name: Put cargo and node on PATH shell: bash run: | echo "/opt/homebrew/bin" >> "$GITHUB_PATH" echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" - name: Build, check and package shell: bash run: | set -euo pipefail v="${{ needs.version.outputs.version }}" rustup target add "${{ matrix.triple }}" curl -fsS -o "$RUNNER_TEMP/web.tar.gz" "${FORGE_API%/api/v1}/$FORGE_REPO/releases/download/$GITHUB_REF_NAME/wai-web-$v.tar.gz" tar -C "$RUNNER_TEMP" -xzf "$RUNNER_TEMP/web.tar.gz" nice -n 10 bash wai/quickstart/build.sh "$RUNNER_TEMP/dist" --web "$RUNNER_TEMP/web" --studio "$RUNNER_TEMP/studio" --target "${{ matrix.triple }}" --version "$v" if [ "${{ matrix.triple }}" = "$(rustc -vV | sed -n 's/^host: //p')" ]; then t="$RUNNER_TEMP/try"; mkdir -p "$t"; tar -C "$t" -xzf "$RUNNER_TEMP/dist/wai-$v-${{ matrix.triple }}.tar.gz" python3 -I wai/quickstart/test/archive_doctor.py "$t/wai-$v-${{ matrix.triple }}" fi - name: The quickstart from the archive, as a new user runs it shell: bash env: BROWSER_BIN: /Applications/Google Chrome.app/Contents/MacOS/Google Chrome # allow-name: the macOS runner's browser executable, a file path run: | set -euo pipefail v="${{ needs.version.outputs.version }}" if [ "${{ matrix.triple }}" != "$(rustc -vV | sed -n 's/^host: //p')" ]; then echo "cross-built for ${{ matrix.triple }}: this host cannot run it" exit 0 fi flag="" if [ "${{ matrix.browser }}" = true ]; then test -x "$BROWSER_BIN" || { echo "::error::this runner has no browser at $BROWSER_BIN"; exit 1; } pw="$RUNNER_TEMP/automation"; mkdir -p "$pw" cp wai/quickstart/test/package.json wai/quickstart/test/package-lock.json "$pw/" (cd "$pw" && npm ci --ignore-scripts --no-audit --no-fund > /dev/null) export PLAYWRIGHT_HOST="$pw/node_modules" flag=--browser fi bash wai/quickstart/test/clean-machine.sh "$RUNNER_TEMP/dist/wai-$v-${{ matrix.triple }}.tar.gz" $flag - name: Attach to the release shell: bash env: FORGE_TOKEN: ${{ github.token }} run: bash wai/quickstart/publish.sh "$GITHUB_REF_NAME" "wai ${{ needs.version.outputs.version }}" "$RUNNER_TEMP"/dist/* linux: name: Linux · ${{ matrix.triple }} needs: [version, web] runs-on: ${{ matrix.runner }} timeout-minutes: 120 strategy: fail-fast: false matrix: include: - runner: linux triple: x86_64-unknown-linux-musl - runner: linux-arm64 triple: aarch64-unknown-linux-musl steps: - uses: actions/checkout@v5 - name: Install Rust 1.99.1, the musl target and musl-gcc 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.99.1 rustup toolchain install 1.99.1 --profile minimal rustup default 1.99.1 rustup target add "${{ matrix.triple }}" SUDO=""; [ "$(id -u)" -ne 0 ] && SUDO="sudo" $SUDO apt-get update -qq $SUDO apt-get install -y -qq musl-tools python3 curl echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" - name: Build, check and package shell: bash run: | set -euo pipefail v="${{ needs.version.outputs.version }}" t="${{ matrix.triple }}" T="$(echo "$t" | tr 'a-z-' 'A-Z_')" # Static, as the quantum command line is built (wai-quantum-cli-release.yml). export "CC_${t//-/_}=musl-gcc" "CARGO_TARGET_${T}_LINKER=musl-gcc" export RUSTFLAGS="-C target-feature=+crt-static -C relocation-model=static" curl -fsS -o "$RUNNER_TEMP/web.tar.gz" "${FORGE_API%/api/v1}/$FORGE_REPO/releases/download/$GITHUB_REF_NAME/wai-web-$v.tar.gz" tar -C "$RUNNER_TEMP" -xzf "$RUNNER_TEMP/web.tar.gz" bash wai/quickstart/build.sh "$RUNNER_TEMP/dist" --web "$RUNNER_TEMP/web" --studio "$RUNNER_TEMP/studio" --target "$t" --version "$v" d="$RUNNER_TEMP/try"; mkdir -p "$d"; tar -C "$d" -xzf "$RUNNER_TEMP/dist/wai-$v-$t.tar.gz" python3 -I wai/quickstart/test/archive_doctor.py "$d/wai-$v-$t" - name: The quickstart from the archive, as a new user runs it shell: bash run: bash wai/quickstart/test/clean-machine.sh "$RUNNER_TEMP/dist/wai-${{ needs.version.outputs.version }}-${{ matrix.triple }}.tar.gz" - name: Attach to the release shell: bash env: FORGE_TOKEN: ${{ github.token }} run: bash wai/quickstart/publish.sh "$GITHUB_REF_NAME" "wai ${{ needs.version.outputs.version }}" "$RUNNER_TEMP"/dist/* # libwai for C, for this runner's glibc target and without crt-static (a step of its own, # so the static musl flags above do not reach it): the header the build regenerates must be # the committed one, the library must hold exactly the envelope's and the native sink's # functions, and wai/quickstart/test/c-library.sh compiles a C program against the archive # through pkg-config, links it and runs it, all before it is attached. - name: libwai for C, and a C program linked against it through pkg-config shell: bash run: | set -euo pipefail SUDO=""; [ "$(id -u)" -ne 0 ] && SUDO="sudo" command -v pkg-config >/dev/null || { $SUDO apt-get update -qq; $SUDO apt-get install -y -qq pkg-config; } g="$(echo "${{ matrix.triple }}" | sed 's/-musl$/-gnu/')" bash wai/quickstart/build-libwai.sh "$RUNNER_TEMP/dist-c" --target "$g" --version "${{ needs.version.outputs.version }}" - name: Attach libwai to the release shell: bash env: FORGE_TOKEN: ${{ github.token }} run: bash wai/quickstart/publish.sh "$GITHUB_REF_NAME" "wai ${{ needs.version.outputs.version }}" "$RUNNER_TEMP"/dist-c/* windows: name: Windows · x86_64-pc-windows-msvc needs: [version, web] runs-on: windows timeout-minutes: 120 env: CI: "" steps: - uses: actions/checkout@v5 - name: Build, check and package shell: pwsh run: | $ErrorActionPreference = 'Stop' Remove-Item Env:CI -ErrorAction SilentlyContinue $v = "${{ needs.version.outputs.version }}" $t = "x86_64-pc-windows-msvc" $web = Join-Path $env:RUNNER_TEMP 'web.tar.gz' curl.exe -fsS -o $web "$($env:FORGE_API -replace '/api/v1$','')/$env:FORGE_REPO/releases/download/$env:GITHUB_REF_NAME/wai-web-$v.tar.gz" tar -xzf $web -C $env:RUNNER_TEMP $name = "wai-$v-$t" $stage = Join-Path $env:RUNNER_TEMP $name $dist = Join-Path $env:RUNNER_TEMP 'dist' Remove-Item $stage, $dist -Recurse -Force -ErrorAction Ignore New-Item -ItemType Directory -Force -Path (Join-Path $stage 'bin'), (Join-Path $stage 'share\wai'), $dist | Out-Null $builds = @( @{ dir = 'wai\wai-rs'; bin = 'wai'; args = @('--no-default-features', '--features', 'quickstart', '--bin', 'wai') }, @{ dir = 'joule-code\joule-code-rs'; bin = 'wai-relay'; args = @('-p', 'joule-moq-relay', '--bin', 'wai-relay') }, @{ dir = 'joule-code\joule-code-rs\crates\joule-moq-room'; bin = 'wai-room'; args = @('--bin', 'wai-room') }, @{ dir = 'joule-code\joule-code-rs'; bin = 'wai-ingest'; args = @('-p', 'joule-moq-ingest', '--bin', 'wai-ingest') }, @{ dir = 'joule-code\joule-code-rs\crates\joule-moq-remote'; bin = 'wai-remote-host'; args = @('--bin', 'wai-remote-host') }, @{ dir = 'wai\wai-pack'; bin = 'wai-pack'; args = @('--bin', 'wai-pack') }, @{ dir = 'wai\wai-quantum-cli'; bin = 'wai-quantum'; args = @('--bin', 'wai-quantum') } ) foreach ($b in $builds) { Push-Location $b.dir cargo build --locked --release --target $t @($b.args) if ($LASTEXITCODE -ne 0) { throw "the build of $($b.bin) failed" } $target = (cargo metadata --format-version 1 --no-deps --locked | ConvertFrom-Json).target_directory Copy-Item (Join-Path $target "$t\release\$($b.bin).exe") (Join-Path $stage 'bin') Pop-Location } Copy-Item -Recurse (Join-Path $env:RUNNER_TEMP 'web') (Join-Path $stage 'share\wai\web') Copy-Item -Recurse (Join-Path $env:RUNNER_TEMP 'studio') (Join-Path $stage 'share\wai\studio') Copy-Item wai\quickstart\README.md, wai\LICENSE $stage & (Join-Path $stage 'bin\wai.exe') create --list if ($LASTEXITCODE -ne 0) { throw "the packaged wai does not run" } & (Join-Path $stage 'bin\wai-quantum.exe') --version if ($LASTEXITCODE -ne 0) { throw "the packaged wai-quantum does not run" } python -I wai\quickstart\test\archive_doctor.py $stage if ($LASTEXITCODE -ne 0) { throw "wai doctor from the archive: an install or stack check did not pass" } Compress-Archive -Path $stage -DestinationPath (Join-Path $dist "$name.zip") -Force $h = (Get-FileHash (Join-Path $dist "$name.zip") -Algorithm SHA256).Hash.ToLower() Set-Content -NoNewline -Path (Join-Path $dist "$name.zip.sha256") -Value "$h $name.zip`n" # clean-machine.ps1 has run under PowerShell 7 on a macOS archive, without its contents step # and its check of WAI Studio. No Windows archive has been through it. - name: The quickstart from the archive, as a new user runs it shell: pwsh run: | $ErrorActionPreference = 'Stop' Remove-Item Env:CI -ErrorAction SilentlyContinue $name = "wai-${{ needs.version.outputs.version }}-x86_64-pc-windows-msvc" pwsh -NoProfile -File wai\quickstart\test\clean-machine.ps1 (Join-Path $env:RUNNER_TEMP "dist\$name.zip") if ($LASTEXITCODE -ne 0) { throw "the quickstart failed from $name.zip" } - name: Attach to the release shell: pwsh env: FORGE_TOKEN: ${{ github.token }} run: | $ErrorActionPreference = 'Stop' $H = @{ Authorization = "token $env:FORGE_TOKEN" } $base = "$env:FORGE_API/repos/$env:FORGE_REPO/releases" $rel = Invoke-RestMethod -Headers $H -Uri "$base/tags/$env:GITHUB_REF_NAME" foreach ($f in Get-ChildItem (Join-Path $env:RUNNER_TEMP 'dist')) { foreach ($a in (Invoke-RestMethod -Headers $H -Uri "$base/$($rel.id)/assets" | Where-Object { $_.name -eq $f.Name })) { Invoke-RestMethod -Headers $H -Method Delete -Uri "$base/$($rel.id)/assets/$($a.id)" | Out-Null } curl.exe -fsS -H "Authorization: token $env:FORGE_TOKEN" -X POST "$base/$($rel.id)/assets?name=$($f.Name)" -F "attachment=@$($f.FullName);type=application/octet-stream" -o NUL -w "uploaded $($f.Name): http %{http_code}`n" if ($LASTEXITCODE -ne 0) { throw "the upload of $($f.Name) failed" } } formulae: name: Homebrew formula and Scoop manifest needs: [version, macos, linux, windows] if: always() && needs.version.result == 'success' runs-on: linux steps: - uses: actions/checkout@v5 - name: Write and attach them shell: bash env: FORGE_TOKEN: ${{ github.token }} run: | set -euo pipefail v="${{ needs.version.outputs.version }}" out="$RUNNER_TEMP/formulae"; mkdir -p "$out" python3 wai/quickstart/formulae.py "$v" "$GITHUB_REF_NAME" "$FORGE_API" "$FORGE_REPO" "$PUBLIC" "$out" bash wai/quickstart/publish.sh "$GITHUB_REF_NAME" "wai $v" "$out"/* # The Swift package's binary target: WaiFFI.xcframework with every slice (wai-sink-swift.yml # builds the same on each change), zipped, with a copy of the package whose Package.swift names # the zip by its download URL and SwiftPM checksum (scripts/package-release.sh). Before they are # attached, an application that depends on the copy is resolved, built and run, the framework # served from this job's files through a mirror of the URL; after, the same from the URL itself. # Each time, the copy with a wrong checksum must be refused (scripts/check-release-package.sh). swift: name: the Swift package · WaiFFI.xcframework needs: version runs-on: macos timeout-minutes: 120 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" - name: The targets the slices need shell: bash run: | set -euo pipefail rustup target add aarch64-apple-darwin x86_64-apple-darwin aarch64-apple-ios aarch64-apple-ios-sim x86_64-apple-ios rustup component list --toolchain nightly-2026-10-17 --installed | grep -q '^rust-src' \ || rustup toolchain install nightly-2026-10-17 --profile minimal --component rust-src - name: WaiFFI.xcframework, every slice shell: bash working-directory: wai/wai-sink-swift run: nice -n 10 bash scripts/build-xcframework.sh - name: The package, resolved, built and run as an application uses it, before it is attached shell: bash working-directory: wai/wai-sink-swift run: | set -euo pipefail v="${{ needs.version.outputs.version }}" bash scripts/package-release.sh "$v" "$PUBLIC/releases/download/$GITHUB_REF_NAME" "$RUNNER_TEMP/swift" bash scripts/check-release-package.sh "$RUNNER_TEMP/swift" "$v" --local - name: Attach to the release shell: bash env: FORGE_TOKEN: ${{ github.token }} run: bash wai/quickstart/publish.sh "$GITHUB_REF_NAME" "wai ${{ needs.version.outputs.version }}" "$RUNNER_TEMP"/swift/* - name: The attached package, resolved from its URL shell: bash working-directory: wai/wai-sink-swift run: bash scripts/check-release-package.sh "$RUNNER_TEMP/swift" "${{ needs.version.outputs.version }}" # The Android library: libwai and the JNI layer for each ABI with the pinned NDK, and the AAR, # on the host with the platform SDK (wai-sink-android.yml builds the same on each change). # maven-publish writes science.transaction.wai:wai-sink: to a repository on disk, # held to scripts/check-publication.sh (the coordinates, the manifest's package, each ABI's two # libraries, the classes); the AAR is attached, then the publication goes to the forge's Maven # registry over the private network, with the secret WAI_PACKAGES_TOKEN. android: name: the Android library · science.transaction.wai:wai-sink needs: version 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" - name: The targets the ABIs need shell: bash run: rustup target add aarch64-linux-android armv7-linux-androideabi x86_64-linux-android - name: libwai and the JNI layer for each ABI shell: bash working-directory: wai/wai-sink-android run: nice -n 10 bash scripts/build-native.sh android - name: The publication, written to a repository on disk and checked shell: bash working-directory: wai/wai-sink-android run: | set -euo pipefail v="${{ needs.version.outputs.version }}" rm -rf sink/build/staging-repo nice -n 10 bash scripts/gradle.sh -Pwai.version="$v" :sink:publishReleasePublicationToStagingRepository bash scripts/check-publication.sh sink/build/staging-repo "$v" mkdir -p "$RUNNER_TEMP/aar" cp "sink/build/staging-repo/science/transaction/wai/wai-sink/$v/wai-sink-$v.aar" "$RUNNER_TEMP/aar/" ( cd "$RUNNER_TEMP/aar" && shasum -a 256 "wai-sink-$v.aar" > "wai-sink-$v.aar.sha256" ) - name: Attach the AAR to the release shell: bash env: FORGE_TOKEN: ${{ github.token }} run: bash wai/quickstart/publish.sh "$GITHUB_REF_NAME" "wai ${{ needs.version.outputs.version }}" "$RUNNER_TEMP"/aar/* # Only with the owner's secret (the release workflow's header): without it, one notice, and # the step succeeds; the AAR above is attached either way. - name: Publish to the forge's Maven registry shell: bash working-directory: wai/wai-sink-android env: WAI_PACKAGES_TOKEN: ${{ secrets.WAI_PACKAGES_TOKEN }} run: | set -euo pipefail if [ -z "${WAI_PACKAGES_TOKEN:-}" ]; then echo "::notice::the repository secret WAI_PACKAGES_TOKEN is not set: science.transaction.wai:wai-sink is not published to the forge's Maven registry (the AAR is attached)" exit 0 fi nice -n 10 bash scripts/gradle.sh -Pwai.version="${{ needs.version.outputs.version }}" \ -Pwai.maven.url="${FORGE_API%/api/v1}/api/packages/Transaction-Science/maven" :sink:publishReleasePublicationToForgeRepository # The relay's and the room service's OCI images, from their Containerfiles (deploy/ beside each # crate): built from the tag, each held to its check-image.sh (the room service's runs beside # the relay's, its clock connected), then pushed over the private network to the forge's # container registry as transaction-science/wai-relay: and wai-room:, for the # platform of the runner's engine (wai/quickstart/release-images.sh); an image that fails its # check is not pushed. It needs a runner whose jobs reach a container engine (docker or # podman), labelled oci: the linux runners' jobs reach none. It runs only once the owner's # setup exists (the header): the secret, which job setup reports, and the variable # WAI_OCI_RUNNER set to true; else the server skips it, so it is never queued for a runner that # does not exist. Once it runs, a failure fails it. The source is fetched with git, so the # runner needs no node. images: name: OCI images · the relay and the room service · oci needs: [version, setup] if: needs.version.result == 'success' && needs.setup.outputs.packages == 'true' && vars.WAI_OCI_RUNNER == 'true' runs-on: oci timeout-minutes: 120 steps: - name: Build, check and push the two images shell: bash env: OCI_TOKEN: ${{ secrets.WAI_PACKAGES_TOKEN }} run: | set -euo pipefail [ -n "${OCI_TOKEN:-}" ] || { echo "::error::the repository secret WAI_PACKAGES_TOKEN is not set: a forge token with the scope write:package pushes the images"; exit 1; } src="$RUNNER_TEMP/src"; rm -rf "$src" git clone -q --depth 1 --branch "$GITHUB_REF_NAME" "${FORGE_API%/api/v1}/$FORGE_REPO.git" "$src" cd "$src" OCI_USER=wai-release OCI_TOKEN_URL="${FORGE_API%/api/v1}/v2/token" \ bash wai/quickstart/release-images.sh "${{ needs.version.outputs.version }}" "${FORGE_API%/api/v1}" transaction-science cd "$RUNNER_TEMP" && rm -rf "$src" # WAI's own implementations through the conformance runner at the tag, each suite's record # signed with WAI's key (the repository secret WAI_CONFORMANCE_SEED) from paths a record may # publish (wai/quickstart/conformance-records.sh, the runs of wai-breaking.yml's conformance # runner job), every record admitted against the tag's corpus, and the pack attached as # wai-conformance-.tar.gz with its SHA-256: the WAI site's list of conforming # implementations fetches it by tag and admits each record (scripts/sync-conform.mjs # --release). It runs only once the secret exists, which job setup reports; else the server # skips it, and setup's notice says so. The seed reaches the step that runs and signs them and # no other, and is never printed. conformance: name: WAI's own conformance records, signed and attached · linux needs: [version, setup] if: needs.version.result == 'success' && needs.setup.outputs.conformance == 'true' runs-on: linux timeout-minutes: 90 steps: - uses: actions/checkout@v5 - name: Install Rust 1.99.1 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.99.1 rustup toolchain install 1.99.1 --profile minimal rustup default 1.99.1 echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" - uses: actions/setup-python@v5 with: python-version: '3.11' # The claim, JWP and signer verifiers check Ed25519's equation with it, and the pack reads # the seed's public key with it. - name: Install the Ed25519 library shell: bash run: python -m pip install cryptography - name: Build wai, its vector programs, the reference packager, the relay and its interop client shell: bash run: | set -euo pipefail out() { (cd "$1" && cargo metadata --format-version 1 --no-deps --locked | python3 -c 'import json,sys; print(json.load(sys.stdin)["target_directory"])'); } mkdir -p "$RUNNER_TEMP/bin" (cd wai/wai-rs && cargo build --locked --no-default-features --features quickstart,score,key_discovery,native_sink,browser_sink,neural_int,pq_signatures \ --bin wai --bin wai_native_sink_vectors --bin wai_browser_sink_vectors --bin wai_int_synth_vectors) for b in wai wai_native_sink_vectors wai_browser_sink_vectors wai_int_synth_vectors; do cp "$(out wai/wai-rs)/debug/$b" "$RUNNER_TEMP/bin/"; done (cd wai/wai-pack && cargo build --locked --bin wai-pack) && cp "$(out wai/wai-pack)/debug/wai-pack" "$RUNNER_TEMP/bin/" jc=joule-code/joule-code-rs (cd "$jc" && cargo build --locked -p joule-moq-relay --bin wai-relay -p joule-moq-interop --bin joule-moq-interop) for b in wai-relay joule-moq-interop; do cp "$(out "$jc")/debug/$b" "$RUNNER_TEMP/bin/"; done - name: Run and sign them, each admitted against the tag's corpus shell: bash env: WAI_CONFORMANCE_SEED: ${{ secrets.WAI_CONFORMANCE_SEED }} run: | set -euo pipefail bash wai/quickstart/conformance-records.sh --bin "$RUNNER_TEMP/bin" --commit "$GITHUB_SHA" \ --label "${{ needs.version.outputs.version }}" --out "$RUNNER_TEMP/conformance" - name: Attach them to the release shell: bash env: FORGE_TOKEN: ${{ github.token }} run: | set -euo pipefail v="${{ needs.version.outputs.version }}" [ -f "$RUNNER_TEMP/conformance/wai-conformance-$v.tar.gz" ] || { echo "::error::no pack was written: WAI_CONFORMANCE_SEED is empty"; exit 1; } bash wai/quickstart/publish.sh "$GITHUB_REF_NAME" "wai $v" "$RUNNER_TEMP/conformance/wai-conformance-$v.tar.gz" "$RUNNER_TEMP/conformance/wai-conformance-$v.tar.gz.sha256" # Which of the owner's setup for the registry legs and the conformance records exists (the # header): the secrets WAI_PACKAGES_TOKEN and WAI_CONFORMANCE_SEED, each reported as an output # that says only whether it is set, since a job's `if:` cannot read a secret; and the variable # WAI_OCI_RUNNER. Each piece that is missing is one notice line, and the job succeeds. It # compiles nothing, so it runs in the gate pool. setup: name: the owner's setup for the registry legs and the conformance records runs-on: gate outputs: packages: ${{ steps.setup.outputs.packages }} conformance: ${{ steps.setup.outputs.conformance }} steps: - id: setup shell: bash env: TOKEN: ${{ secrets.WAI_PACKAGES_TOKEN }} SEED: ${{ secrets.WAI_CONFORMANCE_SEED }} OCI_RUNNER: ${{ vars.WAI_OCI_RUNNER }} run: | set -euo pipefail if [ -n "${TOKEN:-}" ]; then echo "packages=true" >> "$GITHUB_OUTPUT" else echo "packages=false" >> "$GITHUB_OUTPUT" echo "::notice::the repository secret WAI_PACKAGES_TOKEN is not set: the Android library is attached but not published to the forge's Maven registry, and the images are not built" fi if [ -n "${SEED:-}" ]; then echo "conformance=true" >> "$GITHUB_OUTPUT" else echo "conformance=false" >> "$GITHUB_OUTPUT" echo "::notice::the repository secret WAI_CONFORMANCE_SEED is not set: WAI's own conformance records are not run, signed or attached to the release (wai-conformance-.tar.gz)" fi if [ "${OCI_RUNNER:-}" != true ]; then echo "::notice::the repository variable WAI_OCI_RUNNER is not true: the images job is skipped (set it once a runner labelled oci serves)" fi