Re-landed from PR #668 (branch wai/quantum-planner-contract, merged with main
495651ddd at 37350335b) as one commit on main, under the land-it model. Five
review rounds; round 5's findings (two high: workers that died as PID 1) were
fixed on the branch, and its CI reds were traced and fixed before landing.
A job may carry a contract (deadline, energy ceiling, cost, accuracy); the
planner chooses a route that meets it or refuses with every route's reason,
and its reasoning is sealed in the receipt. Under #676's reading child the
contract job is planned in the child from the per-method timings the parent
sends, bounded as a job naming its chosen method plus its reasoning, kept
across a restart. Both verifiers (Rust and receipt_verify.py) check the
planner statement beside wasmQ's; the receipts corpus is regenerated with
both kinds (268 crafted receipts, 14 plants caught). The views page carries
its engine raw-deflated and stays under its 1 MiB limit (669,484 bytes).
Passed before the push (Framework, linux; M3, macOS): every repository
check, receipts conformance, breaking, corpora, catalogue, the qpu clients,
energy (wai-rs, wai-quantum full, serve), the CLI tests and clippy, writer,
smoke, cross-simulator corpus, job receipts, wasmQ tests and vectors, the
engine with and without receipts under -D warnings, the docs pages, and the
service suite with clippy.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
132 lines
7.8 KiB
JavaScript
132 lines
7.8 KiB
JavaScript
// Build the views an agent host draws (src/views/) into one self-contained
|
|
// page: the view's script (the Studio's components, bundled), the Studio's
|
|
// styles, and the engine inside the view (wai/wai-quantum-views) twice, as
|
|
// WebAssembly and as the same build translated to JavaScript, each raw-deflated
|
|
// and then base64: the view inflates the form it runs with the platform's
|
|
// DecompressionStream. The page
|
|
// loads nothing and connects nowhere. The servers serve it for each of the six
|
|
// views, naming the view in its <meta name="wai-view">.
|
|
//
|
|
// node build-views.mjs --wasm VIEWS.wasm --js VIEWS.mjs --out DIR
|
|
//
|
|
// writes DIR/wai-quantum-views.html and DIR/manifest.json (each part's size and
|
|
// SHA-256, and every source the page is built from with its SHA-256). Run it
|
|
// through wai/wai-quantum-views/build.sh, which builds the two engines first.
|
|
//
|
|
// node build-views.mjs --check
|
|
//
|
|
// says whether the committed page (wai/wai-quantum/views/) is built from the
|
|
// sources as they are: every source it names unchanged, and no source added.
|
|
import { build, transform } from 'esbuild';
|
|
import { createHash } from 'node:crypto';
|
|
import { deflateRawSync } from 'node:zlib';
|
|
import { mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs';
|
|
import { dirname, join, relative } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
const repo = join(here, '..', '..');
|
|
const arg = (k) => { const i = process.argv.indexOf(k); return i > 0 ? process.argv[i + 1] : null; };
|
|
const [wasmPath, jsPath, out] = [arg('--wasm'), arg('--js'), arg('--out')];
|
|
const checking = process.argv.includes('--check');
|
|
if (!checking && (!wasmPath || !jsPath || !out)) {
|
|
console.error('usage: node build-views.mjs --wasm VIEWS.wasm --js VIEWS.mjs --out DIR');
|
|
process.exit(2);
|
|
}
|
|
// The most a view may be: the servers' tests hold every view to it.
|
|
export const LIMIT = 1 << 20;
|
|
// The name the servers replace with the view's.
|
|
export const PLACEHOLDER = '__WAI_QUANTUM_VIEW__';
|
|
|
|
const sha = (b) => createHash('sha256').update(b).digest('hex');
|
|
const bytes = (s) => Buffer.byteLength(s, 'utf8');
|
|
|
|
// The views are in English: the Studio's components they share read their
|
|
// words through src/i18n.js, and here its locales are English alone, so the
|
|
// page carries no other language's words.
|
|
const englishOnly = {
|
|
name: 'english-only',
|
|
setup(b) {
|
|
b.onResolve({ filter: /\/locales\/index\.js$/ }, () => ({ path: 'english-only', namespace: 'wai-views-locales' }));
|
|
b.onLoad({ filter: /.*/, namespace: 'wai-views-locales' }, () => {
|
|
const dir = join(here, 'locales', 'en');
|
|
const areas = readdirSync(dir).filter((f) => f.endsWith('.json')).sort();
|
|
const names = areas.map((f, i) => `en${i}`);
|
|
return {
|
|
resolveDir: dir,
|
|
loader: 'js',
|
|
contents: `${areas.map((f, i) => `import ${names[i]} from './${f}';`).join('\n')}\nexport const AREAS = ${JSON.stringify(areas.map((f) => f.replace(/\.json$/, '')))};\nexport default { en: Object.assign({}, ${names.join(', ')}) };\n`,
|
|
};
|
|
});
|
|
},
|
|
};
|
|
// The view's script, and every module it bundles.
|
|
const app = await build({ absWorkingDir: here, entryPoints: [join(here, 'src/views/main.js')], bundle: true, format: 'esm', target: ['es2022'], minify: true, legalComments: 'none', write: false, metafile: true, logLevel: 'warning', plugins: [englishOnly] });
|
|
const script = app.outputFiles[0].text;
|
|
const styleFiles = ['src/tokens.css', 'src/studio.css', 'src/views/views.css'];
|
|
// Every source, relative to the repository.
|
|
const sourcesOf = (inputs) => [
|
|
...Object.keys(inputs).filter((p) => !p.startsWith('wai-views-locales:')).map((p) => join(here, p)),
|
|
...styleFiles.map((f) => join(here, f)),
|
|
join(here, 'build-views.mjs'),
|
|
...['src/lib.rs', 'Cargo.toml', 'Cargo.lock', 'build.sh'].map((f) => join(repo, 'wai/wai-quantum-views', f)),
|
|
].map((p) => relative(repo, p).split('\\').join('/')).sort();
|
|
if (checking) {
|
|
const committed = JSON.parse(readFileSync(join(repo, 'wai/wai-quantum/views/manifest.json'), 'utf8'));
|
|
const now = sourcesOf(app.metafile.inputs);
|
|
const stale = now.filter((p) => committed.sources[p] !== sha(readFileSync(join(repo, p))));
|
|
const gone = Object.keys(committed.sources).filter((p) => !now.includes(p));
|
|
if (stale.length || gone.length) {
|
|
console.error(`the committed views page is not built from these sources: ${[...stale.map((p) => `${p} changed`), ...gone.map((p) => `${p} no longer used`)].join('; ')}. Rebuild it: bash wai/wai-quantum-views/build.sh`);
|
|
process.exit(1);
|
|
}
|
|
console.log(`the committed views page is built from these ${now.length} sources`);
|
|
process.exit(0);
|
|
}
|
|
// The engine as JavaScript, made a factory: it runs only when a host's policy refuses the WebAssembly.
|
|
const engineJs = (await transform(readFileSync(jsPath, 'utf8'), { format: 'iife', globalName: '__wqEngine', minify: true, legalComments: 'none', target: 'es2022' })).code;
|
|
const factory = `globalThis.wqEngineJs=function(){${engineJs};return __wqEngine};`;
|
|
const wasm = readFileSync(wasmPath);
|
|
// Both forms carried deflated (RFC 1951, zlib's best compression): the
|
|
// WebAssembly in less than half its bytes and the JavaScript in about a fifth,
|
|
// and the page within its limit with the engine whole, in both forms.
|
|
const wasmDeflated = deflateRawSync(wasm, { level: 9 });
|
|
const factoryDeflated = deflateRawSync(Buffer.from(factory, 'utf8'), { level: 9 });
|
|
const css = (await transform(styleFiles.map((f) => readFileSync(join(here, f), 'utf8')).join('\n'), { loader: 'css', minify: true, legalComments: 'none' })).code;
|
|
|
|
// Text inside a <script> or <style> must not end it early.
|
|
const inert = (s, tag) => s.replace(new RegExp(`</${tag}`, 'gi'), `<\\/${tag}`);
|
|
const html = `<!doctype html>
|
|
<html lang="en" data-theme="auto">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<meta name="wai-view" content="${PLACEHOLDER}">
|
|
<meta name="generator" content="wai-quantum-views">
|
|
<title>WAI Quantum</title>
|
|
<style>${inert(css, 'style')}</style>
|
|
</head>
|
|
<body>
|
|
<main id="wq" aria-live="polite"></main>
|
|
<script>globalThis.WQ_ENGINE_WASM="${wasmDeflated.toString('base64')}";globalThis.WQ_ENGINE_JS="${factoryDeflated.toString('base64')}";</script>
|
|
<script type="module">${inert(script, 'script')}</script>
|
|
</body>
|
|
</html>
|
|
`;
|
|
if (html.split(PLACEHOLDER).length !== 2) throw new Error('the page must name its view in one place');
|
|
if (bytes(html) > LIMIT) throw new Error(`the page is ${bytes(html)} bytes, past the limit of ${LIMIT}`);
|
|
|
|
const sources = sourcesOf(app.metafile.inputs);
|
|
const manifest = {
|
|
format: 'wai.quantum.views-bundle/1',
|
|
about: 'The page every view of WAI Quantum is served from, the view named in its <meta name="wai-view">. Built by wai/wai-quantum-views/build.sh; do not edit it by hand.',
|
|
placeholder: PLACEHOLDER,
|
|
limit_bytes: LIMIT,
|
|
bytes: { page: bytes(html), script: bytes(script), styles: bytes(css), engine_wasm: wasm.length, engine_wasm_deflated: wasmDeflated.length, engine_wasm_base64: Math.ceil(wasmDeflated.length / 3) * 4, engine_js: bytes(factory), engine_js_deflated: factoryDeflated.length, engine_js_base64: Math.ceil(factoryDeflated.length / 3) * 4 },
|
|
sha256: { page: sha(html), script: sha(script), styles: sha(css), engine_wasm: sha(wasm), engine_wasm_deflated: sha(wasmDeflated), engine_js: sha(factory), engine_js_deflated: sha(factoryDeflated) },
|
|
sources: Object.fromEntries(sources.map((p) => [p, sha(readFileSync(join(repo, p)))])),
|
|
};
|
|
mkdirSync(out, { recursive: true });
|
|
writeFileSync(join(out, 'wai-quantum-views.html'), html);
|
|
writeFileSync(join(out, 'manifest.json'), JSON.stringify(manifest, null, 2) + '\n');
|
|
console.log(`wrote ${join(out, 'wai-quantum-views.html')}: ${bytes(html)} bytes (script ${bytes(script)}, styles ${bytes(css)}, engine ${wasm.length} as WebAssembly (${wasmDeflated.length} deflated) and ${bytes(factory)} as JavaScript (${factoryDeflated.length} deflated))`);
|