open-standards/wai/wai-int-module/cases.mjs
Transaction Science 98d646dc92 wai-int-module: the first WA²ISM module of record, the operator-set module held at the integer level on every engine
wai/wai-int-module is one WebAssembly module with the interface of waism §5 and nothing else:
no import, one memory with a declared maximum, and every allocation trapping where memory
cannot grow (R13). It decodes the integer payloads (WIV1, WIH1, WIA1, WIS1), int_synth,
int_mlicpp, opus.dred.int and avatar.face through wai-rs's integer decoders, whose networks
run on wai-ops/1; networks and weights arrive as each capability's set.

- record/: the scalar and SIMD128 builds (7,099 vector instructions), named by SHA-256 in
  manifest.json, each held at the integer level by the Rust checker and check-module.mjs.
  build.sh builds both with the machine's paths remapped and checks both with both checkers.
  A build on another host has other bytes (the compiler's crate hashes take in each path
  dependency's absolute path and the host) and gives every case the same outcome.
- corpus.json (corpus.py): every committed vector and refusal fixture of those
  capabilities, 120 cases, from the committed corpora.
- host/ (wasmtime 50.0.1 Cranelift and Winch, Wasmi 2.0.0 with floats off), run-node.mjs and
  browser/run-browser.mjs run every case as a sink does and print one run digest; every run,
  on Linux x86_64, Linux arm64 and macOS arm64, gives the same.
- plant.py plants a float or a relaxed vector instruction in a module, for the checkers.
- waism §5, The set: one file as its bytes, several as their file sequence. avatars §6 names
  generator.wfg and identity.wfi; avatars §7.8, opus-dred §4.8 and determinism-tiers §5 state
  the module as a fabric.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-08 16:45:56 -05:00

90 lines
3.6 KiB
JavaScript

// cases.mjs: the module corpus (corpus.json) read into the bytes a sink passes, and the
// checks every runner makes of what came back. For Node; no dependency.
//
// A case's payload is a path below wai/, or {"hex"}; its set is null, one file, {"external"}
// (a file the corpus names by digest and does not hold, read from the --external
// directory), or a list of [name, file] pairs passed as waism §5's file sequence: for each
// file in order, the name's length in one byte, the name, the file's length in four bytes
// little-endian, and the file.
import { createHash } from 'node:crypto';
import { readFileSync } from 'node:fs';
import { dirname, join, resolve } from 'node:path';
export function sha256(b) {
return createHash('sha256').update(b).digest('hex');
}
function file(v, wai, external) {
if (typeof v === 'string') return new Uint8Array(readFileSync(join(wai, v)));
if (v && typeof v.hex === 'string') return new Uint8Array(Buffer.from(v.hex, 'hex'));
if (v && typeof v.file === 'string') return new Uint8Array(readFileSync(join(wai, v.file)));
if (v && typeof v.external === 'string') {
if (!external) throw new Error(`${v.external} is an external file, and no --external directory was given`);
return new Uint8Array(readFileSync(join(external, v.external)));
}
throw new Error(`not a file: ${JSON.stringify(v)}`);
}
function set(v, wai, external) {
if (v === null) return new Uint8Array(0);
if (!Array.isArray(v)) return file(v, wai, external);
const parts = [];
for (const [name, what] of v) {
const b = file(what, wai, external);
const n = Buffer.from(name, 'utf8');
if (n.length < 1 || n.length > 255) throw new Error(`a name of ${n.length} bytes`);
const len = Buffer.alloc(4);
len.writeUInt32LE(b.length);
parts.push(Buffer.from([n.length]), n, len, b);
}
return new Uint8Array(Buffer.concat(parts));
}
/** The cases of a corpus.json, with their bytes. */
export function loadCases(corpusPath, { external = null, only = null } = {}) {
const doc = JSON.parse(readFileSync(corpusPath, 'utf8'));
if (doc.format !== 'waism-module-corpus/1') throw new Error(`${corpusPath}: not a waism-module-corpus/1`);
const wai = dirname(dirname(resolve(corpusPath)));
const cases = [];
for (const c of doc.cases) {
if (only && !c.name.startsWith(only)) continue;
try {
cases.push({ name: c.name, cap: c.capability, payload: file(c.payload, wai, external), set: set(c.set, wai, external), expect: c.expect });
} catch (e) {
throw new Error(`${c.name}: ${e.message}`);
}
}
return cases;
}
/** A result as one line of the run digest: sha256:<hex>, refuse:<code> or sink:<code>. */
export function outcomeLine(r) {
if (r.output) return `sha256:${sha256(r.output)}`;
if (r.refused !== undefined) return `refuse:${r.refused}`;
return `sink:${r.sink}`;
}
/** Whether a result is the one the case states. */
export function holds(c, r) {
if (c.expect.sha256 !== undefined) return !!r.output && sha256(r.output) === c.expect.sha256;
return r.refused === c.expect.refuse;
}
/** The check of one run: each case its corpus outcome, and the run digest. */
export function judge(cases, results) {
const lines = [];
let wrong = 0;
const report = [];
cases.forEach((c, i) => {
const r = results[i];
const line = outcomeLine(r);
lines.push(`${c.name} ${line}\n`);
if (!holds(c, r)) {
wrong++;
const want = c.expect.sha256 !== undefined ? `sha256:${c.expect.sha256}` : `refuse:${c.expect.refuse}`;
report.push(` ${c.name}: ${line}${r.detail ? ` (${r.detail})` : ''}, not ${want}`);
}
});
return { wrong, report, digest: sha256(Buffer.from(lines.join(''), 'utf8')) };
}