name: wai-quantum contract # The version and deprecation contract (wai/wai-quantum/VERSIONING.md), on # Linux: # # - api: cargo-semver-checks, built from crates.io at a pinned version, # compares wai-quantum, wai-quantum-cli and wai-quantum-qpu with the last # version of each on crates.io (the baselines below). It checks every change as if for a minor # release, so that each breaking change is listed, and fails only on a # breaking change made without a major bump. Before 1.0 the first non-zero # component is the major (0.6.16 to 0.7.0 is a major bump). A step warns # when crates.io holds a newer version than a baseline here, so a baseline # does not go stale unseen. # - schemas: the schema tests, each by name. The engine's: every schema file # reads and names itself, every method's job, a set, a result's provenance, # every error-correction answer, a mitigated job, every answer of the # variational and algorithm library and every engine tool's # answer follow their schemas with no member they do not name, and receipts issued # before results named their engine and schema still verify, their digests # pinned. Both MCP servers': every tool's answer follows its outputSchema, # the committed tool lists (wai-quantum-cli/docs/local-mcp.json and # service-mcp.json) are the live lists, the agent docs are what the binary # generates, and initialize reports the versions. The job service's job in # each status, a set's among them, and its receipt key set after a rotation; # the command line's scheduler lease, its harness's report, corpus and token # counts, and the course's progress file; the answer layer's documents, a # correctness record, the views page's manifest, the course, its exercises # and its checks. The provider protocol's messages and # answers. Then a full # JSON Schema implementation (wai/wai-quantum/ci/check_schemas.py) reads # every schema file and both tool lists, and holds a release build's # answers to them. # - policy: every README of a WAI Quantum package links the policy, and the # deprecations page's table is well formed. # # The receipts' legacy pins and the conformance corpora run where they always # have (wai-energy-receipts.yml, wai-conformance.yml). concurrency: group: ${{ github.workflow }}-${{ github.event_name == 'push' && github.sha || github.ref }} cancel-in-progress: true on: push: branches: [main] paths: - 'wai/wai-quantum/**' - 'wai/wai-quantum-cli/**' - 'wai/wai-quantum-service/**' - 'wai/wai-quantum-qpu/**' # The schema tests read the answer layer's conformance set and the harness's corpus. - 'wai/quantum-conformance/answers/**' - 'wai/quantum-conformance/eval/**' - 'wai/wai-quantum-py/README.md' - 'wai/wai-quantum-desktop/README.md' - 'wai/quantum-studio/README.md' - 'wai/tools/wai-quantum-editor/README.md' - 'wai/tools/tree-sitter-openqasm/README.md' - 'rust-toolchain.toml' - '.github/workflows/wai-quantum-contract.yml' pull_request: paths: - 'wai/wai-quantum/**' - 'wai/wai-quantum-cli/**' - 'wai/wai-quantum-service/**' - 'wai/wai-quantum-qpu/**' # The schema tests read the answer layer's conformance set and the harness's corpus. - 'wai/quantum-conformance/answers/**' - 'wai/quantum-conformance/eval/**' - 'wai/wai-quantum-py/README.md' - 'wai/wai-quantum-desktop/README.md' - 'wai/quantum-studio/README.md' - 'wai/tools/wai-quantum-editor/README.md' - 'wai/tools/tree-sitter-openqasm/README.md' - 'rust-toolchain.toml' - '.github/workflows/wai-quantum-contract.yml' jobs: # Does this pull request's own diff touch the paths above? This forge matches a # workflow's `paths:` against the push, so a merge of main into a branch fires the # workflow whatever the pull request changes; every job here waits on this answer. # A push to main, or anything the script cannot determine, answers `true` # (scripts/pr-touches-paths.py). relevant: name: does this change touch it runs-on: gate 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-contract.yml \ "${{ github.event.pull_request.base.sha }}" \ "${{ github.event.pull_request.head.sha }}") echo "this change touches the wai-quantum contract: $r" echo "run=$r" >> "$GITHUB_OUTPUT" api: name: API compatibility · linux needs: relevant if: needs.relevant.outputs.run == 'true' runs-on: linux timeout-minutes: 60 env: # The last version of each on crates.io. Raise each when a release is published. WAI_QUANTUM_BASELINE: "0.8.1" WAI_QUANTUM_CLI_BASELINE: "0.2.4" WAI_QUANTUM_QPU_BASELINE: "0.4.1" # Built from its source on crates.io, at this version. SEMVER_CHECKS: "0.51.0" 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" - name: Install cargo-semver-checks ${{ env.SEMVER_CHECKS }} shell: bash run: | set -euo pipefail cargo install cargo-semver-checks --version "$SEMVER_CHECKS" --locked --root "$RUNNER_TEMP/semver" echo "$RUNNER_TEMP/semver/bin" >> "$GITHUB_PATH" - name: The baselines are the last versions published shell: bash working-directory: ${{ runner.temp }} run: | set -euo pipefail for pair in "wai-quantum $WAI_QUANTUM_BASELINE" "wai-quantum-cli $WAI_QUANTUM_CLI_BASELINE" "wai-quantum-qpu $WAI_QUANTUM_QPU_BASELINE"; do set -- $pair # `cargo info` names the version it would select and, when that is not the newest, # the newest after it: `version: 0.4.0 (latest 0.4.1)`. The last published is the latter. line=$(cargo info "$1" 2>/dev/null | sed -n 's/^version: *//p' | head -1 || true) latest=$(printf '%s\n' "$line" | sed -n 's/.*(latest \([^)]*\)).*/\1/p') [ -n "$latest" ] || latest=${line%% *} if [ -z "$latest" ]; then echo "::warning::crates.io did not say which version of $1 is the last; the check runs against $2" elif [ "$latest" != "$2" ]; then echo "::warning::crates.io's last version of $1 is $latest, and this workflow's baseline is $2: raise the baseline" else echo "$1: the baseline $2 is the last version on crates.io" fi done - name: API compatibility, against the last versions published shell: bash run: | set -euo pipefail # The major of a version: the first non-zero component (0.6.16 -> 0.6, 1.2.3 -> 1). major() { IFS=. read -r a b _ <<< "$1"; if [ "$a" = 0 ]; then echo "0.$b"; else echo "$a"; fi; } # Every change is checked as if for a minor release, so that each breaking # change is listed; a breaking change fails the step only when the major # is still the baseline's. A build that fails is a failure either way. check() { local dir=$1 base=$2 cur log cur=$(sed -n 's/^version *= *"\([^"]*\)".*/\1/p' "$dir/Cargo.toml" | head -1) log="$RUNNER_TEMP/semver-$(basename "$dir").log" echo "== $dir: $base -> $cur, the major $(major "$base") -> $(major "$cur")" if cargo semver-checks check-release --manifest-path "$dir/Cargo.toml" --baseline-version "$base" --release-type minor 2>&1 | tee "$log"; then echo "$dir: no breaking change against $base" elif ! grep -q 'semver requires new major version' "$log"; then echo "::error::$dir: the check did not run to its end (above)" return 1 elif [ "$(major "$cur")" != "$(major "$base")" ]; then echo "::warning::$dir: breaking changes against $base, listed above; allowed, as the major is bumped to $(major "$cur")" else echo "::error::$dir: a breaking change against $base, listed above, without a major bump: raise $(major "$base") (before 1.0, the 0.y component), and say what to do instead in the changelog" return 1 fi } r=0 check wai/wai-quantum "$WAI_QUANTUM_BASELINE" || r=1 check wai/wai-quantum-cli "$WAI_QUANTUM_CLI_BASELINE" || r=1 check wai/wai-quantum-qpu "$WAI_QUANTUM_QPU_BASELINE" || r=1 exit $r schemas: name: schemas and the committed tool lists · linux 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, and uv shell: bash run: | export PATH="$HOME/.local/bin:$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 command -v uv >/dev/null 2>&1 || curl -LsSf https://astral.sh/uv/install.sh | sh echo "$HOME/.local/bin" >> "$GITHUB_PATH" echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" - name: The engine's schemas, and receipts issued before shell: bash working-directory: wai/wai-quantum run: | set -euo pipefail cargo test --locked --release --no-default-features --features quantum_engine,quantum_receipt,quantum_qec,quantum_noise --lib engine::schema:: 2>&1 | tee "$RUNNER_TEMP/engine.log" for t in every_schema_reads_names_itself_and_refers_only_to_what_is_there \ the_checker_reads_the_subset_it_names_and_refuses_what_it_should \ every_check_s_answer_follows_its_schema \ every_method_s_job_follows_the_job_schemas \ a_new_result_names_its_engine_and_schema_and_its_receipt_binds_them \ error_correction_and_mitigation_follow_their_schemas \ the_answer_layer_the_correctness_record_and_the_views_bundle_follow_their_schemas \ the_course_its_exercises_and_its_checks_follow_their_schemas \ the_variational_and_algorithm_answers_follow_their_schema \ receipts_issued_before_results_named_them_still_verify; do grep -q "^test engine::schema::tests::$t \.\.\. ok$" "$RUNNER_TEMP/engine.log" || { echo "::error::$t did not run and pass"; exit 1; } done - name: The command line's MCP server, its committed tool list and its docs shell: bash working-directory: wai/wai-quantum-cli run: | set -euo pipefail cargo test --locked --release --bin wai-quantum 2>&1 | tee "$RUNNER_TEMP/cli.log" for t in mcp::tests::the_schemas_read_and_name_themselves \ mcp::tests::every_tool_s_answer_follows_its_output_schema \ mcp::tests::the_committed_tool_list_is_this_server_s_live_list \ mcp::tests::an_agent_initializes_lists_the_tools_and_runs_a_circuit \ guide::tests::the_committed_docs_are_what_the_binary_generates \ guide::tests::the_docs_list_exactly_the_servers_tools_and_name_no_version \ tests::the_command_line_parses_as_documented \ task::tests::a_lease_follows_its_schema \ eval::tests::a_scripted_run_reports_what_the_engine_found \ eval::tests::the_corpus_is_the_task_set_written_three_ways \ learn::tests::progress_is_kept_in_one_file_and_marks_a_pass_once; do grep -q "^test $t \.\.\. ok$" "$RUNNER_TEMP/cli.log" || { echo "::error::$t did not run and pass"; exit 1; } done - name: The job service's MCP server, its committed tool list, its job and its receipt keys shell: bash working-directory: wai/wai-quantum-service run: | set -euo pipefail cargo test --locked --release -- mcp:: jobs:: receipt:: 2>&1 | tee "$RUNNER_TEMP/service.log" for t in mcp::tests::the_schemas_read_and_the_versions_are_reported \ mcp::tests::every_tool_s_answer_follows_its_output_schema \ mcp::tests::every_tool_has_a_schema_and_a_description \ mcp::tests::the_command_line_s_snapshot_is_this_server_s_live_list \ jobs::tests::a_job_s_form_follows_the_job_schema \ receipt::tests::a_rotation_keeps_the_old_key_published_with_its_window_and_a_signed_transition; do grep -q "^test $t \.\.\. ok$" "$RUNNER_TEMP/service.log" || { echo "::error::$t did not run and pass"; exit 1; } done - name: The provider protocol's messages shell: bash working-directory: wai/wai-quantum-qpu run: | set -euo pipefail cargo test --locked --release --test protocol_schema 2>&1 | tee "$RUNNER_TEMP/qpu.log" for t in the_schema_names_the_protocol_s_own_constants every_message_and_answer_follows_the_protocol_s_schema; do grep -q "^test $t \.\.\. ok$" "$RUNNER_TEMP/qpu.log" || { echo "::error::$t did not run and pass"; exit 1; } done # A second implementation of JSON Schema, sharing no code with the engine's # checker: every schema file and both tool lists valid 2020-12 schemas, every # reference resolving, and a release build's answers holding to them. - name: The schemas, read by a full JSON Schema implementation shell: bash run: | set -euo pipefail (cd wai/wai-quantum-cli && cargo build --locked --release) uv venv -q --python 3.12 "$RUNNER_TEMP/schemas" uv pip install -q --python "$RUNNER_TEMP/schemas/bin/python" --require-hashes -r wai/wai-quantum-py/tests/requirements.lock "$RUNNER_TEMP/schemas/bin/python" -I wai/wai-quantum/ci/check_schemas.py wai wai/wai-quantum-cli/target/release/wai-quantum policy: name: the policy is linked, and the deprecations page reads needs: relevant if: needs.relevant.outputs.run == 'true' runs-on: linux timeout-minutes: 10 steps: - uses: actions/checkout@v5 - name: Every README of a WAI Quantum package links the policy shell: bash run: | set -euo pipefail for f in wai/wai-quantum/README.md wai/wai-quantum-cli/README.md wai/wai-quantum-service/README.md \ wai/wai-quantum-qpu/README.md wai/wai-quantum-py/README.md wai/wai-quantum-desktop/README.md \ wai/quantum-studio/README.md wai/tools/wai-quantum-editor/README.md wai/tools/tree-sitter-openqasm/README.md; do grep -q 'wai/wai-quantum/VERSIONING.md\|(VERSIONING.md)' "$f" || { echo "::error::$f does not link the policy (wai/wai-quantum/VERSIONING.md)"; exit 1; } done echo "every README links the policy" - name: The deprecations page's table is well formed shell: bash run: | set -euo pipefail python3 -I - wai/wai-quantum/DEPRECATIONS.md <<'EOF' import re, sys lines = open(sys.argv[1], encoding="utf-8").read().splitlines() head = "| Package | Item | Deprecated in | Replacement | Removed in |" assert head in lines, "the table's header is " + head at = lines.index(head) assert re.fullmatch(r"\|(---\|){5}", lines[at + 1]), lines[at + 1] semver = re.compile(r"\d+\.\d+\.\d+") rows = [] for line in lines[at + 2:]: if not line.startswith("|"): break cells = [c.strip() for c in line.strip("|").split("|")] assert len(cells) == 5 and all(cells[:4]), f"a row names its package, item, version and replacement: {line}" assert semver.fullmatch(cells[2]), f"deprecated in a version: {line}" assert cells[4] == "" or semver.fullmatch(cells[4]), f"removed in a version, or not yet: {line}" rows.append(cells) print(f"{len(rows)} deprecated item(s) listed") EOF