open-standards/wai/wai-quantum-service/tests/write.rs
Transaction Science 655b366aa1
Some checks failed
wai-quantum command line / does this change touch it (push) Successful in 25s
wai-quantum docs, course and locales / does this change touch it (push) Successful in 23s
WAI operation catalogue / does this change touch it (push) Successful in 28s
wai-quantum editors / does this change touch it (push) Successful in 23s
wai-quantum service / does this change touch it (push) Successful in 17s
wai-quantum for Python / does this change touch it (push) Successful in 18s
wai-quantum extension points / does this change touch it (push) Successful in 18s
wai-quantum error correction and mitigation / does this change touch it (push) Successful in 18s
wai-quantum variational and algorithm library / does this change touch it (push) Successful in 21s
wai-quantum studio and desktop / does this change touch it (push) Successful in 24s
wai-quantum views in agent hosts / does this change touch it (push) Successful in 19s
wai-quantum verdicts and repairs / does this change touch it (push) Successful in 21s
wai-sink-android native sink library / does this change touch it (push) Successful in 11s
WAI Studio / does this change touch it (push) Successful in 12s
wai-sink-swift native sink package / does this change touch it (push) Successful in 13s
wai-quantum resource estimation / does this change touch it (push) Successful in 2m1s
wai-quantum OpenQASM reader / does this change touch it (push) Successful in 2m22s
wai-quantum pulse and dynamics / does this change touch it (push) Successful in 2m22s
wai-quantum for JavaScript / does this change touch it (push) Successful in 2m25s
wai-quantum contract / does this change touch it (push) Successful in 2m32s
wai-quantum other formats / does this change touch it (push) Successful in 2m26s
joule-moq-room room service / joule-moq-room · does this change touch it (push) Successful in 2m46s
wai-audit collector and player integration / does this change touch it (push) Successful in 2m39s
wai-quantum command line / command line · macos (push) Successful in 2m38s
wai-quantum service / service energy · linux packages (push) Successful in 2m19s
wai-quantum editors / editors · macos (push) Successful in 4m22s
WAI breaking changes and the conformance runner / WAI breaking · does this change touch WAI (push) Successful in 12s
wai-quantum service / service · macos (push) Successful in 6m19s
wai-quantum studio and desktop / desktop app · macos (push) Successful in 4m50s
wai-quantum wasmQ / does this change touch it (push) Successful in 10s
wai-quantum for Python / python · macos (push) Successful in 6m32s
wai-quantum pulse and dynamics / the same bits · macos (push) Successful in 1m17s
wai-quantum docs, course and locales / the Studio's words in every locale · linux (push) Successful in 22s
wai-audit collector and player integration / collector, player integration and the browser end to end · macos (push) Successful in 1m26s
WAI operation catalogue / the catalogue and every door · linux (push) Failing after 45s
wai-quantum wasmQ / profile, engines, reference modules, adversarial, vectors · macos (push) Successful in 2m6s
wai-quantum service / service · linux (push) Failing after 36s
wai-quantum extension points / the registry under wasm32-wasip2 (wasmtime) (push) Failing after 33s
wai-quantum error correction and mitigation / codes, decoders, texts and mitigation · linux, release (push) Failing after 22s
wai-quantum studio and desktop / desktop app · linux (push) Failing after 4s
wai-quantum studio and desktop / studio · browser (push) Failing after 4s
wai-quantum variational and algorithm library / parameters, gradients, optimisers, VQE, QAOA and builders · linux, release (push) Failing after 2s
wai-quantum verdicts and repairs / verdicts, repairs and the intent set · linux (push) Failing after 2s
wai-sink-android native sink library / every ABI, the AAR, and the instrumented tests in a headless emulator · macos (push) Successful in 6m54s
wai-quantum docs, course and locales / the course and the command line's languages · linux (push) Successful in 2m58s
wai-quantum verdicts and repairs / the intent set and the verdict under wasm32-wasip2 (wasmtime) (push) Failing after 18s
wai-quantum views in agent hosts / views · the page and its engine (push) Failing after 6s
WAI Studio / Studio in WebKit · linux (push) Failing after 1m24s
WAI Studio / the catalogue's actions, the unit tests and the core · linux (push) Successful in 1m28s
wai-quantum extension points / the registry and the example · linux (push) Failing after 4m4s
WAI Studio / Studio in Chromium and Firefox · macos (push) Successful in 7m39s
wai-quantum OpenQASM reader / corpus under wasm32-wasip2 (wasmtime) (push) Successful in 1m6s
wai-quantum OpenQASM reader / reader and corpus · linux (push) Successful in 2m23s
wai-quantum editors / editors · linux (push) Successful in 6m26s
wai-quantum for JavaScript / javascript · linux (push) Successful in 3m35s
wai-quantum for Python / python · linux (push) Successful in 6m26s
wai-quantum views in agent hosts / views · a scripted host (push) Failing after 5m32s
wai-quantum other formats / the set under wasm32-wasip2 (wasmtime) (push) Successful in 2m45s
wai-quantum contract / the policy is linked, and the deprecations page reads (push) Successful in 11s
wai-quantum pulse and dynamics / reader, simulator and pulse jobs · linux, release (push) Successful in 1m43s
wai-quantum pulse and dynamics / reader, simulator and pulse jobs under wasm32-wasip2 (wasmtime) (push) Successful in 2m6s
wai-audit collector and player integration / collector · linux (push) Successful in 1m18s
wai-quantum wasmQ / the reference modules rebuild to their modules of record · linux (push) Successful in 40s
wai-quantum other formats / readers, writers and the set · linux (push) Successful in 5m23s
WAI breaking changes and the conformance runner / WAI breaking · wire, record and API against main · linux (push) Successful in 2m33s
repository checks / lockfiles, advisories, tests in CI, workflow hygiene, spec and section references, public text (push) Successful in 4m49s
wai-quantum extension points / the registry and the example · linux-arm64 (push) Failing after 3m4s
wai-quantum variational and algorithm library / parameters, gradients, optimisers, VQE, QAOA and builders · linux-arm64, release (push) Successful in 2m52s
wai-quantum wasmQ / interpreter and vectors under wasm32-wasip2 (wasmtime) (push) Successful in 2m50s
wai-quantum wasmQ / profile, engines, reference modules, adversarial, vectors · linux (push) Successful in 3m45s
wai-sink-swift native sink package / libwai slices, desktop tests, simulator tests · macos (push) Successful in 16m1s
WAI breaking changes and the conformance runner / WAI breaking and conform · their tests, and the independent verifiers through wai conform · linux (push) Successful in 6m20s
wai-quantum contract / API compatibility · linux (push) Successful in 7m48s
wai-quantum contract / schemas and the committed tool lists · linux (push) Successful in 7m49s
wai-quantum resource estimation / estimation and calibration · linux, release (push) Successful in 12m15s
joule-moq-room room service / joule-moq-room, every target (push) Successful in 5m29s
wai-sink-android native sink library / the JNI layer and the platform-free half on the host JVM · linux-arm64 (push) Successful in 10m57s
wai-quantum pulse and dynamics / the same bits · linux-arm64 (push) Successful in 11m28s
wai-quantum studio and desktop / desktop app · windows (push) Successful in 12m28s
wai-quantum docs, course and locales / every command and snippet in the docs · linux (push) Successful in 40m54s
wai-quantum command line / command line · linux (push) Successful in 53m9s
wai-quantum-service: POST /v1/write and write_circuit, a program written for a task on the service's own model
A caller sends a task in the course's exercise form; the service's own code model
writes an OpenQASM program for it with the writer's loop, the engine checks each
attempt as it checks a lesson's answer, and the answer is the transcript, sealed
with the service's receipt so POST /v1/verify checks it. Re-landed from #648.

- A write is a job (Work::Write): admitted in #676's order (the client's window
  first, then a reading turn, its programs read in the capped reading child),
  kept, reloaded and run again as a job is. A queued write keeps the plan it was
  admitted with across a restart and fails only if the service has no model.
- The writer process starts with the service from its kept binary copy, in the
  worker's sandbox on macOS, in its own process group, carrying the parent pid,
  first in line for the kernel under memory pressure. WAI_QSVC_WRITER_GB and
  WAI_QSVC_WRITE_SECS bound it; one client holds one queued or running write.
- Refusals J080 (no model, 503), J081 (task) and J082 (options); main's contracts
  hold J070 to J074. A write carries no contract, so the energy ceilings do not
  apply; its energy figure is built on the engine's own functions.
- Discovery (GET /v1, quantum_service_info), OpenAPI, the MCP task write_circuit,
  an attempt event per attempt, and its own label in GET /metrics.
- WAI_QSVC_WRITER_SCRIPT runs the scripted model in the model's place for tests;
  smoke.sh writes a Bell pair's program with SMOKE_WRITE=1 and otherwise checks
  that a service with no model refuses a write.
- offload.rs: each client counts the reading turns its requests actually hold,
  so no turn is lent past its share while one passes between its requests. The
  new test no_turn_is_lent_while_one_passes_between_a_client_s_requests fails
  3 of 3 on main and passes 3 of 3 here.

Tested on the Framework (Linux) and the M3 Ultra (macOS): service 140 tests and
clippy with warnings denied; wai-quantum full library 1,517; the engine-only and
engine+receipt builds with warnings denied; the writer leg; the course and docs
legs; check_schemas.py; smoke over HTTP with and without a scripted writer (its
receipt verifies, a changed transcript does not); the shipped image.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-09 20:59:46 -05:00

428 lines
24 KiB
Rust

//! A program written for a task, against the service itself, its writer
//! process running a script in the model's place (`WAI_QSVC_WRITER_SCRIPT`):
//! a Bell pair's task, whose first program the engine finds wrong and whose
//! repair passes. The script is a FIFO, so the writer holds at its start, with
//! its model "loading", until the test writes the script into it: until then
//! every write stays queued, which is how the per-client limit, the order and
//! the attempts' events are seen as they happen.
use serde_json::{Value, json};
use std::io::{Read, Write};
use std::net::{TcpListener, TcpStream};
use std::path::Path;
use std::process::{Child, Command, Stdio};
use std::time::{Duration, Instant};
use wai_quantum::engine::schema::Registry;
const BELL: &str = "```qasm\nOPENQASM 3.0;\ninclude \"stdgates.inc\";\nqubit[2] q;\nbit[2] c;\nh q[0];\ncx q[0], q[1];\nc = measure q;\n```";
const NO_CNOT: &str = "```qasm\nOPENQASM 3.0;\ninclude \"stdgates.inc\";\nqubit[2] q;\nbit[2] c;\nh q[0];\nc = measure q;\n```";
fn bell_task() -> Value {
json!({
"schema": "wai.quantum.exercise/1",
"title": "A Bell pair",
"task": "Entangle two qubits so that their measured bits always agree.",
"starter": ["OPENQASM 3.0;", "include \"stdgates.inc\";", "qubit[2] q;", "bit[2] c;", "c = measure q;"],
"intent": { "distribution": { "00": 0.5, "11": 0.5 } }
})
}
fn free_port() -> u16 {
TcpListener::bind("127.0.0.1:0").unwrap().local_addr().unwrap().port()
}
/// A body sent with chunked transfer coding, as it was meant.
fn dechunk(body: &str) -> String {
let (mut out, mut rest) = (String::new(), body);
while let Some((size, after)) = rest.split_once("\r\n") {
let Ok(n) = usize::from_str_radix(size.trim(), 16) else { break };
if n == 0 || after.len() < n {
break;
}
out.push_str(&after[..n]);
rest = after[n..].strip_prefix("\r\n").unwrap_or(&after[n..]);
}
out
}
/// One HTTP/1.1 exchange, with extra header fields: the status, the headers
/// (lowercased) and the body.
fn http(port: u16, method: &str, path: &str, body: Option<&Value>, extra: &[(&str, &str)]) -> (u16, String, String) {
let mut s = TcpStream::connect(("127.0.0.1", port)).unwrap();
s.set_read_timeout(Some(Duration::from_secs(180))).unwrap();
let text = body.map(Value::to_string).unwrap_or_default();
let fields: String = extra.iter().map(|(k, v)| format!("{k}: {v}\r\n")).collect();
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\n{fields}Content-Length: {}\r\nConnection: close\r\n\r\n{text}", text.len()).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);
let (head, body) = out.split_once("\r\n\r\n").map(|(h, b)| (h.to_ascii_lowercase(), b.to_string())).unwrap_or_default();
let body = if head.contains("transfer-encoding: chunked") { dechunk(&body) } else { body };
(status, head, body)
}
fn json_of(port: u16, method: &str, path: &str, body: Option<&Value>, extra: &[(&str, &str)]) -> (u16, Value) {
let (code, _, text) = http(port, method, path, body, extra);
(code, serde_json::from_str(&text).unwrap_or_else(|e| panic!("{method} {path}: {e}: {text}")))
}
fn rpc(port: u16, method: &str, params: Value) -> Value {
let (code, v) = json_of(port, "POST", "/mcp", Some(&json!({ "jsonrpc": "2.0", "id": 1, "method": method, "params": params })), &[]);
assert_eq!(code, 200, "{method}: {v}");
v
}
/// The service, its writer running the script at `script` if any, once it answers.
fn start(dir: &Path, port: u16, script: Option<&Path>) -> Child {
start_with(dir, port, script, 120)
}
/// [`start`], a write (and the writer's start) held to `write_secs`.
fn start_with(dir: &Path, port: u16, script: Option<&Path>, write_secs: u64) -> Child {
start_env(dir, port, script, write_secs, &[])
}
/// [`start_with`], with more of the service's variables.
fn start_env(dir: &Path, port: u16, script: Option<&Path>, write_secs: u64, env: &[(&str, &str)]) -> Child {
let mut cmd = Command::new(env!("CARGO_BIN_EXE_wai-quantum-service"));
cmd.arg("serve")
.env("WAI_QSVC_ADDR", format!("127.0.0.1:{port}"))
.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")
.env("WAI_QSVC_WRITE_SECS", write_secs.to_string())
.env_remove("WAI_QSVC_MODEL")
.env_remove("WAI_QSVC_WRITER_SCRIPT")
.stdout(Stdio::null())
.stderr(Stdio::null());
if let Some(s) = script {
cmd.env("WAI_QSVC_WRITER_SCRIPT", s);
}
for (k, v) in env {
cmd.env(k, v);
}
let child = cmd.spawn().expect("the service starts");
let t = Instant::now();
while TcpStream::connect(("127.0.0.1", port)).is_err() || http(port, "GET", "/v1/health", None, &[]).0 != 200 {
assert!(t.elapsed() < Duration::from_secs(60), "the service did not answer");
std::thread::sleep(Duration::from_millis(50));
}
child
}
/// Waits until a job's status is `want`.
fn until(port: u16, id: &str, want: &str) -> Value {
let t = Instant::now();
loop {
let (_, j) = json_of(port, "GET", &format!("/v1/jobs/{id}"), None, &[]);
if j["status"] == want {
return j;
}
assert!(t.elapsed() < Duration::from_secs(120), "{id} never {want}: {j}");
std::thread::sleep(Duration::from_millis(25));
}
}
/// The submissions left in this client's window, from a write's answer.
fn left(head: &str) -> u64 {
head.lines().find_map(|l| l.strip_prefix("ratelimit: ")).and_then(|v| v.split("r=").nth(1)).and_then(|v| v.split(';').next()).and_then(|v| v.trim().parse().ok()).unwrap()
}
/// A document's canonical text (RFC 8785), which a receipt binds: what "the
/// same" means for two documents, whichever reader carried them.
fn canonical(v: &Value) -> String {
String::from_utf8(wai_quantum::engine::receipt::canonical(v)).unwrap()
}
/// The code a refusal ends with.
fn code_of(v: &Value) -> &str {
v["code"].as_str().unwrap_or_else(|| panic!("no code: {v}"))
}
#[test]
fn a_write_passes_after_one_repair_its_attempts_stream_as_they_land_and_its_receipt_verifies() {
let dir = std::env::temp_dir().join(format!("wai-qsvc-write-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
let fifo = dir.join("script.json");
assert!(Command::new("mkfifo").arg(&fifo).status().unwrap().success());
let port = free_port();
let mut svc = start(&dir, port, Some(&fifo));
// The service says it writes, its model still loading.
let (_, d) = json_of(port, "GET", "/v1", None, &[]);
let w = &d["write"];
assert_eq!((w["available"].as_bool(), w["writer"].as_str(), w["model"].is_null()), (Some(true), Some("loading its model"), true), "{w}");
assert_eq!((w["limits"]["writes_at_once"].as_u64(), w["limits"]["writes_per_client"].as_u64(), w["limits"]["write_seconds"].as_u64()), (Some(1), Some(1), Some(120)), "{w}");
assert_eq!((w["limits"]["task_bytes"].as_u64(), w["limits"]["prompt_bytes"].as_u64(), w["limits"]["cases"].as_u64(), w["limits"]["intents"].as_u64()), (Some(65536), Some(8192), Some(16), Some(8)), "{w}");
assert!(d["endpoints"].as_array().unwrap().iter().any(|e| e == "POST /v1/write"), "{d}");
// The queue's limit is each kind's.
assert_eq!(d["limits"]["queue"]["per_kind"].as_u64(), Some(256), "{d}");
let (_, api) = json_of(port, "GET", "/v1/openapi.json", None, &[]);
assert!(api["paths"]["/v1/write"]["post"]["responses"]["422"]["description"].as_str().unwrap().contains("J031"), "{}", api["paths"]["/v1/write"]);
// A malformed task, and options out of range, each refused with its code.
for (req, status, code) in [
(json!({ "task": { "task": "anything" } }), 400, "J081"),
(json!({ "task": { "cases": [{ "name": "n = 1", "inputs": {} }] } }), 400, "J081"),
(json!({ "task": bell_task(), "rounds": 9 }), 400, "J082"),
(json!({ "task": bell_task(), "max_new": 0 }), 400, "J082"),
(json!({ "task": bell_task(), "qasm": "OPENQASM 3.0;" }), 400, "J082"),
] {
let (c, v) = json_of(port, "POST", "/v1/write", Some(&req), &[]);
assert_eq!((c, code_of(&v)), (status, code), "{req}: {v}");
}
// A write, queued while the writer loads; sent again under its key, the
// same job, counting as no submission.
let req = json!({ "task": bell_task() });
let (c, head, body) = http(port, "POST", "/v1/write", Some(&req), &[("Idempotency-Key", "write-1")]);
let a: Value = serde_json::from_str(&body).unwrap();
assert_eq!((c, a["status"].as_str(), a["method"].as_str()), (202, Some("queued"), Some("write")), "{a}");
let id = a["id"].as_str().unwrap().to_string();
let (c, head2, body) = http(port, "POST", "/v1/write", Some(&req), &[("Idempotency-Key", "write-1")]);
assert_eq!((c, serde_json::from_str::<Value>(&body).unwrap()["id"].as_str()), (202, Some(id.as_str())), "{body}");
assert_eq!(left(&head2), left(&head), "a write sent again under its key counts as nothing");
// The same key with another request is refused.
let (c, v) = json_of(port, "POST", "/v1/write", Some(&json!({ "task": bell_task(), "rounds": 1 })), &[("Idempotency-Key", "write-1")]);
assert_eq!((c, code_of(&v)), (422, "J034"), "{v}");
// One write a client: a second, queued or running beside the first, is refused.
let (c, v) = json_of(port, "POST", "/v1/write", Some(&req), &[]);
assert_eq!((c, code_of(&v)), (429, "J030"), "{v}");
assert!(v["error"].as_str().unwrap().contains("one at a time"), "{v}");
// Another client's write waits behind it.
let (c, b) = json_of(port, "POST", "/v1/write", Some(&req), &[("X-Forwarded-For", "203.0.113.9")]);
assert_eq!((c, b["status"].as_str()), (202, Some("queued")), "{b}");
let other = b["id"].as_str().unwrap().to_string();
// The write's events, followed from while it is queued to its end.
let events = {
let path = format!("/v1/jobs/{id}/events");
std::thread::spawn(move || http(port, "GET", &path, None, &[]).2)
};
std::thread::sleep(Duration::from_millis(300));
// The writer's model loads: the script reaches it.
let script = json!([NO_CNOT, BELL]);
std::fs::write(&fifo, script.to_string()).unwrap();
let done = until(port, &id, "done");
let t = &done["result"];
// The transcript: the task, the model, the prompt, the decoding, each attempt and the pass.
assert_eq!((t["schema"].as_str(), t["passed"].as_bool(), t["passed_round"].as_u64(), t["level"].as_str()), (Some("wai.quantum.write/1"), Some(true), Some(1), Some("proof")), "{t}");
assert_eq!(canonical(&t["task"]), canonical(&bell_task()));
assert_eq!(t["task_digest"], blake3::hash(&wai_quantum::engine::receipt::canonical(&bell_task())).to_hex().to_string());
assert_eq!(t["model"], json!({ "name": "scripted", "licence": "none", "digest": wai_quantum_writer::digest(&script) }));
assert_eq!(t["decoding"], json!({ "method": "greedy", "max_new": 3000, "think_max": 2000, "rounds": 4 }));
// Each check's run was planned in one job's memory (WAI_QSVC_JOB_GB), and the transcript says so.
assert_eq!(t["check_memory_bytes"].as_u64(), Some(2 << 30), "{t}");
assert_eq!((t["prompt"]["version"].as_str(), t["engine"].as_str()), (Some(wai_quantum_writer::PROMPT_VERSION), Some(wai_quantum::engine::schema::ENGINE_VERSION)), "{t}");
let attempts = t["attempts"].as_array().unwrap();
assert_eq!(attempts.len(), 2, "{t}");
assert_eq!((attempts[0]["passed"].as_bool(), attempts[0]["check"]["what_went_wrong"][0]["kind"].as_str()), (Some(false), Some("distribution")), "{t}");
assert!(attempts[1]["passed"] == true && t["program"].as_str().unwrap().contains("cx q[0], q[1];"), "{t}");
assert_eq!(t["program_digest"], blake3::hash(t["program"].as_str().unwrap().as_bytes()).to_hex().to_string());
assert!(t["energy"]["unmetered"].is_string(), "the energy is inside the signed body: {t}");
// The timing is beside the transcript, outside the signed body.
let timing = done["timing"].as_array().unwrap();
assert!(timing.len() == 2 && timing.iter().all(|x| x["reply_seconds"].as_f64().is_some() && x["check_seconds"].as_f64().is_some()), "{done}");
assert!(t.get("timing").is_none() && done.get("progress").is_none(), "{done}");
// The job follows its schema.
let errs = wai_quantum_schemas().check("service-tools.v1.json#/$defs/write", &done);
assert!(errs.is_empty(), "{}\n{done}", errs.join("; "));
// Its receipt verifies against its request, and a changed transcript does not.
let (_, v) = json_of(port, "POST", "/v1/verify", Some(&json!({ "request": done["request"], "result": done["result"] })), &[]);
assert_eq!(v["valid"], true, "{v}");
let mut forged = done["result"].clone();
forged["passed_round"] = json!(0);
let (_, v) = json_of(port, "POST", "/v1/verify", Some(&json!({ "request": done["request"], "result": forged })), &[]);
assert_eq!(v["valid"], false, "{v}");
// Its events: its status, then each attempt as it landed, then the job.
let stream = events.join().unwrap();
let mut seen: Vec<(String, Value)> = Vec::new();
let mut name = String::new();
for line in stream.lines() {
if let Some(e) = line.strip_prefix("event:") {
name = e.trim().to_string();
} else if let Some(d) = line.strip_prefix("data:") {
seen.push((name.clone(), serde_json::from_str(d.trim()).unwrap_or_else(|e| panic!("{e}: {d}"))));
}
}
// The stream began while the write was queued; a scripted write runs in
// milliseconds, so the stream may find it running or already past it.
let statuses: Vec<&str> = seen.iter().filter(|(k, _)| k == "status").filter_map(|(_, v)| v["status"].as_str()).collect();
assert!(statuses == ["queued", "running"] || statuses == ["queued"], "{stream}");
let rest: Vec<&(String, Value)> = seen.iter().filter(|(k, _)| k != "status").collect();
assert_eq!(rest.iter().map(|(k, _)| k.as_str()).collect::<Vec<_>>(), ["attempt", "attempt", "job"], "{stream}");
for (n, (_, a)) in rest[..2].iter().enumerate() {
assert_eq!((a["id"].as_str(), a["attempt"]["round"].as_u64(), a["attempt"]["passed"].as_bool()), (Some(id.as_str()), Some(n as u64), Some(n == 1)), "{a}");
assert_eq!(canonical(&a["attempt"]), canonical(&attempts[n]));
assert!(a["timing"]["reply_seconds"].is_number(), "{a}");
}
assert_eq!(rest[2].1["status"], "done");
// The other client's write ran after it, one at a time, and the same
// task gave the same transcript.
let b = until(port, &other, "done");
assert!(b["started_ms"].as_u64().unwrap() >= done["finished_ms"].as_u64().unwrap(), "two writes ran at once: {done}\n{b}");
let strip = |r: &Value| {
let mut r = r.clone();
for k in ["energy", "receipt"] {
r.as_object_mut().unwrap().remove(k);
}
r
};
assert_eq!(canonical(&strip(&b["result"])), canonical(&strip(&done["result"])));
// The service names its model now, loaded.
let (_, d) = json_of(port, "GET", "/v1", None, &[]);
assert_eq!((d["write"]["writer"].as_str(), &d["write"]["model"]), (Some("ready"), &t["model"]), "{d}");
// As an MCP task: made at once, followed to its end, its result the job,
// with the verdict of the program it wrote, held to the tool's schema.
let tools = rpc(port, "tools/list", json!({}));
let tool = tools["result"]["tools"].as_array().unwrap().iter().find(|t| t["name"] == "write_circuit").cloned().unwrap();
assert_eq!(tool["execution"]["taskSupport"], "optional", "{tool}");
let made = rpc(port, "tools/call", json!({ "name": "write_circuit", "arguments": { "task": bell_task(), "rounds": 1 }, "task": { "ttl": 60000 } }));
let task = made["result"]["task"]["taskId"].as_str().unwrap_or_else(|| panic!("{made}")).to_string();
let t0 = Instant::now();
while rpc(port, "tasks/get", json!({ "taskId": task }))["result"]["status"] == "working" {
assert!(t0.elapsed() < Duration::from_secs(120), "the task never ended");
std::thread::sleep(Duration::from_millis(50));
}
let r = rpc(port, "tasks/result", json!({ "taskId": task }));
let sc = &r["result"]["structuredContent"];
assert_eq!((r["result"]["isError"].as_bool(), sc["status"].as_str(), sc["result"]["passed_round"].as_u64()), (Some(false), Some("done"), Some(1)), "{r}");
assert_eq!((sc["verdict"]["level"].as_str(), sc["verdict"]["holds"].as_bool()), (Some("proof"), Some(true)), "{r}");
assert!(sc["verdict"]["statement"].as_str().unwrap().starts_with("the program written at round 1"), "{r}");
let get = tools["result"]["tools"].as_array().unwrap().iter().find(|t| t["name"] == "get_quantum_job").cloned().unwrap();
let errs = Registry::empty().check_value(&get["outputSchema"], sc);
assert!(errs.is_empty(), "{}\n{sc}", errs.join("; "));
assert!(r["result"]["content"][0]["text"].as_str().unwrap().contains("passed after 1 repair(s)"), "{r}");
svc.kill().unwrap();
svc.wait().unwrap();
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn a_service_with_no_model_refuses_a_write_and_says_so() {
let dir = std::env::temp_dir().join(format!("wai-qsvc-write-none-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
let port = free_port();
let mut svc = start(&dir, port, None);
let (_, d) = json_of(port, "GET", "/v1", None, &[]);
assert_eq!(d["write"]["available"], false, "{d}");
assert!(d["write"]["why"].as_str().unwrap().contains("no model"), "{d}");
let (c, v) = json_of(port, "POST", "/v1/write", Some(&json!({ "task": bell_task() })), &[]);
assert_eq!((c, code_of(&v)), (503, "J080"), "{v}");
let r = rpc(port, "tools/call", json!({ "name": "write_circuit", "arguments": { "task": bell_task() } }));
assert_eq!(r["result"]["isError"], true, "{r}");
assert!(r["result"]["structuredContent"]["error"].as_str().unwrap().ends_with("[J080]"), "{r}");
let t = rpc(port, "tools/call", json!({ "name": "write_circuit", "arguments": { "task": bell_task() }, "task": {} }));
assert_eq!((t["error"]["code"].as_i64(), t["error"]["data"]["code"].as_str()), (Some(-32602), Some("J080")), "{t}");
svc.kill().unwrap();
svc.wait().unwrap();
let _ = std::fs::remove_dir_all(&dir);
}
/// A write cancelled while the writer starts is not sent to it once it has
/// started: the cancel stands, and the writer never runs it. The script is a
/// FIFO nothing writes at first, so the writer's start at boot runs out of
/// time and fails; the write, taken next, then waits while the writer starts
/// again, and is cancelled there.
#[test]
fn a_write_cancelled_while_the_writer_starts_is_never_written() {
let dir = std::env::temp_dir().join(format!("wai-qsvc-write-cancel-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
let fifo = dir.join("script.json");
assert!(Command::new("mkfifo").arg(&fifo).status().unwrap().success());
let port = free_port();
let mut svc = start_with(&dir, port, Some(&fifo), 8);
let (c, a) = json_of(port, "POST", "/v1/write", Some(&json!({ "task": bell_task() })), &[]);
assert_eq!(c, 202, "{a}");
let id = a["id"].as_str().unwrap().to_string();
// Taken once the first start has failed, while the writer starts again.
until(port, &id, "running");
let (_, d) = json_of(port, "GET", "/v1", None, &[]);
assert_eq!(d["write"]["writer"], "loading its model", "{d}");
let (c, v) = json_of(port, "DELETE", &format!("/v1/jobs/{id}"), None, &[]);
assert!(c == 200 && v["status"] == "cancelled", "{c} {v}");
// The writer loads.
std::fs::write(&fifo, json!([BELL]).to_string()).unwrap();
let t = Instant::now();
while json_of(port, "GET", "/v1", None, &[]).1["write"]["writer"] != "ready" {
assert!(t.elapsed() < Duration::from_secs(60), "the writer never loaded");
std::thread::sleep(Duration::from_millis(25));
}
// A write after it runs, so whatever the writer was sent before it is done.
let (c, b) = json_of(port, "POST", "/v1/write", Some(&json!({ "task": bell_task() })), &[]);
assert_eq!(c, 202, "the cancelled write still counts against its client: {b}");
let next = until(port, b["id"].as_str().unwrap(), "done");
assert_eq!(next["result"]["passed"], true, "{next}");
let (_, w) = json_of(port, "GET", &format!("/v1/jobs/{id}"), None, &[]);
assert_eq!(w["status"], "cancelled", "{w}");
assert!(w.get("peak_rss_bytes").is_none() && w.get("result").is_none(), "the writer ran the cancelled write: {w}");
svc.kill().unwrap();
svc.wait().unwrap();
let _ = std::fs::remove_dir_all(&dir);
}
/// A write is refused for what costs nothing to read before its client's
/// submission is counted, and its programs are read only after, each once and
/// all within one budget, as work that holds up no other request: four writes at
/// once, each with eight programs that run for millions of statements, are each
/// refused in moments, the service answers meanwhile, and the refusals count
/// against their client's window.
#[test]
fn a_write_s_programs_are_read_last_within_one_budget_and_hold_up_nothing() {
let dir = std::env::temp_dir().join(format!("wai-qsvc-write-budget-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
let script = dir.join("script.json");
std::fs::write(&script, json!([BELL]).to_string()).unwrap();
let port = free_port();
let mut svc = start_env(&dir, port, Some(&script), 120, &[("WAI_QSVC_CLIENT_BURST", "3")]);
let looping = |n: u64| format!("OPENQASM 3.0;\nqubit[1] q;\nint a = 0;\nfor int i in [0:{n}] {{ a += 1; }}\n");
let body = json!({ "task": { "intent": (0..8).map(|k| json!({ "equivalent": looping(9_999_990 - k) })).collect::<Vec<_>>() } });
let sent: Vec<_> = (0..4)
.map(|_| {
let b = body.clone();
std::thread::spawn(move || {
let t = Instant::now();
let (c, v) = json_of(port, "POST", "/v1/write", Some(&b), &[]);
(c, v, t.elapsed())
})
})
.collect();
std::thread::sleep(Duration::from_millis(200));
let t = Instant::now();
assert_eq!(http(port, "GET", "/v1/health", None, &[]).0, 200);
let health = t.elapsed();
let answers: Vec<(u16, Value, Duration)> = sent.into_iter().map(|h| h.join().unwrap()).collect();
assert!(health < Duration::from_secs(2), "the service took {health:?} to answer while four writes were read");
for (c, v, took) in &answers {
assert!(*took < Duration::from_secs(10), "a write took {took:?} to be refused: {c} {v}");
}
// Three counted and refused for their programs, past their budget; the fourth past the client's window.
let mut codes: Vec<(u16, String)> = answers.iter().map(|(c, v, _)| (*c, code_of(v).to_string())).collect();
codes.sort();
assert_eq!(codes, [(400, "J081".to_string()), (400, "J081".to_string()), (400, "J081".to_string()), (429, "J030".to_string())], "{answers:?}");
assert!(answers.iter().filter(|(c, _, _)| *c == 400).all(|(_, v, _)| v["error"].as_str().unwrap().contains("budget")), "{answers:?}");
svc.kill().unwrap();
svc.wait().unwrap();
let _ = std::fs::remove_dir_all(&dir);
}
/// The service's schemas, as its tools' answers are held to them.
fn wai_quantum_schemas() -> Registry {
let mut r = Registry::engine();
r.add("service-tools.v1.json", include_str!("../schemas/service-tools.v1.json")).unwrap();
r
}