Every registered string now lives in one table (codecs/registry.rs) with its kind, status, media class, registering SPEC section, edition and registration date. `wai registry export` writes registry/capabilities.json, its JSON Schema, the generated table in REGISTRY.md and vocab v2; --check fails on drift. vocab v1 stays stable. A candidate capability (a registered string whose payload format no revision has pinned) must not be emitted. The C packers, the browser writers and `wai wrap` refuse one, and `codecs::is_dispatchable` is the single dispatch and selection predicate (SPEC §4): candidates, companion-only capabilities, records and derivations are not dispatchable, and an unregistered string is decoded only where the sink advertises it. The codec glossary is linked to the registry through a reviewed table and held to its schema by a library test; `org` becomes `body`, from a closed allowlist. The SPEC gains §5 sections on kinds and candidates, a §8 edition rule, corrected citations and rewritten appendices. The quantum-ops vectors are regenerated with neutral device ids. CI declares the feature-gated test targets with required-features, checks the registry export, builds wai-web for wasm32, and runs two public-text checks (comparative prose, and private name patterns) with a self-test. The specification, extensions, research notes, READMEs, glossary and source comments are rewritten to name no company or product and to make no comparisons. Co-Authored-By: Claude Opus 5.5 <[email protected]>
79 lines
4.1 KiB
JavaScript
79 lines
4.1 KiB
JavaScript
// Reproducible proof: the wasm32 FULL CONDITIONAL codec decode is byte-identical
|
||
// to the native reference — a pretrained learned image codec (PTQ of mbt2018_mean)
|
||
// whose whole bitstream→image decode (factorized z, integer h_s, integer-σ
|
||
// entropy, integer g_s) runs in the browser engine with NO ML runtime, and whose
|
||
// RGB verifies against the native FNV-1a hash. The integer-entropy contract,
|
||
// in-browser.
|
||
//
|
||
// Run via demo/verify_int_hyper.sh (builds the nodejs-target pkg first), or:
|
||
// node verify_int_hyper_wasm.mjs [path/to/wai_web.js] [model-dir]
|
||
// The model dir (default $WAI_SOTA_INT_DIR or /tmp/sota_int) holds the artifacts
|
||
// tools/wai_sota_int_synth_export.py emits (the g_s/h_s model JSONs are large, so
|
||
// they are NOT committed — regenerate them with the export tool).
|
||
|
||
import { readFileSync, existsSync } from 'node:fs';
|
||
import { dirname, resolve } from 'node:path';
|
||
import { fileURLToPath, pathToFileURL } from 'node:url';
|
||
|
||
const here = dirname(fileURLToPath(import.meta.url));
|
||
const pkg = resolve(here, process.argv[2] ?? './pkg-node/wai_web.js');
|
||
const named = process.argv[3] ?? process.env.WAI_SOTA_INT_DIR;
|
||
const dir = resolve(named || '/tmp/sota_int');
|
||
|
||
// The same rule as wai-rs/tests/support/require_vectors.rs: a missing artifact is a
|
||
// failure when WAI_REQUIRE_SOTA_VECTORS is on (any value but empty, 0, false, no), or
|
||
// when a directory was named (the argument or WAI_SOTA_INT_DIR); it skips only when
|
||
// neither holds and the default location lacks the artifacts.
|
||
const sw = (process.env.WAI_REQUIRE_SOTA_VECTORS ?? '').trim().toLowerCase();
|
||
const required = !['', '0', 'false', 'no'].includes(sw);
|
||
const ARTIFACTS = ['cond.json', 'cond_entropy.json', 'cond_bitstream.json', 'int_synth_sota_model.json',
|
||
'int_hs_model.json', 'int_synth_sota_model.bin', 'int_hs_model.bin'];
|
||
const missing = ARTIFACTS.filter((f) => !existsSync(`${dir}/${f}`));
|
||
if (missing.length) {
|
||
if (required || named) {
|
||
console.error(`FAIL: missing in ${dir}: ${missing.join(', ')}`
|
||
+ (required ? ' (WAI_REQUIRE_SOTA_VECTORS is set)' : ' (the directory was named explicitly)'));
|
||
process.exit(1);
|
||
}
|
||
console.log(`skip: artifacts not in ${dir} (run tools/wai_sota_int_synth_export.py) — NOT VERIFIED`);
|
||
process.exit(0);
|
||
}
|
||
const wai = await import(pathToFileURL(pkg).href);
|
||
const bytes = (name) => new Uint8Array(readFileSync(`${dir}/${name}`));
|
||
|
||
// FNV-1a 64-bit — the same dependency-free checksum the export tool and the
|
||
// native Rust decode share (no domain separation; raw RGB bytes).
|
||
function fnv1a(buf) {
|
||
const mask = (1n << 64n) - 1n;
|
||
let h = 14695981039346656037n;
|
||
for (let i = 0; i < buf.length; i++) h = ((h ^ BigInt(buf[i])) * 1099511628211n) & mask;
|
||
return h.toString(16).padStart(16, '0');
|
||
}
|
||
|
||
const conf = JSON.parse(readFileSync(`${dir}/cond.json`, 'utf8'));
|
||
const ent = bytes('cond_entropy.json'); // y Gaussian-conditional + z factorized tables
|
||
const bs = bytes('cond_bitstream.json'); // z_rans + y_rans
|
||
|
||
function check(label, img) {
|
||
const got = fnv1a(img.rgb);
|
||
const ok = got === conf.rgb_hash
|
||
&& img.width === conf.out_w && img.height === conf.out_h
|
||
&& img.rgb.length === conf.out_w * conf.out_h * conf.out_c;
|
||
console.log(`${ok ? 'PASS' : 'FAIL'} ${label.padEnd(12)} ${img.width}x${img.height} ${img.rgb.length}B `
|
||
+ `${got.slice(0, 16)}… ${got === conf.rgb_hash ? '== native hash' : `MISMATCH (want ${conf.rgb_hash})`} `
|
||
+ `(${conf.psnr_cond?.toFixed?.(2) ?? '?'} dB)`);
|
||
return ok;
|
||
}
|
||
|
||
// JSON model path and the COMPACT BINARY model path must both reproduce the
|
||
// native hash (the binary is what the browser demo downloads — ~½ the size).
|
||
const json = check('json model', wai.decodeIntHyper(
|
||
bytes('int_synth_sota_model.json'), bytes('int_hs_model.json'), ent, bs));
|
||
const bin = check('binary model', wai.decodeIntHyperBin(
|
||
bytes('int_synth_sota_model.bin'), bytes('int_hs_model.bin'), ent, bs));
|
||
|
||
const pass = json && bin;
|
||
console.log(pass
|
||
? '\n✓ wasm32 conditional-codec decode (JSON + compact-binary model) is byte-identical to the native reference — a pretrained learned codec decoded in-browser, zero JS ML runtime.'
|
||
: '\n✗ mismatch');
|
||
process.exit(pass ? 0 : 1);
|