open-standards/.github/workflows/wai-quantum-contract.yml
Transaction Science 3b3cedfc21 wai-quantum 0.7.0, wai-quantum-cli 0.2.0, wai-quantum-qpu 0.3.0 and the job service 0.2.0: the release's versions, its changelog heading, and every lock
The engine, the command line, the provider crate and the job service move to
their next 0.y, which is a major bump before 1.0 (VERSIONING.md); the Python
package stays 0.1.0 and the editor extension keeps its version. Every
dependent's requirement names the new version, and the sixteen lockfiles that
resolve any of them move with them, each read with --locked.

cargo-semver-checks keeps its baselines at the versions last published (0.6.16,
0.1.4 and 0.2.0); wai-quantum-qpu joins the check. Against them it lists the
breaking changes and passes, as each major is bumped.

The changelog's release heading summarises the twelve entries below it and
lists what a caller may have to change. DEPRECATIONS.md stays empty.

The engine's version is among the sealed bytes of a stamped result, so the
page's pinned digests move: the compile's 0a165415... to ac948133..., the
estimate's 0cd25cf6... to 8715e76b... The job pin strips the engine and
schema and does not move. The notebooks' stored outputs are re-stored.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-27 23:44:28 -04:00

289 lines
15 KiB
YAML

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 and every engine tool's answer
# follows its schema with no member it does 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. 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.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/**'
- '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/**'
- '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: linux-arm64
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.6.16"
WAI_QUANTUM_CLI_BASELINE: "0.1.4"
WAI_QUANTUM_QPU_BASELINE: "0.2.0"
# Built from its source on crates.io, at this version.
SEMVER_CHECKS: "0.51.0"
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"
- 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
latest=$(cargo info "$1" 2>/dev/null | sed -n 's/^version: *\([^ ]*\).*/\1/p' | head -1 || true)
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.98.0, 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.98.0
rustup toolchain install 1.98.0 --profile minimal
rustup default 1.98.0
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 --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 \
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; 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 and its job
shell: bash
working-directory: wai/wai-quantum-service
run: |
set -euo pipefail
cargo test --locked --release -- mcp:: jobs:: 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; 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