open-standards/.github/workflows/wai-quantum-studio.yml
Transaction Science ec45aaaca9 toolchain: Rust 1.98.0 -> 1.99.1 (change-controlled pin)
Approved by the owner on 2026-10-22. Procedure: change `channel`, run
`rustup show`, then run the pipeline.

What changed
- rust-toolchain.toml and eoc/eoc-rs/rust-toolchain.toml: channel "1.98.0"
  becomes "1.99.1". These are the only two pins in the tree, and the root one
  governs every other cargo root.
- 64 workflows under .github/workflows (the forge's CI): every `Install Rust
  1.98.0`, `rustup toolchain install / default / component add / target add
  --toolchain 1.98.0` and `--default-toolchain 1.98.0` now names 1.99.1
  (409 lines). Two of those lines are in build steps rather than install
  steps (wai-conformance.yml's two `wasm32 (unix)` steps each
  `rustup target add --toolchain`), and they move with the rest: a target
  added to a toolchain that is not installed is an error.
- Left as they are, because they record what was measured on 1.98.0:
  standards-build.yml's "Verified on a native arm64 Linux container (rustc
  1.98.0)", wai-conformance.yml's note on Rust 1.98's LLD 22, and the
  "rustc 1.98.0 (example)" strings in receipts, vectors and docs. The
  one-codegen-unit workaround that note explains also stays. 1.99.1 ships
  LLD 23.1.1, which the note says links those inputs, but removing the
  workaround should wait for a run at the CI path on x86_64.

Why
- 1.98.0 has a P-critical miscompile, rust-lang/rust#161441 (I-miscompile,
  regression-from-stable-to-stable). rustc emits a vacant (null) method slot
  in a trait-object vtable, so a safe dynamic call jumps to address zero
  (undefined behaviour). RELEASES.md records the fix in 1.98.1 ("rustc: fix
  miscompilation in generating vtables").
- 1.99.1 is the current stable (channel manifest dated 2026-10-15; rustc
  58cf8f9c3 2026-10-13). It also fixes 1.99.0's own regressions: wcslen on
  UEFI and embedded targets, x86_64 FFI `bool` returns, and the rust-lld rpath
  on macOS.

Evidence
All of it ran on the build host ([email protected], aarch64-apple-darwin)
through ~/kani-logs/remote/rb. Nothing was built on the dev Mac.

- `rustup show`: the active toolchain is 1.99.1-aarch64-apple-darwin,
  "overridden by .../rust-toolchain.toml" (and by eoc/eoc-rs's own file
  there). rustc 1.99.1 (58cf8f9c3 2026-10-13), LLVM 23.1.1, clippy 0.1.99,
  rustfmt 1.10.0. The baseline is rustc 1.98.0 (88d9e12ae 2026-08-18).
- Scope: every cargo root the pin governs, which is 46 of the 49 tracked
  lockfiles. The other three: two are vendored crates that resolve inside
  their parents, and wai/wai-rs/fuzz builds on its own pinned nightly.
- Trees. The final tree is main at 96117c570 plus these two commits. Main
  moved four times while this ran, and each root's evidence comes from the
  newest tree it was run on:
  - 13 roots on the final tree: every root main changed after 1a465f23d,
    and every root that consumes such a crate by path.
  - 12 roots on 1a465f23d plus these two commits.
  - 7 roots on b16816671 plus these two commits.
  - 14 roots on aa61ab3b9 plus the pin change.
  For each root in the last three groups, checked with git diff and the
  lockfiles' path packages: none of its files, and no crate it reaches by
  path, differs on the final tree.
- Each root got:
  (a) `cargo fmt --all -- --check` on the pin and on 1.98.0. The output is
      byte-identical in all 46. CI gates no root on rustfmt, and none is
      rustfmt-clean on main.
  (b) `cargo clippy --locked --keep-going --workspace --all-targets` on both
      toolchains, with the diagnostics compared line by line. After the
      previous commit, no root has a diagnostic that only 1.99.1 reports.
      openpay's one differing line is the same lint at the same site,
      reworded.
  (c) `cargo test --workspace --locked`.
  (d) the root's other CI steps that can run on this host: feature-gated
      tests, conformance suites, release-profile tests, the aac page-set
      checks, and every clippy -D warnings gate. The gates are
      standards-build's macOS meter and omni lints (jouleclaw-energy x2,
      jouleclaw-omni metal, jouleclaw-loader-gguf, jcp, proof, eoc, sandbox),
      wai-pack, wai-quantum-cli (default, no-default and metal),
      wai-quantum-service, wai-quantum-qpu and wai-quantum-desktop.
- Totals: `cargo test --workspace` ran in 43 roots: 12,532 passed, 1 failed,
  101 ignored. The other 3 roots are wasm32-only, and their
  `cargo build --release --target wasm32-unknown-unknown` passed. Every
  other CI step passed (62 of 62).
- On the final tree: scripts/check-lockfiles.sh (cargo 1.99.1) found all 47
  lockfiles current, and scripts/check-advisories.py passes.
- tokio is 1.54.1 in every lockfile cargo reads. That is patched for both
  RUSTSEC-2026-0386 (>= 1.53.3) and RUSTSEC-2026-0384 (>= 1.54.1). The 1.53.1
  in vendor/wtransport/Cargo.lock is the upstream file kept with the vendored
  source; cargo resolves wtransport inside its parents, and both gates skip
  that file.
- What is not green, each one reproduced on 1.98.0:
  - wai-ops-gpu: under the debug profile, `corpus` trips the debug_assert at
    exec.rs:187 on both toolchains. CI runs it in release only, and that
    passes (6/6).
  - wai-ops: clippy::derived_hash_with_manual_eq (deny-by-default) on both
    toolchains. No CI job runs clippy there.
  - wai-vector/wasm: `--all-targets` cannot build the lib-test target (its
    own `panic_impl`) on either toolchain. `clippy --lib` for wasm32 reports
    0 diagnostics on both.
  - eoc-cascade's hit_calibration_process_energy test reads real energy
    counters, and it fails some of the time on the loaded shared host. In
    interleaved runs each toolchain passed 18 of 20. The full eoc-rs suite
    passed on the final tree (741 passed).
- Environment, not code:
  - wai-quantum-desktop embeds the Studio's build, so wai/quantum-studio was
    built first (`npm ci`, `node build.mjs`), as CI does.
- Not covered here: the x86_64 and arm64 Linux and Windows legs; the GPU,
  NPU, FPGA, emulator and hardware-meter legs; wasm32 tests under Node,
  wasmtime and wasip2; and the fuzz job.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-03 17:42:20 -05:00

297 lines
16 KiB
YAML

name: wai-quantum studio and desktop
# Quantum Studio (wai/quantum-studio), the interface for the browser and the
# desktop, and the desktop app around it (wai/wai-quantum-desktop):
# - the Studio built against the engine's browser package (wai-web, feature
# quantum_engine) and run in a browser by test/studio.test.mjs: linted, drawn and
# planned as it is typed, run here with its receipt checked here, an error marked
# on its line, any angle on the state vector, Ctrl+Enter, and the program and the
# history kept across a reload; a device target's coupling map coloured by its
# errors, a run placed and simulated on it, and the layout drawn on the map;
# nothing scrolls sideways at a phone's width;
# - test/hardware.test.mjs, without a browser: "Your hardware" asks the person before
# a run that is not free, never before a free one, and sends nothing on a "no"; a job
# in the provider's queue is asked after by its id, Cancel cancels it there, "Wait in
# the background" leaves it, and a job left waiting gives its result when it finishes;
# - test/service.test.mjs, without a browser: a job on the job service whose answer is
# lost is sent again under its key to a service that keeps keys, and once, with no
# key, to one that does not; a poll whose answer is lost does not end the wait;
# - test/telemetry.test.mjs, without a browser: the correctness record is off by
# default, and while off records nothing, writes nothing and asks the engine
# nothing; and the views page agent hosts draw is built from these sources
# (build-views.mjs --check; wai-quantum-views.yml draws it);
# - in the browser, too: answer views drawn as an answer streams in, and the
# correctness record turned on and its failures exported;
# - test/locales.test.mjs, without a browser: every key of the interface's words in
# every locale, with the same placeholders, each translation marked machine-assisted;
# - the course in the browser, in English and in Chinese (studio.test.mjs): a lesson
# checked by the engine, what went wrong, a hint, a pass kept in the browser, and the
# language followed from the browser and switched;
# - test/update.test.mjs, without a browser: the desktop app's update offer names
# both versions, installs nothing until asked, and reports a refusal;
# - the desktop app on macOS, Linux and Windows: its tests and clippy with warnings
# as errors, and `check`: a job run in the app's worker process, sealed in the app
# with a key that never leaves it, and verified, with no window. The tests hold an
# update to the build's key and the version it was signed for, and say which
# copies update themselves; on Linux, the release's manifests (the Homebrew cask,
# the Scoop manifest, the update manifest) are written from releases made up here
# (release/manifests_test.py). The installers themselves are built by
# wai-quantum-desktop-release.yml, on a tag.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
on:
push:
branches: [main]
paths:
- 'wai/quantum-studio/**'
- 'wai/wai-quantum-desktop/**'
- 'wai/wai-quantum/**'
- 'wai/wai-quantum-cli/**'
- 'wai/wai-quantum-qpu/**'
- 'wai/wai-web/**'
- 'wai/wai-rs/**'
- 'wai/wai-quantum-views/**'
- 'rust-toolchain.toml'
- '.github/workflows/wai-quantum-studio.yml'
- '.github/workflows/wai-quantum-desktop-release.yml'
pull_request:
paths:
- 'wai/quantum-studio/**'
- 'wai/wai-quantum-desktop/**'
- 'wai/wai-quantum/**'
- 'wai/wai-quantum-cli/**'
- 'wai/wai-quantum-qpu/**'
- 'wai/wai-web/**'
- 'wai/wai-rs/**'
- 'wai/wai-quantum-views/**'
- 'rust-toolchain.toml'
- '.github/workflows/wai-quantum-studio.yml'
- '.github/workflows/wai-quantum-desktop-release.yml'
jobs:
relevant:
name: 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-quantum-studio.yml \
"${{ github.event.pull_request.base.sha }}" \
"${{ github.event.pull_request.head.sha }}")
echo "this change touches the studio or the desktop app: $r"
echo "run=$r" >> "$GITHUB_OUTPUT"
studio:
name: studio · browser
needs: relevant
if: needs.relevant.outputs.run == 'true'
runs-on: linux
timeout-minutes: 60
steps:
- uses: actions/checkout@v5
- name: Install Rust 1.99.1 with the wasm32 target
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 wasm32-unknown-unknown
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
# Pinned to the wasm-bindgen version in wai-web/Cargo.lock.
- name: wasm-bindgen CLI 0.2.127
shell: bash
run: cargo install --locked wasm-bindgen-cli --version 0.2.127
- uses: actions/setup-node@v4
with:
node-version: '22'
- name: The engine's browser package
shell: bash
working-directory: wai/wai-web
run: |
set -euo pipefail
cargo build --locked --release --target wasm32-unknown-unknown --features quantum_engine
wasm-bindgen --target web --out-dir "$RUNNER_TEMP/pkg" --out-name wai_quantum_ide \
target/wasm32-unknown-unknown/release/wai_web.wasm
- name: Build the Studio and run it in a browser
shell: bash
working-directory: wai/quantum-studio
env:
WAI_E2E_CHANNEL: ''
STUDIO_SKIP_SERVICE: '1'
run: |
set -euo pipefail
npm ci --no-audit --no-fund
npx playwright install --with-deps chromium
node test/hardware.test.mjs
node test/service.test.mjs
node test/telemetry.test.mjs
node test/update.test.mjs
node build-views.mjs --check
node test/locales.test.mjs
node build.mjs --pkg "$RUNNER_TEMP/pkg"
node test/studio.test.mjs
desktop:
name: desktop app · 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 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"
# The app embeds the Studio's build, so it is built first.
- name: Build the Studio
shell: bash
working-directory: wai/quantum-studio
run: |
set -euo pipefail
npm ci --no-audit --no-fund
node build.mjs
- name: Tests, clippy, and a job through the worker with no window
shell: bash
working-directory: wai/wai-quantum-desktop
run: |
set -euo pipefail
nice -n 10 cargo test --locked --release 2>&1 | tee "$RUNNER_TEMP/test.log"
grep -qE '^test result: ok\. [1-9][0-9]* passed; 0 failed' "$RUNNER_TEMP/test.log" || { echo "::error::no test ran"; exit 1; }
grep -q '^test tests::a_run_on_a_provider_is_estimated_refused_until_confirmed_and_sealed_with_the_app_s_key \.\.\. ok$' "$RUNNER_TEMP/test.log" || { echo "::error::the provider run test did not run and pass"; exit 1; }
grep -q '^test tests::a_job_still_queued_is_answered_by_its_id_and_cancelled_only_when_asked \.\.\. ok$' "$RUNNER_TEMP/test.log" || { echo "::error::the queued provider job test did not run and pass"; exit 1; }
grep -q '^test tests::device_targets_are_listed_shown_and_placed_for_the_page \.\.\. ok$' "$RUNNER_TEMP/test.log" || { echo "::error::the device targets test did not run and pass"; exit 1; }
grep -q '^test tests::an_answer_is_read_run_and_sealed_for_the_page \.\.\. ok$' "$RUNNER_TEMP/test.log" || { echo "::error::the answer views test did not run and pass"; exit 1; }
grep -q '^test tests::the_course_is_read_and_checked_in_the_app \.\.\. ok$' "$RUNNER_TEMP/test.log" || { echo "::error::the course test did not run and pass"; exit 1; }
for t in a_read_only_volume_is_told_from_a_writable_one an_update_is_held_to_this_build_s_key_and_the_version_it_was_signed_for only_a_copy_installed_by_its_own_installer_updates_itself the_updater_asks_the_release_s_manifest_and_requires_a_signed_version; do
grep -q "^test tests::$t \.\.\. ok\$" "$RUNNER_TEMP/test.log" || { echo "::error::the update test $t did not run and pass"; exit 1; }
done
grep -q '^test tests::a_program_in_another_format_opens_in_the_app \.\.\. ok$' "$RUNNER_TEMP/test.log" || { echo "::error::the formats test did not run and pass"; exit 1; }
nice -n 10 cargo clippy --locked --release --all-targets -- -D warnings
nice -n 10 cargo build --locked --release
./target/release/wai-quantum-desktop check
s="$(./target/release/wai-quantum-desktop update-status)"; echo "update-status: $s"
[[ "$s" == *'"installed_by":null'* && "$s" == *'"updates_itself":false'* ]] || { echo "::error::a copy cargo built would update itself"; exit 1; }
desktop-linux:
name: desktop app · linux
needs: relevant
if: needs.relevant.outputs.run == 'true'
runs-on: linux
timeout-minutes: 90
steps:
- uses: actions/checkout@v5
- name: Install Rust 1.99.1 and the web view's libraries
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 --component clippy
rustup default 1.99.1
SUDO=""; [ "$(id -u)" -ne 0 ] && SUDO="sudo"
$SUDO apt-get update -qq
$SUDO apt-get install -y -qq libwebkit2gtk-4.1-dev libxdo-dev libayatana-appindicator3-dev librsvg2-dev
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
- uses: actions/setup-node@v4
with:
node-version: '22'
- name: Build the Studio
shell: bash
working-directory: wai/quantum-studio
run: |
set -euo pipefail
npm ci --no-audit --no-fund
node build.mjs
- name: Tests, clippy, and a job through the worker with no window
shell: bash
working-directory: wai/wai-quantum-desktop
run: |
set -euo pipefail
cargo test --locked --release 2>&1 | tee "$RUNNER_TEMP/test.log"
grep -qE '^test result: ok\. [1-9][0-9]* passed; 0 failed' "$RUNNER_TEMP/test.log" || { echo "::error::no test ran"; exit 1; }
for t in a_run_on_a_provider_is_estimated_refused_until_confirmed_and_sealed_with_the_app_s_key a_job_still_queued_is_answered_by_its_id_and_cancelled_only_when_asked device_targets_are_listed_shown_and_placed_for_the_page an_answer_is_read_run_and_sealed_for_the_page the_course_is_read_and_checked_in_the_app an_update_is_held_to_this_build_s_key_and_the_version_it_was_signed_for only_a_copy_installed_by_its_own_installer_updates_itself the_updater_asks_the_release_s_manifest_and_requires_a_signed_version; do
grep -q "^test tests::$t \.\.\. ok\$" "$RUNNER_TEMP/test.log" || { echo "::error::the test $t did not run and pass"; exit 1; }
done
cargo clippy --locked --release --all-targets -- -D warnings
cargo build --locked --release
./target/release/wai-quantum-desktop check
s="$(./target/release/wai-quantum-desktop update-status)"; echo "update-status: $s"
[[ "$s" == *'"installed_by":null'* && "$s" == *'"updates_itself":false'* ]] || { echo "::error::a copy cargo built would update itself"; exit 1; }
- name: The release's manifests, from releases made up here
shell: bash
working-directory: wai/wai-quantum-desktop
run: |
set -euo pipefail
bash -n release/collect.sh
bash -n release/publish.sh
bash -n release/signing.sh
python3 -I release/manifests_test.py
desktop-windows:
name: desktop app · windows
needs: relevant
if: needs.relevant.outputs.run == 'true'
runs-on: windows
timeout-minutes: 120
steps:
- uses: actions/checkout@v5
- name: Build the Studio
shell: pwsh
working-directory: wai/quantum-studio
run: |
$ErrorActionPreference = 'Stop'
Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass -Force
node --version
npm ci --no-audit --no-fund
if ($LASTEXITCODE -ne 0) { throw "npm ci failed" }
node build.mjs
if ($LASTEXITCODE -ne 0) { throw "the Studio's build failed" }
- name: Tests, clippy, and a job through the worker with no window
shell: pwsh
working-directory: wai/wai-quantum-desktop
run: |
$ErrorActionPreference = 'Stop'
Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass -Force
$log = Join-Path $env:RUNNER_TEMP 'test.log'
# cargo writes its progress to stderr; the exit status is what counts.
$ErrorActionPreference = 'Continue'
cargo test --locked --release *> $log
$rc = $LASTEXITCODE
$ErrorActionPreference = 'Stop'
Get-Content $log
if ($rc -ne 0) { throw "the tests failed ($rc)" }
$text = Get-Content $log -Raw
if ($text -notmatch '(?m)^test result: ok\. [1-9][0-9]* passed; 0 failed') { throw "no test ran" }
foreach ($t in @('a_run_on_a_provider_is_estimated_refused_until_confirmed_and_sealed_with_the_app_s_key', 'a_job_still_queued_is_answered_by_its_id_and_cancelled_only_when_asked', 'device_targets_are_listed_shown_and_placed_for_the_page', 'an_answer_is_read_run_and_sealed_for_the_page', 'the_course_is_read_and_checked_in_the_app', 'an_update_is_held_to_this_build_s_key_and_the_version_it_was_signed_for', 'only_a_copy_installed_by_its_own_installer_updates_itself', 'the_updater_asks_the_release_s_manifest_and_requires_a_signed_version')) {
if ($text -notmatch ('(?m)^test tests::' + [regex]::Escape($t) + ' \.\.\. ok\r?$')) { throw "the test $t did not run and pass" }
}
# '--' quoted, so PowerShell hands it to cargo as it is.
cargo clippy --locked --release --all-targets '--' -D warnings
if ($LASTEXITCODE -ne 0) { throw "clippy failed" }
cargo build --locked --release
if ($LASTEXITCODE -ne 0) { throw "the build failed" }
# The app is a windowed program: waited for, its output kept.
$out = Join-Path $env:RUNNER_TEMP 'check.out'; $err = Join-Path $env:RUNNER_TEMP 'check.err'
$p = Start-Process -FilePath 'target\release\wai-quantum-desktop.exe' -ArgumentList 'check' -Wait -PassThru -NoNewWindow -RedirectStandardOutput $out -RedirectStandardError $err
Get-Content $out, $err
if ($p.ExitCode -ne 0) { throw "check exited $($p.ExitCode)" }
if (-not ((Get-Content $out -Raw) -match '^check: ')) { throw "check said nothing" }
# A copy cargo built knows no installer made it, so it never updates itself.
$p = Start-Process -FilePath 'target\release\wai-quantum-desktop.exe' -ArgumentList 'update-status' -Wait -PassThru -NoNewWindow -RedirectStandardOutput $out -RedirectStandardError $err
$s = Get-Content $out -Raw
Write-Output "update-status: $s"
if ($p.ExitCode -ne 0 -or $s -notmatch '"installed_by":null' -or $s -notmatch '"updates_itself":false') { throw "a copy cargo built would update itself: $s" }