Method, Pass, Channel and FrontEnd, and one registry the planner, the run, the compile, the conversions and the verdict go through. Every built-in registers through the same trait a host's own implements: the twelve methods and the pulse simulator, the eight passes, the five channels and the three formats. Registry::with_builtins() and register(...) are explicit and stay in the process. The engine checks what a registered component gives: a method's class and the members the engine writes, a pass's output against its input on the whole circuit by the ladder or a distance bound, a channel's Kraus operators completely positive and trace preserving at each job's values, a format's program reading. A result a registered component took part in carries `components`, each identity (name, version, descriptor digest) marked registered and certified false, sealed by its receipt; a result of built-ins alone gives the bytes it gave, the eight pinned job digests holding through any registry. Refusal codes J060 to J065. The example examples/extension_points.rs, docs/extension-points.md, and the CI leg wai-quantum-registry.yml. Co-Authored-By: Claude Opus 5.5 <[email protected]>
243 lines
14 KiB
Python
243 lines
14 KiB
Python
"""The committed schemas, read by a second implementation of JSON Schema.
|
|
|
|
python -I wai/wai-quantum/ci/check_schemas.py WAI_DIR [WAI_QUANTUM_BINARY]
|
|
|
|
The engine's tests hold what the code writes to its schemas with the engine's
|
|
own checker, which reads the subset of JSON Schema the files use. This reads
|
|
them with a full implementation of the 2020-12 dialect (the `jsonschema`
|
|
package, pinned in wai-quantum-py/tests/requirements.lock), sharing no code
|
|
with the engine:
|
|
|
|
- every schema file (each `*.json` under the packages' `schemas/`) is a valid
|
|
2020-12 schema, named by its `$id`, and every `$ref` in it resolves among
|
|
the files;
|
|
- every tool of both MCP servers, in the committed tool lists, has an
|
|
`inputSchema` and an `outputSchema` that are valid schemas of objects, and
|
|
each `outputSchema` stands alone (no `$ref` out of it);
|
|
- the job bodies sealed before results named their engine and schema follow
|
|
the job schema;
|
|
- with the command line given: a run's job, a set's, the versions it reports, and the
|
|
answer of each of its MCP server's tools that needs no file (the verdict,
|
|
the repair, the compile, the estimate and its check, the debugger, the
|
|
device targets, error correction, mitigation and pulse runs, gradients,
|
|
optimisation and the algorithm builders among them), called over
|
|
standard input and output, each follow their schema.
|
|
|
|
Exit status 0 when everything holds; 1 with the first failure otherwise.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
from jsonschema import Draft202012Validator
|
|
from referencing import Registry, Resource
|
|
|
|
# Every schema file is read, so a new one is checked without being listed here;
|
|
# these must be among them.
|
|
PACKAGES = ["wai-quantum", "wai-quantum-cli", "wai-quantum-service", "wai-quantum-qpu"]
|
|
REQUIRED = [
|
|
"wai-quantum/schemas/job-request.v1.json",
|
|
"wai-quantum/schemas/job-result.v1.json",
|
|
"wai-quantum/schemas/job.v1.json",
|
|
"wai-quantum/schemas/receipt.v2.json",
|
|
"wai-quantum/schemas/plan.v1.json",
|
|
"wai-quantum/schemas/tools.v1.json",
|
|
"wai-quantum/schemas/versions.v1.json",
|
|
"wai-quantum/schemas/compile-result.v1.json",
|
|
"wai-quantum/schemas/verdict.v1.json",
|
|
"wai-quantum/schemas/target.v1.json",
|
|
"wai-quantum/schemas/estimate.v1.json",
|
|
"wai-quantum/schemas/set.v1.json",
|
|
"wai-quantum/schemas/provenance.v1.json",
|
|
"wai-quantum-cli/schemas/lease.v1.json",
|
|
"wai-quantum-cli/schemas/eval-report.v1.json",
|
|
"wai-quantum-cli/schemas/eval-answers.v1.json",
|
|
"wai-quantum-cli/schemas/token-cost.v1.json",
|
|
"wai-quantum/schemas/qec.v1.json",
|
|
"wai-quantum/schemas/distance.v1.json",
|
|
"wai-quantum/schemas/mitigation.v1.json",
|
|
"wai-quantum/schemas/answer.v1.json",
|
|
"wai-quantum/schemas/telemetry.v1.json",
|
|
"wai-quantum/schemas/views-bundle.v1.json",
|
|
"wai-quantum/schemas/algorithms.v1.json",
|
|
"wai-quantum-service/schemas/receipt-keys.v1.json",
|
|
"wai-quantum-cli/schemas/local-tools.v1.json",
|
|
"wai-quantum-service/schemas/service-tools.v1.json",
|
|
"wai-quantum-qpu/schemas/provider.v1.json",
|
|
]
|
|
BASE = "https://git.transaction.science/Transaction-Science/open-standards/raw/branch/main/wai/"
|
|
|
|
|
|
def refs(v, out):
|
|
if isinstance(v, dict):
|
|
if isinstance(v.get("$ref"), str):
|
|
out.append(v["$ref"])
|
|
for x in v.values():
|
|
refs(x, out)
|
|
elif isinstance(v, list):
|
|
for x in v:
|
|
refs(x, out)
|
|
return out
|
|
|
|
|
|
def main() -> int:
|
|
wai = Path(sys.argv[1])
|
|
binary = sys.argv[2] if len(sys.argv) > 2 else None
|
|
schemas = {}
|
|
files = sorted(str(p.relative_to(wai)) for d in PACKAGES for p in (wai / d / "schemas").glob("*.json"))
|
|
missing = [f for f in REQUIRED if f not in files]
|
|
assert not missing, f"schema files not found: {missing}"
|
|
for f in files:
|
|
s = json.loads((wai / f).read_text(encoding="utf-8"))
|
|
Draft202012Validator.check_schema(s)
|
|
assert s["$id"] == BASE + f, f"{f}: its $id is {s['$id']}"
|
|
schemas[s["$id"]] = s
|
|
registry = Registry().with_resources((i, Resource.from_contents(s)) for i, s in schemas.items())
|
|
for i, s in schemas.items():
|
|
for r in refs(s, []):
|
|
# Resolved as a validator resolves it: against the file's own $id.
|
|
registry.resolver(base_uri=i).lookup(r)
|
|
print(f"{len(schemas)} schema files: valid 2020-12 schemas, every reference resolving")
|
|
|
|
def holds(at: str, doc, what: str) -> None:
|
|
v = Draft202012Validator({"$ref": at}, registry=registry)
|
|
errs = sorted(v.iter_errors(doc), key=lambda e: list(e.path))
|
|
assert not errs, f"{what}: " + "; ".join(f"{'/'.join(map(str, e.path))}: {e.message[:200]}" for e in errs[:5])
|
|
|
|
lists = {}
|
|
for f in ["wai-quantum-cli/docs/local-mcp.json", "wai-quantum-cli/docs/service-mcp.json"]:
|
|
tools = json.loads((wai / f).read_text(encoding="utf-8"))["tools"]
|
|
for t in tools:
|
|
for k in ("inputSchema", "outputSchema"):
|
|
Draft202012Validator.check_schema(t[k])
|
|
assert t[k].get("type") == "object", f"{f}: {t['name']}: its {k} is an object's"
|
|
assert not refs(t["outputSchema"], []), f"{f}: {t['name']}: its outputSchema refers out of itself"
|
|
lists[f] = {t["name"]: t for t in tools}
|
|
print(f"{f}: {len(tools)} tools, each with valid input and output schemas, each output standing alone")
|
|
|
|
job = BASE + "wai-quantum/schemas/job.v1.json"
|
|
for f in ["wai-quantum/src/engine/testdata/job-legacy.json", "wai-quantum/src/engine/testdata/provider-job-legacy.json"]:
|
|
holds(job, json.loads((wai / f).read_text(encoding="utf-8")), f)
|
|
print("the job bodies sealed before results named their engine and schema follow the job schema")
|
|
|
|
for doc, at in [
|
|
("quantum-conformance/eval/answers.json", "wai-quantum-cli/schemas/eval-answers.v1.json"),
|
|
("quantum-conformance/eval/token-cost.json", "wai-quantum-cli/schemas/token-cost.v1.json"),
|
|
("wai-quantum/views/manifest.json", "wai-quantum/schemas/views-bundle.v1.json"),
|
|
]:
|
|
holds(BASE + at, json.loads((wai / doc).read_text(encoding="utf-8")), doc)
|
|
print("the committed eval corpus, its token counts and the views page's manifest follow their schemas")
|
|
|
|
if binary:
|
|
bell = "OPENQASM 3.0;\nqubit[2] q;\nbit[2] c;\nh q[0];\nh q[0];\ncx q[0], q[1];\nc = measure q;\n"
|
|
run = json.loads(subprocess.run([binary, "run", "--demo-key", "--json", "--shots", "50", "-o", "Z0 Z1", "-"], input=bell, capture_output=True, text=True, check=True).stdout)
|
|
holds(job, run, "run --json")
|
|
assert run["result"]["schema"] == "wai.quantum.job-result/1" and run["result"]["engine"]["name"] == "wai-quantum", run["result"]
|
|
# A set, run here: the set's result, each item sealed alone.
|
|
import tempfile
|
|
with tempfile.TemporaryDirectory() as d:
|
|
prog = Path(d) / "bell.qasm"
|
|
prog.write_text(bell, encoding="utf-8")
|
|
sset = json.loads(subprocess.run([binary, "run", "--demo-key", "--json", "--shots", "20", str(prog), "--program", str(prog)], capture_output=True, text=True, check=True).stdout)
|
|
holds(job, sset, "run --program --json")
|
|
holds(BASE + "wai-quantum/schemas/set.v1.json", sset["result"], "a set's result")
|
|
assert sset["result"]["schema"] == "wai.quantum.set/1" and sset["result"]["engine"]["name"] == "wai-quantum", sset["result"]
|
|
versions = json.loads(subprocess.run([binary, "version", "--json"], capture_output=True, text=True, check=True).stdout)
|
|
holds(BASE + "wai-quantum/schemas/versions.v1.json", versions, "version --json")
|
|
assert versions["engine"] == run["result"]["engine"], (versions["engine"], run["result"]["engine"])
|
|
tele = ("OPENQASM 3.0;\nqubit[3] q;\nbit[3] c;\nh q[0];\ns q[0];\nh q[1];\ncx q[1], q[2];\ncx q[0], q[1];\nh q[0];\n"
|
|
"c[0] = measure q[0];\nc[1] = measure q[1];\nif (c[1]) x q[2];\nif (c[0]) z q[2];\nc[2] = measure q[2];\n")
|
|
pair = "OPENQASM 3.0;\nqubit[2] q;\nbit[2] c;\nh q[0];\ncx q[0], q[1];\nc = measure q;\n"
|
|
rot = "OPENQASM 3.0;\nqubit[3] q;\nh q;\nccx q[0], q[1], q[2];\nrz(0.3) q[0];\n"
|
|
pulse = ("OPENQASM 3.0;\ndefcalgrammar \"openpulse\";\ncal {\n extern port d0;\n extern port a0;\n"
|
|
" frame q0 = newframe(d0, 5.0e9, 0);\n frame c0 = newframe(a0, 5.0e9, 0);\n}\n"
|
|
"defcal x $0 { play(q0, gaussian(0.4, 64dt, 16dt)); }\ndefcal measure $0 -> bit { return capture_v2(c0, 100dt); }\n"
|
|
"bit c;\nx $0;\nc = measure $0;\n")
|
|
calls = [
|
|
("run_quantum_circuit", {"qasm": bell, "shots": 20}),
|
|
("run_quantum_circuit", {"qasm": pair, "shots": 64, "repair": True, "intent": [
|
|
{"distribution": {"00": 0.5, "11": 0.5}},
|
|
{"sampled": {"00": 0.5, "11": 0.5}, "test": "chi_squared", "alpha": 0.01, "shots": 2000}]}),
|
|
("run_quantum_circuit", {"qasm": pair, "shots": 32, "intent": {"equivalent": pair}}),
|
|
("run_quantum_circuit", {"qasm": bell, "shots": 20, "compile": {"basis": "standard"}}),
|
|
("plan_quantum_circuit", {"qasm": bell}),
|
|
("lint_qasm", {"qasm": bell}),
|
|
("lint_qasm", {"qasm": "qubit[1] q;\nfoo q[0];"}),
|
|
("critique_qasm", {"qasm": bell}),
|
|
("check_equivalent", {"a": bell, "b": "OPENQASM 3.0;\nqubit[2] q;\nbit[2] c;\ncx q[0], q[1];\nc = measure q;\n"}),
|
|
("improve_qasm", {"qasm": bell}),
|
|
("classical_shortcuts", {"qasm": bell}),
|
|
("draw_circuit", {"qasm": bell}),
|
|
("verify_quantum_result", {"job": run}),
|
|
("list_methods", {}),
|
|
("list_components", {}),
|
|
("novelty_qasm", {"qasm": bell}),
|
|
("rewrite_qasm", {"qasm": bell, "op": "options"}),
|
|
("rewrite_qasm", {"qasm": bell, "op": "simplify"}),
|
|
("converge_cohort", {"programs": [{"name": "a", "qasm": bell}, {"name": "b", "qasm": "OPENQASM 3.0;\nqubit[1] q;\nt q[0];\n"}]}),
|
|
("repair_qasm", {"qasm": "OPENQASM 3.0;\nqubit[2] q\nh q[0];\n"}),
|
|
("compile_qasm", {"qasm": "OPENQASM 3.0;\nqubit[2] q;\nh q[0];\nrz(0.3) q[0];\ncx q[0], q[1];\n", "epsilon": 1e-3}),
|
|
("compile_qasm", {"qasm": bell, "basis": "standard", "level": 2, "stages": True}),
|
|
("qasm_diagnostics", {}),
|
|
("qasm_diagnostics", {"code": "Q103"}),
|
|
("qasm_diagnostics", {"code": "J021"}),
|
|
("estimate_resources", {"qasm": rot, "qubit": "us-e4", "budget": 1e-3, "export": "counts"}),
|
|
("debug_branches", {"qasm": tele}),
|
|
("debug_step", {"qasm": tele, "line": 14, "outcomes": [1, 1], "probe": {"qubits": [2]}}),
|
|
("list_targets", {}),
|
|
("show_target", {"target": "synthetic-line-5", "view": True, "document": True}),
|
|
("run_on_target", {"qasm": bell, "target": "synthetic-line-5", "shots": 100}),
|
|
("plan_quantum_circuit", {"qasm": bell, "target": "synthetic-line-5"}),
|
|
("qec_code", {"code": "color:5"}),
|
|
("qec_decode", {"code": "surface:3", "error": [4]}),
|
|
("qec_decode", {"memory": {"distance": 3, "rounds": 3, "p": 0.002}, "decoder": "correlated", "detectors": [0, 1]}),
|
|
("qec_threshold", {"family": "color", "distances": [3, 5], "p": [0.03], "shots": 200}),
|
|
("run_quantum_circuit", {"qasm": rot, "observables": ["Z0 Z1"], "noise": {"depolarizing_2q": 0.02}, "mitigate": "zne"}),
|
|
("compile_qasm", {"qasm": bell, "basis": "standard", "twirl": {"seed": 3}, "dd": "xy4"}),
|
|
("pulse_schedule", {"qasm": pulse}),
|
|
("run_quantum_circuit", {"qasm": pulse, "method": "pulse", "shots": 50}),
|
|
("plan_quantum_circuit", {"qasm": pulse, "method": "pulse"}),
|
|
("gradient_qasm", {"qasm": "OPENQASM 3.0;\ninput float t;\nqubit[2] q;\nh q[0];\ncrx(t) q[0], q[1];\n", "params": {"t": 0.4}, "hamiltonian": ["Z1", "0.5 X0"]}),
|
|
("optimize_qasm", {"qasm": "OPENQASM 3.0;\ninput float t;\nqubit q;\nrx(t) q;\n", "params": {"t": 0.4}, "hamiltonian": ["Z0"], "optimizer": "spsa", "steps": 5}),
|
|
("vqe_ground_state", {"hamiltonian": ["Z0 Z1", "0.5 X0"], "ansatz": {"layers": 1}, "steps": 5}),
|
|
("qaoa_maxcut", {"graph": {"nodes": 3, "edges": [[0, 1], [1, 2]]}, "steps": 5}),
|
|
("build_algorithm", {"algorithm": "qpe", "counting": 3, "phase": 0.375}),
|
|
]
|
|
local = lists["wai-quantum-cli/docs/local-mcp.json"]
|
|
|
|
def session(calls):
|
|
lines = [{"jsonrpc": "2.0", "id": 0, "method": "initialize", "params": {"protocolVersion": "2025-06-18"}}]
|
|
lines += [{"jsonrpc": "2.0", "id": i + 1, "method": "tools/call", "params": {"name": n, "arguments": a}} for i, (n, a) in enumerate(calls)]
|
|
out = subprocess.run([binary, "mcp", "--demo-key"], input="".join(json.dumps(m) + "\n" for m in lines), capture_output=True, text=True, check=True).stdout
|
|
answers = {m["id"]: m for m in map(json.loads, out.splitlines())}
|
|
holds(BASE + "wai-quantum/schemas/versions.v1.json", answers[0]["result"]["_meta"]["science.transaction.wai/versions"], "initialize")
|
|
got = []
|
|
for i, (name, _) in enumerate(calls):
|
|
r = answers[i + 1]["result"]
|
|
assert r["isError"] is False, f"{name}: {r}"
|
|
errs = list(Draft202012Validator(local[name]["outputSchema"]).iter_errors(r["structuredContent"]))
|
|
assert not errs, f"{name}: " + "; ".join(e.message[:200] for e in errs[:5])
|
|
got.append(r["structuredContent"])
|
|
return got
|
|
|
|
got = session(calls)
|
|
# An estimate's check takes the estimate this server wrote.
|
|
est = got[[n for n, _ in calls].index("estimate_resources")]
|
|
session([("verify_quantum_result", {"job": est})])
|
|
# A pulse job's check takes the pulse job this server ran.
|
|
ran = got[[i for i, (n, a) in enumerate(calls) if a.get("method") == "pulse" and n == "run_quantum_circuit"][0]]
|
|
checked = session([("pulse_check", {"request": ran["request"], "result": ran["result"]})])[0]
|
|
assert checked["result"]["verified"] is True, checked
|
|
print(f"{binary}: run --json, version --json, initialize and {len(calls) + 2} tool answers follow their schemas")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
try:
|
|
sys.exit(main())
|
|
except AssertionError as e:
|
|
print(f"check_schemas: {e}", file=sys.stderr)
|
|
sys.exit(1)
|