Some checks failed
wai-audit collector and player integration / does this change touch it (push) Successful in 21s
WAI operation catalogue / does this change touch it (push) Successful in 21s
wai-quantum clippy / does this change touch it (push) Successful in 23s
wai-quantum command line / does this change touch it (push) Successful in 27s
wai-quantum docs, course and locales / does this change touch it (push) Successful in 21s
wai-quantum error correction and mitigation / does this change touch it (push) Successful in 15s
wai-quantum qudit jobs / does this change touch it (push) Successful in 18s
wai-quantum for Python / does this change touch it (push) Successful in 22s
wai-quantum extension points / does this change touch it (push) Successful in 23s
wai-quantum service / does this change touch it (push) Successful in 24s
wai-quantum studio and desktop / does this change touch it (push) Successful in 21s
wai-quantum service / service energy · linux packages (push) Failing after 8s
wai-quantum variational and algorithm library / does this change touch it (push) Successful in 26s
wai-quantum verdicts and repairs / does this change touch it (push) Successful in 19s
wai-quantum views in agent hosts / does this change touch it (push) Successful in 19s
wai-quantum on Windows / does this change touch it (push) Successful in 32s
wai-sink-android native sink library / does this change touch it (push) Successful in 21s
wai-sink-swift native sink package / does this change touch it (push) Successful in 25s
wai-quantum for JavaScript / does this change touch it (push) Successful in 2m37s
WAI Studio / does this change touch it (push) Successful in 12s
wai-quantum OpenQASM reader / does this change touch it (push) Successful in 2m43s
wai-quantum editors / does this change touch it (push) Successful in 2m55s
wai-quantum pulse and dynamics / does this change touch it (push) Successful in 2m42s
wai-quantum contract / does this change touch it (push) Successful in 2m59s
wai-quantum other formats / does this change touch it (push) Successful in 2m48s
wai-quantum-landscape-fpga reference design / does this change touch it (push) Successful in 2m45s
wai-quantum resource estimation / does this change touch it (push) Successful in 2m51s
WAI breaking changes and the conformance runner / WAI breaking · does this change touch WAI (push) Successful in 11s
WAI examples / WAI examples · does this change touch them (push) Successful in 22s
wai-quantum landscape stage 0, the sampler against the exact law / does this change touch it (push) Successful in 10s
wai-quantum landscape energy to solution / does this change touch it (push) Successful in 17s
wai-quantum energy-landscape jobs / does this change touch it (push) Successful in 12s
wai-audit collector and player integration / collector · linux (push) Successful in 3m47s
wai-quantum clippy / clippy, warnings denied, every feature set · linux (push) Successful in 3m46s
wai-quantum docs, course and locales / the Studio's words in every locale · linux (push) Successful in 16s
wai-quantum wasmQ / does this change touch it (push) Successful in 19s
wai-quantum qudit jobs / the module and the conformance set under wasm32-wasip2 (wasmtime) (push) Successful in 2m28s
wai-quantum docs, course and locales / the course and the command line's languages · linux (push) Successful in 3m53s
wai-quantum studio and desktop / studio · browser (push) Successful in 4m10s
WAI operation catalogue / the catalogue and every door · linux (push) Successful in 12m50s
wai-quantum extension points / the registry under wasm32-wasip2 (wasmtime) (push) Successful in 5m9s
wai-quantum verdicts and repairs / the intent set and the verdict under wasm32-wasip2 (wasmtime) (push) Successful in 1m37s
wai-quantum variational and algorithm library / parameters, gradients, optimisers, VQE, QAOA and builders · linux, release (push) Successful in 2m35s
wai-quantum verdicts and repairs / verdicts, repairs and the intent set · linux (push) Successful in 2m58s
wai-quantum error correction and mitigation / codes, decoders, texts and mitigation · linux, release (push) Successful in 10m5s
WAI Studio / the catalogue's actions, the unit tests and the core · linux (push) Successful in 2m21s
wai-quantum studio and desktop / desktop app · linux (push) Successful in 7m30s
wai-quantum OpenQASM reader / corpus under wasm32-wasip2 (wasmtime) (push) Successful in 51s
wai-quantum contract / the policy is linked, and the deprecations page reads (push) Successful in 13s
wai-quantum qudit jobs / the module, the job, the conformance set, the referee and the verb · linux (push) Successful in 12m45s
wai-quantum views in agent hosts / views · the page and its engine (push) Successful in 4m54s
wai-quantum extension points / the registry and the example · linux (push) Successful in 11m13s
wai-quantum for JavaScript / javascript · linux (push) Successful in 4m55s
wai-quantum for Python / python · linux (push) Successful in 12m31s
wai-quantum OpenQASM reader / reader and corpus · linux (push) Successful in 4m0s
wai-quantum pulse and dynamics / reader, simulator and pulse jobs under wasm32-wasip2 (wasmtime) (push) Successful in 1m22s
wai-quantum command line / command line · linux (push) Successful in 19m37s
wai-quantum pulse and dynamics / reader, simulator and pulse jobs · linux, release (push) Successful in 3m20s
wai-quantum editors / editors · linux (push) Successful in 3m41s
wai-quantum other formats / the set under wasm32-wasip2 (wasmtime) (push) Successful in 3m7s
WAI breaking changes and the conformance runner / WAI breaking · wire, record and API against main · linux (push) Successful in 2m44s
wai-quantum landscape stage 0, the sampler against the exact law / the small tier, to the byte · linux (push) Successful in 1m4s
wai-quantum service / service · linux (push) Successful in 17m3s
wai-quantum landscape energy to solution / the small tier, to the byte · linux (push) Successful in 2m23s
wai-quantum views in agent hosts / views · a scripted host (push) Successful in 14m9s
wai-quantum contract / schemas and the committed tool lists · linux (push) Successful in 10m9s
wai-quantum other formats / readers, writers and the set · linux (push) Successful in 9m28s
WAI breaking changes and the conformance runner / WAI breaking and conform · their tests, and the independent verifiers through wai conform · linux (push) Successful in 7m43s
wai-quantum contract / API compatibility · linux (push) Successful in 11m25s
wai-quantum wasmQ / the reference modules rebuild to their modules of record · linux (push) Successful in 34s
WAI Studio / Studio in WebKit · linux (push) Successful in 14m43s
wai-quantum resource estimation / estimation and calibration · linux, release (push) Successful in 10m56s
wai-quantum energy-landscape jobs / native against WebAssembly, over many seeds (push) Successful in 8m32s
wai-quantum wasmQ / interpreter and vectors under wasm32-wasip2 (wasmtime) (push) Successful in 6m27s
WAI examples / WAI examples · portable · linux (push) Successful in 11m57s
wai-quantum wasmQ / profile, engines, reference modules, adversarial, vectors · linux (push) Successful in 8m56s
wai-quantum energy-landscape jobs / the module under wasm32-wasip2 (wasmtime) (push) Successful in 13m28s
wai-quantum-landscape-fpga reference design / the member against the model of the logic, and the logic against the model · linux, release (push) Successful in 22m33s
wai-quantum energy-landscape jobs / reader, kernels, answers and the stated result · linux, release (push) Successful in 17m1s
wai-quantum docs, course and locales / every command and snippet in the docs · linux (push) Successful in 40m14s
wai-audit collector and player integration / collector, player integration and the browser end to end · macos (push) Successful in 2m13s
wai-quantum command line / command line · macos (push) Successful in 2m55s
wai-quantum qudit jobs / the same bits · macos (push) Successful in 1m47s
wai-quantum for Python / python · macos (push) Successful in 3m41s
wai-quantum studio and desktop / desktop app · macos (push) Successful in 5m34s
wai-quantum service / service · macos (push) Successful in 7m44s
wai-quantum editors / editors · macos (push) Successful in 1m53s
repository checks / lockfiles, advisories, tests in CI, workflow hygiene, spec and section references, public text (push) Successful in 9m39s
wai-quantum pulse and dynamics / the same bits · macos (push) Successful in 1m33s
wai-sink-android native sink library / every ABI, the AAR, and the instrumented tests in a headless emulator · macos (push) Successful in 6m5s
wai-quantum landscape stage 0, the sampler against the exact law / the small tier, to the byte · macos (push) Successful in 33s
wai-quantum extension points / the registry and the example · linux-arm64 (push) Successful in 7m49s
wai-quantum landscape energy to solution / the small tier, to the byte · macos (push) Successful in 2m5s
wai-quantum-landscape-fpga reference design / the board's code on the board's processor's architecture · linux-arm64, release (push) Successful in 1m50s
wai-quantum variational and algorithm library / parameters, gradients, optimisers, VQE, QAOA and builders · linux-arm64, release (push) Successful in 4m21s
wai-quantum pulse and dynamics / the same bits · linux-arm64 (push) Successful in 3m11s
WAI Studio / Studio in Chromium and Firefox · macos (push) Successful in 11m4s
wai-quantum energy-landscape jobs / the same bits · macos (push) Successful in 7m36s
wai-sink-android native sink library / the JNI layer and the platform-free half on the host JVM · linux-arm64 (push) Successful in 10m54s
wai-quantum wasmQ / profile, engines, reference modules, adversarial, vectors · macos (push) Successful in 2m40s
WAI examples / WAI examples · the archive, the readiness check, the browser and Apple · macos (push) Successful in 21m33s
wai-sink-swift native sink package / libwai slices, desktop tests, simulator tests · macos (push) Failing after 28m52s
WAI examples / WAI examples · android-app · linux-arm64 (push) Successful in 15m51s
wai-quantum energy-landscape jobs / the same bits · linux-arm64 (push) Successful in 19m35s
wai-quantum on Windows / engine · windows (push) Has been cancelled
wai-quantum on Windows / command line · windows (push) Has been cancelled
wai-quantum on Windows / job service · windows (push) Has been cancelled
wai-quantum on Windows / python · windows (push) Has been cancelled
wai-quantum studio and desktop / desktop app · windows (push) Has been cancelled
The sign-off loop (check, grade, attest;4b17804c2) ran on the command line and its MCP server. It now runs wherever the engine does, with the same answers. - engine::front holds the check, the attestation and the loop's MCP tools (check_quantum_program, grade_quantum_program, attest_quantum_job, in one wording with the sealer named), which the command line, both MCP servers, the Python package and the npm package call; the command line keeps its flags and its printing. The output schemas of check and attest move from the command line's local-tools.v1.json into the engine's tools.v1.json ($defs/check, $defs/attest), which both servers name. - The job service serves the three tools on its MCP server and as POST /v1/check, /v1/grade and /v1/attest (GET /v1, the OpenAPI document and the README list them), a grade and a sign-off sealed with its own key, their energy unmetered with the reason. Each is counted as a check, held to its bounds and made in a reading child, each case within half the child's memory. A check of a task, a grade, and a sealed grade POST /v1/verify works out again are now held case by case (analysis::grade_bounds: each case's intent that compares the program with a reference held to the equivalence check's bound with the program written out at that case's inputs, and the task's job read as a job's is), where before a grade was remade within the reading child's budget whole; a case past them is refused at that case (J031, "case N: ...", naming wai-quantum grade). An attestation holds what verifying the job works out again and the intent its sign-off checks to the same bounds (naming wai-quantum attest). - Python: wq.check, wq.grade, wq.attest (and _engine.check/grade/attest); npm: QuantumEngine.check, .grade, .attest; each with a test (test_engine.py, smoke.mjs, a wasm32 test in wai-web) and a README example. - The instructions both MCP servers give at initialize describe the loop (guide::LOOP), and the committed lists (docs/local-mcp.json, docs/service-mcp.json), llms.txt, llms-full.txt and the skill's tools.md are written again from the live lists. - A whole task set graded: wai-quantum grade --set FILE --programs ANSWERS (the model harness's set; the programs a JSON object of programs or of a model's answers as eval run --scripted reads them, or a directory of NAME.qasm files), or --course --programs DIR; engine::grade::batch, wai.quantum.grades/1 (schemas/grades.v1.json), quantum-providers §8.10 "A task set graded": one sealed grade a task, missing and refused tasks said so, the set's digest and a summary. The batch is not sealed; wai-quantum verify works it out again (grade::check_batch): each grade checked and made again, each entry's name and grade held to what its grade seals, a refused entry refused again, the digest and the summary made again from the sealed results. The same set, programs and key give the same bytes. quantum-conformance/grades/ holds a batch and six changed ones with what a reader must say (written by hand from §8.10), natively and under wasm32-wasip2 (tests/grades_conformance.rs), including one that shows what a batch does not: a grade dropped as missing with the summary made to agree is valid; that a task had no program is the grader's statement. - wai-web's pinned digest of a level-2 compile (COMPILE_DIGEST) follows the compile of7c00ef138, which writes each region of CNOT and phase gates again at level 2 and so changed those sealed bytes; wai-web wasm32 runs on pull requests and nightly only, so nothing had run it since. Native and wasm32 give the new digest alike. The npm smoke runs the loop on an engine of 1 GiB: one of 256 MiB says an intent's exact check is unchecked, and why (asserted too). - Which trust list an attester used stays outside the seal: sealing it would add a member to the sign-off's closed request, which every released verifier refuses (J001), and it would show only which keys the attester trusted, which is each reader's to decide; the changes entry and the skill say so. Tests: engine::front, engine::grade (the batch), the guide's instructions, service analysis (bounds case by case), mcp (an agent runs the loop over the service's server; every tool's answer held to its output schema), verify.rs (the loop over HTTP in a running service, its grade and sign-off verified), the command line's front_door.rs (a whole set graded through the binary, verified, a changed summary found) and check_schemas.py (the batch and an attestation held to their schemas). wai-quantum-verdict.yml, -service.yml, -cli.yml and wai-web-wasm.yml require the new tests by name. schema::FILES gains grades.v1.json (acknowledged in the changes entry). Co-Authored-By: Claude Opus 5.5 <[email protected]>
501 lines
32 KiB
Rust
501 lines
32 KiB
Rust
//! The job service's verify, over HTTP and as an MCP tool, on the corpus of
|
|
//! job receipts: the verdict the manifest gives, every rule of the provider
|
|
//! protocol's section 8.4 and not the seal alone; and the sign-off loop's routes,
|
|
//! whose grades and sign-offs that verify works out again.
|
|
|
|
use serde_json::{Value, json};
|
|
use std::io::{Read, Write};
|
|
use std::net::TcpStream;
|
|
use std::path::{Path, PathBuf};
|
|
use std::process::{Command, Stdio};
|
|
use std::time::{Duration, Instant};
|
|
|
|
mod common;
|
|
|
|
use common::Running;
|
|
|
|
/// One HTTP/1.1 exchange, the body as bytes: the status and the body.
|
|
fn http(port: u16, method: &str, path: &str, body: &[u8]) -> (u16, String) {
|
|
let mut s = TcpStream::connect(("127.0.0.1", port)).unwrap();
|
|
s.set_read_timeout(Some(Duration::from_secs(60))).unwrap();
|
|
write!(s, "{method} {path} HTTP/1.1\r\nHost: 127.0.0.1\r\nContent-Type: application/json\r\nAccept: application/json, text/event-stream\r\nContent-Length: {}\r\nConnection: close\r\n\r\n", body.len()).unwrap();
|
|
s.write_all(body).unwrap();
|
|
let mut out = String::new();
|
|
s.read_to_string(&mut out).unwrap();
|
|
let status = out.split_whitespace().nth(1).and_then(|c| c.parse().ok()).unwrap_or(0);
|
|
(status, out.split_once("\r\n\r\n").map(|(_, b)| b.to_string()).unwrap_or_default())
|
|
}
|
|
|
|
/// [`http`] of a body the service stops reading: written from another thread, so that the
|
|
/// answer is read whenever it comes, and a connection the service closes on the rest of the
|
|
/// body is no failure of the exchange.
|
|
fn http_past_its_limit(port: u16, path: &str, body: Vec<u8>) -> (u16, String) {
|
|
let mut s = TcpStream::connect(("127.0.0.1", port)).unwrap();
|
|
s.set_read_timeout(Some(Duration::from_secs(120))).unwrap();
|
|
let mut w = s.try_clone().unwrap();
|
|
let head = format!("POST {path} HTTP/1.1\r\nHost: 127.0.0.1\r\nContent-Type: application/json\r\nContent-Length: {}\r\nConnection: close\r\n\r\n", body.len());
|
|
let writer = std::thread::spawn(move || {
|
|
let _ = w.write_all(head.as_bytes()).and_then(|()| w.write_all(&body));
|
|
});
|
|
let mut out = Vec::new();
|
|
let mut buf = [0u8; 1 << 16];
|
|
while let Ok(n) = s.read(&mut buf) {
|
|
if n == 0 {
|
|
break;
|
|
}
|
|
out.extend_from_slice(&buf[..n]);
|
|
}
|
|
let _ = writer.join();
|
|
let out = String::from_utf8_lossy(&out).into_owned();
|
|
let status = out.split_whitespace().nth(1).and_then(|c| c.parse().ok()).unwrap_or(0);
|
|
(status, out.split_once("\r\n\r\n").map(|(_, b)| b.to_string()).unwrap_or_default())
|
|
}
|
|
|
|
/// The service over the store at `dir`, once it answers: its process and the port it
|
|
/// bound. It binds a port the system chooses and says which in its log, as
|
|
/// tests/contract.rs's services do: a port found free here and handed to it raced, the
|
|
/// listener that found it inherited, until its exec, by a child another test forked at that
|
|
/// moment, so a test's first connection could reach that copy and be reset before the
|
|
/// service had bound the port (tests/write.rs on CI, run 28969).
|
|
fn start(dir: &Path) -> (Running, u16) {
|
|
let path = dir.join("service.log");
|
|
let mut child = Running::new(Command::new(env!("CARGO_BIN_EXE_wai-quantum-service"))
|
|
.arg("serve")
|
|
.env("WAI_QSVC_ADDR", "127.0.0.1:0")
|
|
.env("WAI_QSVC_STORE", dir.join("store"))
|
|
.env("WAI_QSVC_KEY", dir.join("signing.key"))
|
|
.env("WAI_QSVC_JOB_GB", "2")
|
|
.env("WAI_QSVC_BUDGET_GB", "4")
|
|
.env("WAI_QSVC_WORKERS", "1")
|
|
.env("WAI_QSVC_ENERGY", "0")
|
|
// A set's receipt counts, as a check does: the corpus holds more than a window's.
|
|
.env("WAI_QSVC_CLIENT_BURST", "100000")
|
|
.env("WAI_QSVC_CLIENT_VERIFIES", "100000")
|
|
.stdout(Stdio::null())
|
|
.stderr(std::fs::File::create(&path).expect("the service's log"))
|
|
.spawn()
|
|
.expect("the service starts"));
|
|
let log = || std::fs::read_to_string(&path).unwrap_or_default();
|
|
let t = Instant::now();
|
|
let port = loop {
|
|
if let Some(p) = log().lines().rev().find_map(|l| l.split(" on 127.0.0.1:").nth(1)?.split(',').next()?.trim().parse::<u16>().ok()) {
|
|
break p;
|
|
}
|
|
if let Ok(Some(status)) = child.try_wait() {
|
|
panic!("the service ended as it started ({status}): {}", log());
|
|
}
|
|
assert!(t.elapsed() < Duration::from_secs(60), "the service did not start in 60 s: {}", log());
|
|
std::thread::sleep(Duration::from_millis(50));
|
|
};
|
|
while TcpStream::connect(("127.0.0.1", port)).is_err() || http(port, "GET", "/v1/health", b"").0 != 200 {
|
|
if let Ok(Some(status)) = child.try_wait() {
|
|
panic!("the service ended ({status}): {}", log());
|
|
}
|
|
assert!(t.elapsed() < Duration::from_secs(60), "the service did not answer: {}", log());
|
|
std::thread::sleep(Duration::from_millis(50));
|
|
}
|
|
(child, port)
|
|
}
|
|
|
|
fn corpus() -> PathBuf {
|
|
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../quantum-conformance/receipts")
|
|
}
|
|
|
|
fn sorted(v: &Value) -> Vec<String> {
|
|
let mut r: Vec<String> = v.as_array().map(|a| a.iter().filter_map(Value::as_str).map(str::to_string).collect()).unwrap_or_default();
|
|
r.sort();
|
|
r
|
|
}
|
|
|
|
#[test]
|
|
fn the_service_gives_every_receipt_the_verdict_the_corpus_gives_it() {
|
|
let dir = std::env::temp_dir().join(format!("wai-qsvc-verify-{}", std::process::id()));
|
|
let _ = std::fs::remove_dir_all(&dir);
|
|
std::fs::create_dir_all(&dir).unwrap();
|
|
let (mut child, port) = start(&dir);
|
|
let manifest: Value = serde_json::from_str(&std::fs::read_to_string(corpus().join("manifest.json")).unwrap()).unwrap();
|
|
let mut wrong = Vec::new();
|
|
for e in manifest["entries"].as_array().unwrap() {
|
|
let name = e["file"].as_str().unwrap();
|
|
let want = sorted(&e["reasons"]);
|
|
let bytes = std::fs::read(corpus().join(name)).unwrap();
|
|
// Over HTTP: its text, as it is. A body that is no job at all has its
|
|
// own refusal, 400, as it always has.
|
|
let (code, answer) = http(port, "POST", "/v1/verify", &bytes);
|
|
let alone = want.len() == 1 && ["unreadable", "not_a_job", "no_receipt"].contains(&want[0].as_str());
|
|
let said: Value = serde_json::from_str(&answer).unwrap_or(Value::Null);
|
|
if !(alone && code == 400) && !(code == 200 && sorted(&said["reasons"]) == want && said["valid"].as_bool() == Some(want.is_empty())) {
|
|
wrong.push(format!("{name}: over HTTP, the manifest {want:?}, the service {code} {answer}"));
|
|
}
|
|
// As an MCP tool, given the job's text: every receipt whose bytes a string can hold
|
|
// (a tool's argument is a string, UTF-8 by its form).
|
|
let Ok(text) = String::from_utf8(bytes.clone()) else { continue };
|
|
let call = json!({ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "verify_quantum_receipt", "arguments": { "job": text } } });
|
|
let (_, answer) = http(port, "POST", "/mcp", call.to_string().as_bytes());
|
|
let said: Value = serde_json::from_str(&answer).unwrap_or(Value::Null);
|
|
let s = &said["result"]["structuredContent"];
|
|
let _ = alone;
|
|
if sorted(&s["reasons"]) != want || s["valid"].as_bool() != Some(want.is_empty()) || s["given"] != "text" {
|
|
wrong.push(format!("{name}: as a tool, the manifest {want:?}, the service {s}"));
|
|
}
|
|
// As an MCP tool, given the job as members (the job as an object, and the call's
|
|
// arguments as the job's own members), each written into the call as the corpus
|
|
// writes it: judged as the message writes it, so a member named twice and a whole
|
|
// number past a double are seen. A job that is no JSON value makes a message that
|
|
// is none, which is the protocol's error.
|
|
for (form, arguments) in [("an object", format!("{{\"job\": {text}}}")), ("its members", text.clone())] {
|
|
// A call's arguments are an object: a body that is none is given as its text, or as `job`.
|
|
if form == "its members" && !text.trim_start().starts_with('{') {
|
|
continue;
|
|
}
|
|
let call = format!("{{\"jsonrpc\": \"2.0\", \"id\": 1, \"method\": \"tools/call\", \"params\": {{\"name\": \"verify_quantum_receipt\", \"arguments\": {arguments}}}}}");
|
|
let (_, answer) = http(port, "POST", "/mcp", call.as_bytes());
|
|
let said: Value = serde_json::from_str(&answer).unwrap_or(Value::Null);
|
|
let s = &said["result"]["structuredContent"];
|
|
let no_message = !s.is_object() && want == ["unreadable"];
|
|
if !no_message && (sorted(&s["reasons"]) != want || s["valid"].as_bool() != Some(want.is_empty()) || s["given"] != "members") {
|
|
wrong.push(format!("{name}: as a tool, given {form}, the manifest {want:?}, the service {s} ({})", answer.chars().take(300).collect::<String>()));
|
|
}
|
|
if name.contains("read-duplicate") && s["reasons"] != json!(["unreadable"]) {
|
|
wrong.push(format!("{name}: as a tool, given {form}, a member named twice was not seen: {s}"));
|
|
}
|
|
}
|
|
}
|
|
let _ = child.kill();
|
|
let _ = child.wait();
|
|
let _ = std::fs::remove_dir_all(&dir);
|
|
assert!(wrong.is_empty(), "{} wrong:\n{}", wrong.len(), wrong.join("\n"));
|
|
}
|
|
|
|
/// Text that does not read as a job's, a body that is no job, and a result with no receipt
|
|
/// each get a verdict with that reason alone (§8.4), as every surface gives it, not a refusal
|
|
/// of the request. As a tool, given the job's members, the host's unsealed ones among them, a
|
|
/// wasmQ job's unsealed status is noted where it differs from what is sealed.
|
|
#[test]
|
|
fn every_body_gets_a_verdict_and_the_tool_reads_the_body_it_is_given() {
|
|
let dir = std::env::temp_dir().join(format!("wai-qsvc-verdicts-{}", std::process::id()));
|
|
let _ = std::fs::remove_dir_all(&dir);
|
|
std::fs::create_dir_all(&dir).unwrap();
|
|
let (mut child, port) = start(&dir);
|
|
let job = std::fs::read(corpus().join("jobs/cli-wasmq-fault.json")).unwrap();
|
|
let mut bom = vec![0xef, 0xbb, 0xbf];
|
|
bom.extend_from_slice(&job);
|
|
let mut problems = Vec::new();
|
|
for (what, body, want) in [
|
|
("a byte order mark", bom, "unreadable"),
|
|
("not JSON", b"not json".to_vec(), "unreadable"),
|
|
("not UTF-8", vec![b'{', 0xff, b'}'], "unreadable"),
|
|
("no job", br#"{"request": {}}"#.to_vec(), "not_a_job"),
|
|
("no receipt", br#"{"request": {}, "result": {"counts": {}}}"#.to_vec(), "no_receipt"),
|
|
] {
|
|
let (code, answer) = http(port, "POST", "/v1/verify", &body);
|
|
let said: Value = serde_json::from_str(&answer).unwrap_or(Value::Null);
|
|
if code != 200 || said["reasons"] != json!([want]) || said["valid"] != false {
|
|
problems.push(format!("{what}: {code} {answer}"));
|
|
}
|
|
}
|
|
let parsed: Value = serde_json::from_slice(&job).unwrap();
|
|
let args = json!({ "status": "done", "method": parsed["method"], "request": parsed["request"], "result": parsed["result"] });
|
|
let call = json!({ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "verify_quantum_receipt", "arguments": args } });
|
|
let (_, answer) = http(port, "POST", "/mcp", call.to_string().as_bytes());
|
|
let said: Value = serde_json::from_str(&answer).unwrap_or(Value::Null);
|
|
let s = &said["result"]["structuredContent"];
|
|
if s["valid"] != true || s["wasmq"]["notes"] != json!([r#"the host's unsealed status says "done"; the sealed result says "fault""#]) {
|
|
problems.push(format!("the tool, given the body: {s}"));
|
|
}
|
|
let _ = child.kill();
|
|
let _ = child.wait();
|
|
let _ = std::fs::remove_dir_all(&dir);
|
|
assert!(problems.is_empty(), "{}", problems.join("\n"));
|
|
}
|
|
|
|
/// One answer to every form of a job (the provider protocol's section 8.4): an estimate or
|
|
/// an algorithm's answer with no receipt, or that is no job, is that reason alone over HTTP
|
|
/// and from the tool, given its text, the job as an object or its members; a forged
|
|
/// estimate sealed again is run again and is not valid however it is given; a genuine job
|
|
/// is valid given as an object; a job past 1 MiB is read, and one past what verify reads is
|
|
/// `too_large`, a verdict like any other.
|
|
#[test]
|
|
fn one_answer_to_every_form_of_a_job() {
|
|
let dir = std::env::temp_dir().join(format!("wai-qsvc-one-answer-{}", std::process::id()));
|
|
let _ = std::fs::remove_dir_all(&dir);
|
|
std::fs::create_dir_all(&dir).unwrap();
|
|
let (mut child, port) = start(&dir);
|
|
let estimate = wai_quantum::engine::estimate::FORMAT;
|
|
let algorithms = wai_quantum::engine::schema::ALGORITHMS;
|
|
let key = wai_quantum::ed25519_dalek::SigningKey::from_bytes(&[9; 32]);
|
|
let angles = "OPENQASM 3.0;\nqubit[4] q;\nh q;\nrz(0.3) q[0];\nccx q[0], q[1], q[2];\nry(1.1) q[1];\nt q[2];\nctrl(3) @ x q[0], q[1], q[2], q[3];\n";
|
|
let made = wai_quantum::engine::estimate::sealed(&json!({ "qasm": angles, "budget": 1e-3, "max_slowdown": 64 }), &key).unwrap();
|
|
let mut forged = made.clone();
|
|
forged["result"]["estimate"]["physical_qubits"] = json!(1);
|
|
forged["result"].as_object_mut().unwrap().remove("receipt");
|
|
wai_quantum::engine::receipt::seal_into(&key, &forged["request"].clone(), &mut forged["result"]);
|
|
let tool = |args: Value| -> Value {
|
|
let call = json!({ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "verify_quantum_receipt", "arguments": args } });
|
|
let (_, answer) = http(port, "POST", "/mcp", call.to_string().as_bytes());
|
|
let said: Value = serde_json::from_str(&answer).unwrap_or(Value::Null);
|
|
said["result"]["structuredContent"].clone()
|
|
};
|
|
let mut problems = Vec::new();
|
|
let mut each = |what: &str, body: &Value, ok: &dyn Fn(&Value) -> bool| {
|
|
let (code, answer) = http(port, "POST", "/v1/verify", body.to_string().as_bytes());
|
|
let over_http: Value = serde_json::from_str(&answer).unwrap_or(Value::Null);
|
|
let mut members = body.as_object().cloned().unwrap_or_default();
|
|
members.remove("job");
|
|
for (form, said) in [("POST /v1/verify", if code == 200 { over_http } else { json!({ "status": code, "body": answer }) }), ("the tool, its text", tool(json!({ "job": body.to_string() }))), ("the tool, the job as an object", tool(json!({ "job": body }))), ("the tool, its members", tool(Value::Object(members)))] {
|
|
if !ok(&said) {
|
|
problems.push(format!("{what}, {form}: {said}"));
|
|
}
|
|
}
|
|
};
|
|
let alone = |want: &'static str| move |v: &Value| v["valid"] == false && v["reasons"] == json!([want]);
|
|
each("an estimate with no receipt", &json!({ "request": { "qasm": "OPENQASM 3.0;\nqubit q;\nh q;\n" }, "result": { "format": estimate } }), &alone("no_receipt"));
|
|
each("an estimate with no receipt and no program", &json!({ "request": {}, "result": { "format": estimate } }), &alone("no_receipt"));
|
|
each("an estimate that is no job", &json!({ "request": 5, "result": { "format": estimate } }), &alone("not_a_job"));
|
|
each("an algorithm's answer with no receipt", &json!({ "request": { "qasm": "OPENQASM 3.0;\nqubit q;\n" }, "result": { "schema": algorithms, "call": "gradient" } }), &alone("no_receipt"));
|
|
each("an algorithm's answer that is no job", &json!({ "request": 5, "result": { "schema": algorithms } }), &alone("not_a_job"));
|
|
each("a genuine estimate", &made, &|v: &Value| v["valid"] == true && v["reproduced"] == true);
|
|
each("a forged estimate, sealed again", &forged, &|v: &Value| v["valid"] == false && v["reproduced"] == false && v["signed"] == true);
|
|
let job: Value = serde_json::from_slice(&std::fs::read(corpus().join("jobs/cli-wasmq-run.json")).unwrap()).unwrap();
|
|
each("a genuine wasmQ job", &job, &|v: &Value| v["valid"] == true);
|
|
// A job of 1.5 MiB (a member nothing seals holds the rest) is read and judged.
|
|
let mut big = job.clone();
|
|
big["pad"] = json!("x".repeat(3 << 19));
|
|
each("a genuine job of 1.5 MiB", &big, &|v: &Value| v["valid"] == true);
|
|
// A request no host runs again, sealed: an estimate's verdict says so, as an algorithm's
|
|
// answer's does.
|
|
let reseal = |mut body: Value| {
|
|
body["result"].as_object_mut().unwrap().remove("receipt");
|
|
wai_quantum::engine::receipt::seal_into(&key, &body["request"].clone(), &mut body["result"]);
|
|
body
|
|
};
|
|
let unrunnable = reseal(json!({ "request": {}, "result": made["result"].clone() }));
|
|
each("an estimate whose request no estimate runs", &unrunnable, &|v: &Value| v["valid"] == false && v["reproduced"] == false && v["rerun_error"].is_string());
|
|
// An algorithm's answer whose program nests past the reader's bound (a flat sum of 2000
|
|
// terms) gets its verdict, and the service stays up.
|
|
let gradient = json!({ "qasm": "OPENQASM 3.0;\ninput float t;\nqubit q;\nrx(t) q;\n", "params": { "t": 0.4 }, "hamiltonian": ["Z0"] });
|
|
let answered = wai_quantum::engine::variational::seal(&gradient, wai_quantum::engine::variational::call("gradient", &gradient).unwrap(), &key);
|
|
each("a genuine gradient", &answered, &|v: &Value| v["valid"] == true && v["rerun"] == true);
|
|
let mut deep = answered.clone();
|
|
// Nested 1000 deep in brackets, past the reader's bound (a flat sum is read, at any length).
|
|
deep["request"]["qasm"] = json!(format!("OPENQASM 3.0;\ninput float t;\nqubit q;\nint x = {}1{};\nrx(t) q;\n", "(".repeat(1000), ")".repeat(1000)));
|
|
each("a gradient of a program past the nesting bound", &reseal(deep), &|v: &Value| v["valid"] == false && v["rerun"] == false && v["rerun_error"].as_str().is_some_and(|e| e.contains("Q006")));
|
|
let up = http(port, "GET", "/v1/health", b"").0 == 200;
|
|
// Past what verify reads: too_large, with what it reads, on every surface. Past the bytes:
|
|
let at_most = json!({ "bytes": 4 << 20, "values": 1 << 19 });
|
|
let too_large = |v: &Value| v["valid"] == false && v["reasons"] == json!(["too_large"]) && v["read_at_most"] == json!({ "bytes": 4 << 20, "values": 1 << 19 });
|
|
let padded = |n: usize| {
|
|
let mut j = job.clone();
|
|
j["pad"] = json!("");
|
|
let rest = j.to_string().len();
|
|
j["pad"] = json!("x".repeat(n - rest));
|
|
j
|
|
};
|
|
each("a job of 4 MiB", &padded(4 << 20), &|v: &Value| v["valid"] == true);
|
|
each("a job of 4 MiB and a byte", &padded((4 << 20) + 1), &too_large);
|
|
// a job of quotes, 3 MiB of text, which a string of it doubles again, within them:
|
|
let mut quoted = job.clone();
|
|
quoted["pad"] = json!("\"".repeat(3 << 19));
|
|
each("a job of 3 MiB of escaped quotes", "ed, &|v: &Value| v["valid"] == true);
|
|
// past the values, in a few bytes each:
|
|
let mut many = job.clone();
|
|
many["pad"] = json!(vec![0; 1 << 19]);
|
|
each("a job of 2^19 values more", &many, &too_large);
|
|
if !up {
|
|
problems.push("the service did not stay up".into());
|
|
}
|
|
// and past what /mcp reads of a message: its verdict by its id, too_large.
|
|
let mut huge = job.clone();
|
|
huge["pad"] = json!("x".repeat(30 << 20));
|
|
let (code, answer) = http_past_its_limit(port, "/v1/verify", huge.to_string().into_bytes());
|
|
let said: Value = serde_json::from_str(&answer).unwrap_or(Value::Null);
|
|
if code != 200 || said["reasons"] != json!(["too_large"]) || said["read_at_most"] != at_most {
|
|
problems.push(format!("a job of 30 MiB: {code} {}", &answer[..answer.len().min(300)]));
|
|
}
|
|
let call = json!({ "jsonrpc": "2.0", "id": 77, "method": "tools/call", "params": { "name": "verify_quantum_receipt", "arguments": { "job": huge } } });
|
|
let (code, answer) = http_past_its_limit(port, "/mcp", call.to_string().into_bytes());
|
|
let said: Value = serde_json::from_str(&answer).unwrap_or(Value::Null);
|
|
if code != 200 || said["id"] != 77 || !too_large(&said["result"]["structuredContent"]) {
|
|
problems.push(format!("a call of 30 MiB: {code} {}", &answer[..answer.len().min(300)]));
|
|
}
|
|
// A job nested past what a reader takes, as an object or as members: unreadable, by its id.
|
|
for d in [127usize, 128, 140, 1000] {
|
|
let raw = format!("{{\"request\":{},\"result\":{},\"pad\":{}{}}}", job["request"], job["result"], "[".repeat(d - 1), "]".repeat(d - 1));
|
|
for (form, args) in [("as an object", format!("{{\"job\":{raw}}}")), ("as members", raw.clone()), ("as text", json!({ "job": raw }).to_string())] {
|
|
let call = format!("{{\"jsonrpc\":\"2.0\",\"id\":5,\"method\":\"tools/call\",\"params\":{{\"name\":\"verify_quantum_receipt\",\"arguments\":{args}}}}}");
|
|
let (code, answer) = http(port, "POST", "/mcp", call.as_bytes());
|
|
let said: Value = serde_json::from_str(&answer).unwrap_or(Value::Null);
|
|
let s = &said["result"]["structuredContent"];
|
|
let want = if d <= 127 { s["valid"] == true } else { s["reasons"] == json!(["unreadable"]) };
|
|
if code != 200 || said["id"] != 5 || !want {
|
|
problems.push(format!("a job {d} deep, {form}: {code} {}", &answer[..answer.len().min(300)]));
|
|
}
|
|
}
|
|
}
|
|
let _ = child.kill();
|
|
let _ = child.wait();
|
|
let _ = std::fs::remove_dir_all(&dir);
|
|
assert!(problems.is_empty(), "{}", problems.join("\n"));
|
|
}
|
|
|
|
/// [`http`], or nothing when the service does not answer.
|
|
fn try_http(port: u16, method: &str, path: &str, body: &[u8]) -> Option<(u16, String)> {
|
|
let mut s = TcpStream::connect(("127.0.0.1", port)).ok()?;
|
|
s.set_read_timeout(Some(Duration::from_secs(60))).ok()?;
|
|
write!(s, "{method} {path} HTTP/1.1\r\nHost: 127.0.0.1\r\nContent-Type: application/json\r\nContent-Length: {}\r\nConnection: close\r\n\r\n", body.len()).ok()?;
|
|
s.write_all(body).ok()?;
|
|
let mut out = Vec::new();
|
|
s.read_to_end(&mut out).ok()?;
|
|
let out = String::from_utf8_lossy(&out).into_owned();
|
|
let status = out.split_whitespace().nth(1)?.parse().ok()?;
|
|
Some((status, out.split_once("\r\n\r\n").map(|(_, b)| b.to_string()).unwrap_or_default()))
|
|
}
|
|
|
|
/// A program nested past the reader's bound (Q006) is refused on every route that reads one,
|
|
/// a flat sum of 2000 terms (one level, however long) is read, and the service stays up: the
|
|
/// sum, 4 KB, used to overflow a worker's stack and end the service, on the routes that read
|
|
/// a program on an async worker.
|
|
#[test]
|
|
fn a_program_past_the_nesting_bound_is_refused_and_the_service_stays_up() {
|
|
let dir = std::env::temp_dir().join(format!("wai-qsvc-deep-{}", std::process::id()));
|
|
let _ = std::fs::remove_dir_all(&dir);
|
|
std::fs::create_dir_all(&dir).unwrap();
|
|
let (mut child, mut port) = start(&dir);
|
|
let head = "OPENQASM 3.0;\ninclude \"stdgates.inc\";\nqubit[2] q;\nbit[2] c;\n";
|
|
let programs = [
|
|
("a sum of 2000 terms", format!("{head}int x = 1{};\nh q[0];\nc = measure q;\n", "+1".repeat(1999))),
|
|
("1000 brackets", format!("{head}rz({}0.5{}) q[0];\nc = measure q;\n", "(".repeat(1000), ")".repeat(1000))),
|
|
("1000 blocks", format!("{head}{}h q[0];{}\nc = measure q;\n", "{ ".repeat(1000), " }".repeat(1000))),
|
|
];
|
|
let mut wrong = Vec::new();
|
|
for (what, p) in &programs {
|
|
let bodies = [
|
|
("/v1/jobs", json!({ "qasm": p, "shots": 1, "seed": 1 })),
|
|
("/v1/run", json!({ "qasm": p, "shots": 1, "seed": 1 })),
|
|
("/v1/estimate", json!({ "qasm": p })),
|
|
("/v1/gradient", json!({ "qasm": p, "observables": ["Z0"] })),
|
|
("/v1/critique", json!({ "qasm": p })),
|
|
("/v1/compile", json!({ "qasm": p })),
|
|
("/v1/verify", json!({ "request": { "qasm": p }, "result": { "format": "wai.quantum.estimate/1" } })),
|
|
];
|
|
for (path, body) in bodies {
|
|
let (code, answer) = try_http(port, "POST", path, body.to_string().as_bytes()).unwrap_or((0, String::new()));
|
|
let up = try_http(port, "GET", "/v1/health", b"").is_some_and(|(code, _)| code == 200);
|
|
eprintln!("{what} at {path}: {code} {}", answer.chars().take(160).collect::<String>());
|
|
if !up {
|
|
wrong.push(format!("{what} at {path}: the service ended ({code} {answer})"));
|
|
child.stop();
|
|
(child, port) = start(&dir);
|
|
continue;
|
|
}
|
|
// Read at once, a nested one is refused by its code; queued, the job fails by it.
|
|
// The sum is flat, and read.
|
|
let nested = *what != "a sum of 2000 terms";
|
|
if nested && (200..300).contains(&code) && !answer.contains("Q006") && path != "/v1/jobs" && path != "/v1/verify" {
|
|
wrong.push(format!("{what} at {path}: {code} without Q006: {}", answer.chars().take(300).collect::<String>()));
|
|
}
|
|
if !nested && (answer.contains("Q006") || code >= 500) {
|
|
wrong.push(format!("{what} at {path}: {code}: {}", answer.chars().take(300).collect::<String>()));
|
|
}
|
|
}
|
|
}
|
|
let _ = child.kill();
|
|
let _ = child.wait();
|
|
let _ = std::fs::remove_dir_all(&dir);
|
|
assert!(wrong.is_empty(), "{} wrong:\n{}", wrong.len(), wrong.join("\n"));
|
|
}
|
|
|
|
/// An energy-landscape job over HTTP (wai/extensions/quantum-landscape.md): each operation
|
|
/// answered on the extension's reference, sealed with the service's key, and verified by the
|
|
/// service's own verify with its form held (§5.4); a program the reader refuses is refused
|
|
/// with its line, and a request that names a provider is refused (J002), as the service
|
|
/// reaches none.
|
|
#[test]
|
|
fn an_energy_landscape_job_is_sealed_and_verified_by_its_form() {
|
|
let dir = std::env::temp_dir().join(format!("wai-qsvc-landscape-{}", std::process::id()));
|
|
let _ = std::fs::remove_dir_all(&dir);
|
|
std::fs::create_dir_all(&dir).unwrap();
|
|
let (mut child, port) = start(&dir);
|
|
let program = "vars 4\ncost 1\nagree 0 1\nagree 0 2\nagree 1 2\noppose 2 3\nagree 3 0\nsolve restarts=2 steps=9\n";
|
|
for (op, request) in [
|
|
("run", json!({ "program": program, "seed": "7" })),
|
|
("replay", json!({ "program": program, "seeds": 3 })),
|
|
("sweep", json!({ "program": program, "seeds": 2, "temperatures": [0, 1.5] })),
|
|
("compare", json!({ "program": program, "seeds": 2 })),
|
|
] {
|
|
let (code, body) = http(port, "POST", &format!("/v1/landscape/{op}"), request.to_string().as_bytes());
|
|
assert_eq!(code, 200, "{op}: {body}");
|
|
let job: Value = serde_json::from_str(&body).unwrap();
|
|
assert_eq!(job["result"]["schema"], "wai.quantum.landscape/1", "{op}");
|
|
let c = wai_quantum::engine::job::check(&job);
|
|
assert!(c.valid(), "{op}: {:?} {:?}", c.reasons, c.landscape);
|
|
let (code, said) = http(port, "POST", "/v1/verify", body.as_bytes());
|
|
let said: Value = serde_json::from_str(&said).unwrap();
|
|
assert!(code == 200 && said["valid"] == true, "{op}: {said}");
|
|
assert_eq!(said["landscape"]["op"], op, "{said}");
|
|
// The same answer from another member, as a host would never seal it: its form is broken.
|
|
let mut forged = job.clone();
|
|
forged["result"]["member"]["parity"] = json!("bit-exact");
|
|
let (_, said) = http(port, "POST", "/v1/verify", forged.to_string().as_bytes());
|
|
let said: Value = serde_json::from_str(&said).unwrap();
|
|
assert_eq!(said["valid"], false, "{said}");
|
|
}
|
|
let (code, body) = http(port, "POST", "/v1/landscape/run", json!({ "program": "vars 2\noppos 0 1\nsolve\n" }).to_string().as_bytes());
|
|
assert!(code == 422 && body.contains("Line 2: "), "{code} {body}");
|
|
let (code, body) = http(port, "POST", "/v1/landscape/run", json!({ "program": program, "provider": "lab" }).to_string().as_bytes());
|
|
assert!(code == 422 && body.contains("J002"), "{code} {body}");
|
|
let (code, _) = http(port, "POST", "/v1/landscape/examples", b"{}");
|
|
assert_eq!(code, 404);
|
|
let _ = child.kill();
|
|
let _ = child.wait();
|
|
let _ = std::fs::remove_dir_all(&dir);
|
|
}
|
|
|
|
/// The sign-off loop over HTTP, made in the service's reading child: a check that names
|
|
/// each case's layer, a grade sealed with the service's key that its own verify makes again,
|
|
/// a task that would publish its answer refused, a job another key sealed attested against a
|
|
/// trust list that names that key and the attestation verified, refused against a list that
|
|
/// does not, and a grade whose wide case is past the service's bounds refused at that case.
|
|
#[test]
|
|
fn the_sign_off_loop_answers_over_http_and_its_grade_and_sign_off_verify() {
|
|
let dir = std::env::temp_dir().join(format!("wai-qsvc-loop-{}", std::process::id()));
|
|
let _ = std::fs::remove_dir_all(&dir);
|
|
std::fs::create_dir_all(&dir).unwrap();
|
|
let (mut child, port) = start(&dir);
|
|
let post = |path: &str, body: &Value| {
|
|
let (code, text) = http(port, "POST", path, body.to_string().as_bytes());
|
|
(code, serde_json::from_str::<Value>(&text).unwrap_or_else(|_| json!(text)))
|
|
};
|
|
let pair = "OPENQASM 3.0;\nqubit[2] q;\nbit[2] c;\nh q[0];\ncx q[0], q[1];\nc = measure q;\n";
|
|
let fair = json!({ "distribution": { "00": 0.5, "11": 0.5 } });
|
|
let (code, checked) = post("/v1/check", &json!({ "qasm": pair, "intent": fair }));
|
|
assert_eq!((code, checked["passed"].as_bool(), checked["grade"]["cases"][0]["layer"].as_str()), (200, Some(true), Some("exact_simulation")), "{checked}");
|
|
let (code, graded) = post("/v1/grade", &json!({ "grade": { "task": { "title": "A Bell pair", "intent": fair }, "program": pair } }));
|
|
assert_eq!((code, graded["result"]["passed"].as_bool(), graded["status"].as_str()), (200, Some(true), Some("done")), "{graded}");
|
|
let (code, said) = post("/v1/verify", &graded);
|
|
assert_eq!((code, said["valid"].as_bool(), said["answer"]["again"].as_str()), (200, Some(true), Some("reproduced")), "{said}");
|
|
let (code, leaky) = post("/v1/grade", &json!({ "grade": { "task": { "intent": fair, "reference": pair }, "program": pair } }));
|
|
assert_eq!((code, leaky["code"].as_str()), (422, Some("J001")), "{leaky}");
|
|
// A job another key sealed, attested here.
|
|
let other = wai_quantum::ed25519_dalek::SigningKey::from_bytes(&[23; 32]);
|
|
let ran = wai_quantum::engine::job::run(&json!({ "qasm": pair, "shots": 64, "seed": 5 }), 1 << 30, &other, json!({ "unmetered": "a test" })).unwrap();
|
|
let listing = |k: &str| json!({ "format": "wai.quantum.receipt-keys/1", "algorithm": "ed25519", "keys": [{ "key": k }] });
|
|
let (code, attested) = post("/v1/attest", &json!({ "job": ran.to_string(), "intent": { "outcomes": ["00", "11"] }, "trust": listing(ran["result"]["receipt"]["key"].as_str().unwrap()) }));
|
|
assert_eq!((code, attested["result"]["verdict"]["holds"].as_bool(), attested["attested"]["held_to"]["trust"].as_bool()), (200, Some(true), Some(true)), "{attested}");
|
|
let (code, said) = post("/v1/verify", &json!({ "request": attested["request"], "result": attested["result"] }));
|
|
assert_eq!((code, said["valid"].as_bool(), said["answer"]["again"].as_str()), (200, Some(true), Some("reproduced")), "{said}");
|
|
let (code, untrusted) = post("/v1/attest", &json!({ "job": ran.to_string(), "trust": listing(&"00".repeat(32)) }));
|
|
assert!(code == 422 && untrusted["code"] == "J001" && untrusted["error"].as_str().unwrap().contains("key_untrusted"), "{code} {untrusted}");
|
|
// A generator's wide case, compared with a reference past what the service checks.
|
|
let mut generator = String::from("OPENQASM 3.0;\ninput int n;\nqubit[n] q;\nh q;\n");
|
|
for _ in 0..200 {
|
|
generator += "rz(0.3) q;\n";
|
|
}
|
|
let reference = wai_quantum::engine::front::at_inputs(&generator, &wai_quantum::qasm::Inputs::from([("n".to_string(), 22)])).unwrap();
|
|
let task = json!({ "cases": [{ "name": "n=2", "inputs": { "n": 2 }, "intent": fair }, { "name": "n=22", "inputs": { "n": 22 }, "intent": { "equivalent": reference } }] });
|
|
let (code, wide) = post("/v1/grade", &json!({ "grade": { "task": task, "program": generator } }));
|
|
assert!(code == 422 && wide["code"] == "J031" && wide["error"].as_str().unwrap().starts_with("case 1:"), "{code} {wide}");
|
|
let _ = child.kill();
|
|
let _ = child.wait();
|
|
let _ = std::fs::remove_dir_all(&dir);
|
|
}
|