open-standards/wai/quantum-studio/test/views.test.mjs
Transaction Science 9258a0739f wai-quantum contract over views, the answer layer, correctness records and the harness: their schemas, the view's tool, the basis in the views, and the stored seal for 0.7.0
The apps branch's documents join the version contract. The answer layer's
documents (wai.quantum.answer/1: an answer read, carried out, a seal's
request and result, a stored answer checked), described as a draft whose form
may change before the extension settles; a correctness record and its corpus
(wai.quantum.telemetry/1); the views page's manifest
(wai.quantum.views-bundle/1); and the command line's harness report, corpus
and token counts (wai.quantum.eval-report/1, eval-answers/1, token-cost/1).
The engine's test holds every answer of the layer's conformance set (read,
carried out, half arrived, and stored and verified), records, the corpus and
the committed manifest to them; the eval tests hold a scripted run's report,
a model server's, the corpus and the committed token counts; check_schemas.py
holds the committed documents too. The tool and diagnostic schemas take the
answer layer's outcomes, dropped and defaulted.

The result view's own tool, poll_quantum_job, listed only to a host that
draws views, answers to the job's schema, and the strict test holds it.

The basis rename reaches the views: a compile's words and its view name the
gate set by its basis (both read the certificate's old target), and a test in
each holds it.

The service's job summary now lives in engine::views (the apps branch moved
it there); it carries the durable branch's set summary and the processor
packages' energy wording with it.

The answer set's stored seal was made at 0.6.16; the values it seals name
their engine, so it is written again at 0.7.0 (WAI_ANSWER_BUNDLE_WRITE=1).
The views crate's lock moves to wai-quantum 0.7.0.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-28 08:42:54 -04:00

326 lines
23 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// The engine's views inside an agent host, driven by a scripted host in a
// headless browser:
//
// WQ_CLI=PATH/wai-quantum [WQ_SERVICE=PATH/wai-quantum-service] node test/views.test.mjs
//
// The host speaks the extension for interactive views (io.modelcontextprotocol/ui,
// 2026-01-26) as a host does: it says so in initialize, lists the tools and finds each
// one's ui:// resource, reads the resource, and draws it in a sandboxed frame under a
// content-security policy, answering the view's ui/initialize, giving it the tool's
// input and result, carrying its tool calls to the server (only to tools whose
// visibility lets a view call them), and keeping what it tells the model.
//
// For the command line's server (stdio), under the extension's default policy (no
// WebAssembly) and under that policy with 'wasm-unsafe-eval':
// - every tool with a view names it, and the six views are listed and read, each
// one self-contained page within the limit (the sizes are printed);
// - a run: its verdict, its counts, its circuit, and its receipt checked in the view,
// by the engine as WebAssembly where the policy lets it and as JavaScript where it
// does not; the model told the check; a count changed after sealing fails the check;
// - a refusal with its code; a receipt checked beside the server's own answer, for a
// sealed job and a changed one; a compile's passes and its certificate checked; a
// lint's refusal; a repair's changes; a critique's smells marked on the circuit; an
// estimate's frontier and its seal; a device target's map; a run on a target;
// - a view makes no request of its own, and draws without a script error.
// With WQ_SERVICE, the job service started here over HTTP: the session id that says
// the host draws views, the view's own tool kept from the model, a run drawn and its
// receipt checked as the service's, and a job still running followed by the view
// until it is done.
// Exit 0 iff every check passes.
import { spawn } from 'node:child_process';
import { createServer } from 'node:http';
import { mkdirSync, mkdtempSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { dirname, join, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
const { chromium } = (await import(process.env.WAI_E2E_DRIVER ?? 'playwright')).default;
const SHOTS = resolve(dirname(fileURLToPath(import.meta.url)), 'screenshots');
const CLI = process.env.WQ_CLI;
const SERVICE = process.env.WQ_SERVICE;
if (!CLI) { console.error('give WQ_CLI: the wai-quantum binary'); process.exit(2); }
const LIMIT = 1 << 20;
const UI = { extensions: { 'io.modelcontextprotocol/ui': { mimeTypes: ['text/html;profile=mcp-app'] } } };
const STRICT = "default-src 'none'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; media-src 'self' data:; connect-src 'none';";
const WASM = STRICT.replace("script-src 'self' 'unsafe-inline';", "script-src 'self' 'unsafe-inline' 'wasm-unsafe-eval';");
const LONG = 60_000;
let fails = 0;
const check = (ok, what) => { console.log(`${ok ? 'PASS' : 'FAIL'} ${what}`); if (!ok) fails += 1; };
// ---- the servers ----
function stdio(bin) {
const p = spawn(bin, ['mcp'], { stdio: ['pipe', 'pipe', 'inherit'] });
let buf = '';
let next = 1;
const waiting = new Map();
p.stdout.setEncoding('utf8');
p.stdout.on('data', (d) => {
buf += d;
for (let i = buf.indexOf('\n'); i >= 0; i = buf.indexOf('\n')) {
const line = buf.slice(0, i);
buf = buf.slice(i + 1);
if (!line.trim()) continue;
const m = JSON.parse(line);
waiting.get(m.id)?.(m);
waiting.delete(m.id);
}
});
const rpc = (method, params = {}) => new Promise((ok) => { const id = next++; waiting.set(id, ok); p.stdin.write(JSON.stringify({ jsonrpc: '2.0', id, method, params }) + '\n'); });
return { name: 'the command line’s server', rpc, close: () => p.kill() };
}
function http(base) {
let session = null;
let next = 1;
const rpc = async (method, params = {}) => {
const headers = { 'content-type': 'application/json', accept: 'application/json, text/event-stream' };
if (session) headers['mcp-session-id'] = session;
const r = await fetch(`${base}/mcp`, { method: 'POST', headers, body: JSON.stringify({ jsonrpc: '2.0', id: next++, method, params }) });
session = r.headers.get('mcp-session-id') ?? session;
return r.json();
};
return { name: 'the job service’s server', rpc, session: () => session };
}
// ---- the host page ----
// The host's side of the protocol, in the page: each view in a sandboxed frame of its
// own, under `csp`, given its tool's input and result; what it says is kept in `log`.
const HOST = `<!doctype html><html><head><meta charset="utf-8"><title>scripted host</title></head><body><div id="frames"></div><script>
window.log = {};
window.mount = (key, html, csp, input, result, appTools, theme) => {
const log = window.log[key] = { sizes: [], context: [], calls: [], refused: [] };
const f = document.createElement('iframe');
f.id = 'f-' + key;
f.setAttribute('sandbox', 'allow-scripts');
f.style.cssText = 'width: 720px; height: 900px; border: 1px solid #888';
f.srcdoc = html.replace('<head>', '<head><meta http-equiv="Content-Security-Policy" content="' + csp + '">');
const send = (m) => f.contentWindow.postMessage(m, '*');
window.addEventListener('message', async (ev) => {
if (ev.source !== f.contentWindow) return;
const m = ev.data;
if (!m || m.jsonrpc !== '2.0') return;
switch (m.method) {
case 'ui/initialize':
log.initialize = m.params;
send({ jsonrpc: '2.0', id: m.id, result: { protocolVersion: '2026-01-26', hostInfo: { name: 'scripted-host', version: '1' }, hostCapabilities: { serverTools: {}, logging: {} }, hostContext: { theme, displayMode: 'inline', availableDisplayModes: ['inline'], containerDimensions: { width: 720, maxHeight: 6000 }, platform: 'web', locale: 'en-GB' } } });
break;
case 'ui/notifications/initialized':
log.initialized = true;
send({ jsonrpc: '2.0', method: 'ui/notifications/tool-input', params: { arguments: input } });
send({ jsonrpc: '2.0', method: 'ui/notifications/tool-result', params: result });
break;
case 'tools/call':
log.calls.push(m.params.name);
if (!appTools.includes(m.params.name)) { log.refused.push(m.params.name); send({ jsonrpc: '2.0', id: m.id, error: { code: -32000, message: 'a view may not call this tool' } }); break; }
send({ jsonrpc: '2.0', id: m.id, result: await window.callServer(m.params.name, m.params.arguments) });
break;
case 'ui/update-model-context':
log.context.push(m.params);
send({ jsonrpc: '2.0', id: m.id, result: {} });
break;
case 'ui/notifications/size-changed':
log.sizes.push(m.params);
break;
default:
if (m.id != null) send({ jsonrpc: '2.0', id: m.id, error: { code: -32601, message: 'no ' + m.method } });
}
});
document.getElementById('frames').append(f);
};
</script></body></html>`;
const host = createServer((req, res) => { res.writeHead(200, { 'content-type': 'text/html; charset=utf-8' }); res.end(HOST); });
await new Promise((r) => host.listen(0, '127.0.0.1', r));
const ORIGIN = `http://127.0.0.1:${host.address().port}`;
const browser = await chromium.launch({ channel: process.env.WAI_E2E_CHANNEL ?? 'chrome', headless: true });
// One page for a server, its tool calls carried to it.
async function hostFor(server) {
const ctx = await browser.newContext({ viewport: { width: 1000, height: 1000 }, colorScheme: 'dark' });
const page = await ctx.newPage();
const seen = { errors: [], requests: [] };
page.on('console', (m) => { if (m.type() === 'error') seen.errors.push(m.text()); });
page.on('pageerror', (e) => seen.errors.push(String(e)));
page.on('request', (r) => seen.requests.push(r.url()));
await page.exposeFunction('callServer', async (name, args) => (await server.rpc('tools/call', { name, arguments: args })).result);
await page.goto(ORIGIN + '/', { waitUntil: 'domcontentloaded' });
return { ctx, page, seen };
}
let keys = 0;
// Draw `view` for a tool's `result` (and its `input`) under `csp`; the frame, once the
// view says it is drawn.
async function draw(page, pages, view, input, result, { csp = STRICT, appTools = [], state = 'shown', theme = 'dark' } = {}) {
const key = `v${keys++}`;
await page.evaluate(([k, html, c, i, r, a, t]) => window.mount(k, html, c, i, r, a, t), [key, pages[view], csp, input, result, appTools, theme]);
const frame = await (await page.waitForSelector(`#f-${key}`)).contentFrame();
const ok = await frame.waitForSelector(`body[data-state="${state}"]`, { timeout: LONG }).then(() => true, () => false);
return { key, frame, ok, log: () => page.evaluate((k) => window.log[k], key) };
}
const receiptOf = (frame) => frame.waitForFunction(() => { const r = document.querySelector('#wq-receipt'); return r && r.dataset.receipt !== 'checking' && r.dataset.receipt; }, null, { timeout: LONG }).then((h) => h.jsonValue(), () => null);
const text = (frame, sel) => frame.$eval(sel, (e) => e.textContent).catch(() => '');
// What the server lists for a host that draws views: each tool's view, the views, and their pages.
async function discover(server) {
const init = await server.rpc('initialize', { protocolVersion: '2025-11-25', capabilities: UI, clientInfo: { name: 'scripted-host', version: '1' } });
check(/never write HTML/.test(init.result?.instructions ?? ''), `${server.name}: the model is told not to write views itself`);
const tools = (await server.rpc('tools/list')).result.tools;
const named = Object.fromEntries(tools.filter((t) => t._meta?.ui?.resourceUri).map((t) => [t.name, t._meta.ui]));
const resources = (await server.rpc('resources/list')).result.resources.filter((r) => r.uri.startsWith('ui://'));
check(resources.length === 6 && resources.every((r) => r.mimeType === 'text/html;profile=mcp-app'), `${server.name}: the six views are listed (${resources.map((r) => r.uri.slice(17)).join(', ')})`);
const pages = {};
for (const r of resources) {
const c = (await server.rpc('resources/read', { uri: r.uri })).result.contents[0];
const bytes = Buffer.byteLength(c.text, 'utf8');
const name = r.uri.slice('ui://wai-quantum/'.length);
pages[name] = c.text;
check(c.mimeType === 'text/html;profile=mcp-app' && bytes <= LIMIT && c.text.includes(`content="${name}"`) && !/<script src|<link /.test(c.text), `${server.name}: the ${name} view is one self-contained page of ${bytes} bytes, within ${LIMIT}`);
}
for (const uri of new Set(Object.values(named).map((u) => u.resourceUri))) check(resources.some((r) => r.uri === uri), `${server.name}: ${uri}, which a tool names, is listed`);
const appTools = tools.filter((t) => (t._meta?.ui?.visibility ?? ['model', 'app']).includes('app')).map((t) => t.name);
return { tools, named, pages, appTools };
}
const call = async (server, name, args) => (await server.rpc('tools/call', { name, arguments: args })).result;
const BELL = 'OPENQASM 3.0;\nqubit[2] q;\nbit[2] c;\nh q[0];\ncx q[0], q[1];\nc = measure q;\n';
const forged = (res, f) => { const r = structuredClone(res); f(r.structuredContent); return r; };
// ---- the command line's server ----
const cli = stdio(CLI);
{
const { named, pages, appTools } = await discover(cli);
for (const [tool, view] of Object.entries({ run_quantum_circuit: 'result', run_on_target: 'result', draw_circuit: 'circuit', lint_qasm: 'circuit', repair_qasm: 'circuit', compile_qasm: 'circuit', critique_qasm: 'critique', verify_quantum_result: 'receipt', estimate_resources: 'estimate', show_target: 'target' })) {
check(named[tool]?.resourceUri === `ui://wai-quantum/${view}`, `the command line: ${tool} names the ${view} view`);
}
const { ctx, page, seen } = await hostFor(cli);
const runArgs = { qasm: BELL, shots: 1000, intent: { distribution: { '00': 0.5, '11': 0.5 } } };
const run = await call(cli, 'run_quantum_circuit', runArgs);
check(!!run._meta?.['science.transaction.wai/quantum-view']?.layout && run.content.length === 2, 'a run carries what its view draws in _meta, and its answer in words beside its JSON');
for (const [csp, form] of [[STRICT, 'JavaScript'], [WASM, 'WebAssembly']]) {
const under = form === 'JavaScript' ? 'under the default policy' : 'with WebAssembly allowed';
const v = await draw(page, pages, 'result', runArgs, run, { csp });
check(v.ok, `the result view draws ${under}`);
check((await receiptOf(v.frame)) === 'valid', `the run's receipt is checked in the view ${under}`);
check((await v.frame.$eval('body', (b) => b.dataset.engine)) === form, `by the engine as ${form} (${await text(v.frame, '#wq-checked-by')})`);
check((await v.frame.$eval('#wq-verdict', (b) => b.dataset.level)) === 'proof', `its verdict, as the receipt seals it: ${await v.frame.$eval('#wq-verdict', (b) => b.title)}`);
const bars = await v.frame.$$eval('.qs-bar', (bs) => bs.map((b) => [b.dataset.outcome, Number(b.dataset.count)]));
check(JSON.stringify(Object.fromEntries(bars)) === JSON.stringify(Object.fromEntries(Object.entries(run.structuredContent.result.counts).sort((a, b) => b[1] - a[1]))), `its counts, as the result has them (${bars.map((b) => b.join(': ')).join(', ')})`);
check((await v.frame.$$('#wq-circuit .qc-item')).length === 4, 'its circuit, drawn from the engine’s layout');
const log = await v.log();
check(log.initialize?.protocolVersion === '2026-01-26' && log.initialized, 'the view and the host shook hands at 2026-01-26');
check(log.sizes.some((s) => s.height > 100), `the view told the host its size (${JSON.stringify(log.sizes.at(-1))})`);
await v.frame.waitForFunction(() => true);
await page.waitForFunction((k) => window.log[k].context.length > 0, v.key, { timeout: LONG }).catch(() => {});
const told = (await v.log()).context.at(-1)?.content?.[0]?.text ?? '';
check(/checked the receipt .* it verifies, signed by did:key:z/.test(told), `the model is told the check: ${told}`);
if (form === 'JavaScript') {
mkdirSync(SHOTS, { recursive: true });
await (await page.$(`#f-${v.key}`)).screenshot({ path: join(SHOTS, 'view-result.png') });
}
}
const changed = await draw(page, pages, 'result', runArgs, forged(run, (sc) => { sc.result.counts['00'] += 1; }));
check((await receiptOf(changed.frame)) === 'invalid', 'a count changed after the seal: the view says the receipt does not seal it');
const refused = await call(cli, 'run_quantum_circuit', { qasm: 'OPENQASM 3.0;\nqubit[1] q;\nfoo q[0];\n' });
const r = await draw(page, pages, 'result', {}, refused, { state: 'refused' });
check(r.ok && /Q103/.test(await text(r.frame, '#wq-code')) && (await r.frame.$eval('#wq-verdict', (b) => b.dataset.level)) === 'refused', `a refusal, with its code: ${await text(r.frame, '#wq-code')}`);
const vArgs = { job: run.structuredContent };
const ver = await draw(page, pages, 'receipt', vArgs, await call(cli, 'verify_quantum_result', vArgs));
await ver.frame.waitForFunction(() => document.querySelector('#wq-agree')?.dataset.agree, null, { timeout: LONG }).catch(() => {});
check((await receiptOf(ver.frame)) === 'valid' && (await ver.frame.$eval('#wq-agree', (b) => b.dataset.agree).catch(() => null)) === 'yes', 'a receipt checked in the view agrees with the server’s check');
const badJob = structuredClone(run.structuredContent);
badJob.result.counts['11'] -= 1;
const bad = await draw(page, pages, 'receipt', { job: badJob }, await call(cli, 'verify_quantum_result', { job: badJob }));
await bad.frame.waitForFunction(() => document.querySelector('#wq-agree')?.dataset.agree, null, { timeout: LONG }).catch(() => {});
check((await receiptOf(bad.frame)) === 'invalid' && /does not verify/.test(await text(bad.frame, '#wq-server-valid')) && (await bad.frame.$eval('#wq-agree', (b) => b.dataset.agree)) === 'yes', 'a changed job: the server and the view both say it does not verify');
const cArgs = { qasm: 'OPENQASM 3.0;\nqubit[2] q;\nh q[0];\nrz(0.3) q[0];\ncx q[0], q[1];\n', epsilon: 1e-3 };
const compiled = await call(cli, 'compile_qasm', cArgs);
const c = await draw(page, pages, 'circuit', cArgs, compiled);
check((await receiptOf(c.frame)) === 'valid' && (await c.frame.$$('#wq-passes tbody tr')).length === compiled.structuredContent.result.certificate.passes.length && (await text(c.frame, '#wq-compile')).startsWith(`To ${compiled.structuredContent.result.certificate.basis} at level`), `a compile: each pass with its check, its basis named, and its certificate checked in the view (${await text(c.frame, '#wq-compile')})`);
check((await c.frame.$$('#wq-circuit .qc-item')).length > 10, 'the compiled circuit drawn');
const lintArgs = { qasm: 'OPENQASM 3.0;\nqubit[1] q;\nfoo q[0];\n' };
const l = await draw(page, pages, 'circuit', lintArgs, await call(cli, 'lint_qasm', lintArgs), { state: 'refused' });
check(l.ok && /Q103 on line 3/.test(await text(l.frame, '#wq-code')), 'a lint’s refusal, with its code and line');
const repArgs = { qasm: 'OPENQASM 3.0;\nqubit[2] q\nbit[2] c;\nh q[0];\nmeasure q;\n' };
const rep = await draw(page, pages, 'circuit', repArgs, await call(cli, 'repair_qasm', repArgs));
check(JSON.stringify(await rep.frame.$$eval('#wq-repairs li', (ls) => ls.map((x) => x.dataset.code))) === '["Q004","Q320"]', 'a repair: each change with its code');
const critArgs = { qasm: 'OPENQASM 3.0;\nqubit[3] q;\nh q[0];\nh q[0];\ncx q[0], q[1];\nrz(0.2) q[1];\nrz(0.3) q[1];\n' };
const crit = await draw(page, pages, 'critique', critArgs, await call(cli, 'critique_qasm', critArgs));
check((await crit.frame.$$('#wq-smells li')).length === 3, `a critique: its smells (${await text(crit.frame, '#wq-smells')})`);
await crit.frame.hover('#wq-smells li');
check((await crit.frame.$$('#wq-circuit .qc-item.qc-smell')).length === 2, 'the smell pointed at is marked on the circuit');
const estArgs = { qasm: 'OPENQASM 3.0;\nqubit[3] q;\nh q;\nccx q[0], q[1], q[2];\nrz(0.3) q[0];\n' };
const est = await call(cli, 'estimate_resources', estArgs);
const e = await draw(page, pages, 'estimate', estArgs, est);
check((await e.frame.$$('#wq-frontier .qs-frontier-point')).length === est.structuredContent.result.frontier.length && (await receiptOf(e.frame)) === 'valid', `an estimate: its frontier, and its seal checked in the view (${await text(e.frame, '#wq-headline')})`);
const tArgs = { target: 'synthetic-heavy-hex-27' };
const t = await draw(page, pages, 'target', tArgs, await call(cli, 'show_target', tArgs));
check((await t.frame.$$('#wq-map .qs-dev-q')).length === 27 && (await t.frame.$$('#wq-map .qs-dev-pair')).length === 28, 'a device target: its coupling map, every qubit and pair');
const onArgs = { qasm: BELL, shots: 300, target: 'synthetic-line-5' };
const on = await draw(page, pages, 'result', onArgs, await call(cli, 'run_on_target', onArgs));
check((await receiptOf(on.frame)) === 'valid' && /synthetic-line-5/.test(await text(on.frame, '#qs-dev-name')), 'a run on a device target: the device’s account, and the receipt checked');
const light = await draw(page, pages, 'result', runArgs, run, { theme: 'light' });
check((await light.frame.$eval('html', (h) => h.dataset.theme)) === 'light', 'the host’s light theme is the view’s');
const away = seen.requests.filter((u) => !u.startsWith(ORIGIN));
check(away.length === 0, `the views made no request of their own${away.length ? `: ${away.join(', ')}` : ''}`);
// The default policy refuses WebAssembly, and the browser says so; nothing else may err.
const errors = seen.errors.filter((m) => !/WebAssembly|wasm-unsafe-eval|unsafe-eval/i.test(m));
check(errors.length === 0, `no script error in a view${errors.length ? `: ${errors.join(' | ')}` : ''}`);
await ctx.close();
}
cli.close();
// ---- the job service's server, over HTTP ----
if (SERVICE) {
const dir = mkdtempSync(join(tmpdir(), 'wq-views-'));
const port = 18300 + Math.floor(Math.random() * 500);
const base = `http://127.0.0.1:${port}`;
const svc = spawn(SERVICE, ['serve'], { env: { ...process.env, WAI_QSVC_KEY: join(dir, 'key'), WAI_QSVC_ADDR: `127.0.0.1:${port}`, WAI_QSVC_BUDGET_GB: '4', WAI_QSVC_JOB_GB: '2', WAI_QSVC_WORKERS: '2' }, stdio: ['ignore', 'ignore', 'inherit'] });
for (let i = 0; i < 100; i++) { if (await fetch(`${base}/v1/health`).then((r) => r.ok, () => false)) break; await new Promise((r) => setTimeout(r, 200)); }
const s = http(base);
const { named, pages, appTools, tools } = await discover(s);
check(/^wq-views-[0-9a-f]{32}$/.test(s.session() ?? ''), `the service's session id says the host draws views (${s.session()})`);
const poll = tools.find((x) => x.name === 'poll_quantum_job');
check(poll && JSON.stringify(poll._meta.ui.visibility) === '["app"]' && appTools.includes('poll_quantum_job'), 'the view’s own tool for a running job is kept from the model');
check(named.run_quantum_circuit?.resourceUri === 'ui://wai-quantum/result' && named.verify_quantum_receipt?.resourceUri === 'ui://wai-quantum/receipt', 'the service’s tools name their views');
const plain = http(base);
await plain.rpc('initialize', { protocolVersion: '2025-11-25', capabilities: {} });
check(plain.session() === null && (await plain.rpc('tools/list')).result.tools.every((x) => !x._meta), 'a host that draws no views gets no session id and no views');
const { ctx, page, seen } = await hostFor(s);
const runArgs = { qasm: BELL, shots: 500 };
const run = await call(s, 'run_quantum_circuit', runArgs);
const v = await draw(page, pages, 'result', runArgs, run, { appTools });
check((await receiptOf(v.frame)) === 'valid', `the service's run drawn, its receipt checked in the view (${await text(v.frame, '#wq-signer')})`);
// A job the service is still running: the view follows it to its end.
const sub = await (await fetch(`${base}/v1/jobs`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ qasm: BELL, shots: 200, verdict: true }) })).json();
const running = { content: [{ type: 'text', text: 'still running' }], structuredContent: { id: sub.id, status: 'queued' }, isError: false };
const f = await draw(page, pages, 'result', { qasm: BELL }, running, { appTools });
check((await receiptOf(f.frame)) === 'valid' && (await f.log()).calls.includes('poll_quantum_job'), 'a job still running is followed by the view to its end, and its receipt checked');
const g = await draw(page, pages, 'result', { qasm: BELL }, running, { appTools: [], state: 'running' });
await g.frame.waitForFunction(() => /would not let the view follow/.test(document.querySelector('#wq-waiting')?.textContent ?? ''), null, { timeout: LONG }).catch(() => {});
check((await g.log()).refused.includes('poll_quantum_job'), 'a host that refuses the view’s call: the view says so, and names get_quantum_job');
check(seen.requests.every((u) => u.startsWith(ORIGIN)), 'the service’s views made no request of their own');
await ctx.close();
svc.kill();
rmSync(dir, { recursive: true, force: true });
}
await browser.close();
host.close();
console.log(fails ? `${fails} check(s) failed` : 'every check passed');
process.exit(fails ? 1 : 0);