open-standards/wai/wai-web/reach
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Transaction Science 603c6bbb8c
Some checks failed
standards workspaces build + test / jouleclaw-rs (pull_request) Successful in 2m20s
standards workspaces build + test / aac-rs (pull_request) Successful in 1m44s
standards workspaces build + test / joule-code-rs (pull_request) Successful in 46s
standards workspaces build + test / joulecontract-rs (pull_request) Successful in 35s
standards workspaces build + test / map-rs (pull_request) Successful in 29s
standards workspaces build + test / jouledesk-rs (pull_request) Successful in 6m9s
standards workspaces build + test / mesh-rs (pull_request) Successful in 28s
standards workspaces build + test / openpay (pull_request) Successful in 1m25s
wai cross-architecture byte-equality / linux-aarch64 (pull_request) Successful in 22s
standards workspaces build + test / macOS energy backends (pull_request) Successful in 8m2s
wai cross-architecture byte-equality / macos-arm64 (reference) (pull_request) Successful in 28s
WAI conformance / cargo-class corpora · linux-arm64 (pull_request) Successful in 6m24s
WAI conformance / cross-arch byte-equality · linux-arm64 (pull_request) Successful in 30s
WAI energy receipts / energy receipts · linux-arm64 (pull_request) Successful in 2m0s
WAI conformance / cargo-class corpora · macos (pull_request) Successful in 12m30s
WAI conformance / cross-arch byte-equality · macos (pull_request) Successful in 29s
WAI energy receipts / energy receipts · macos (pull_request) Successful in 3m38s
joule-code conformance gate / Build + round-trip C1-C4 vectors (pull_request) Successful in 1m57s
no-vanity-metrics / Scan committed Markdown for vanity metrics (pull_request) Successful in 4s
no-vanity-metrics / WAI public text names no comparison (pull_request) Successful in 4s
no-vanity-metrics / WAI public text names no company or product (pull_request) Successful in 4s
standards workspaces build + test / arl-rs (pull_request) Successful in 1m20s
standards workspaces build + test / jcp-rs (pull_request) Successful in 1m51s
standards workspaces build + test / eoc-rs (pull_request) Successful in 2m12s
standards workspaces build + test / joule-ui-rs (pull_request) Successful in 1m9s
standards workspaces build + test / joulehook-rs (pull_request) Successful in 1m12s
standards workspaces build + test / proof-rs (pull_request) Successful in 1m17s
standards workspaces build + test / sense-rs (pull_request) Successful in 1m15s
standards workspaces build + test / sandbox-rs (pull_request) Successful in 2m23s
wai cross-architecture byte-equality / linux-x86_64 (pull_request) Successful in 28s
standards workspaces build + test / smart-byte-rs (pull_request) Successful in 4m14s
WAI conformance / quantum core and corpus under wasm32-wasip2 (wasmtime) (pull_request) Failing after 1m22s
WAI conformance / v2 vocabulary · manifests validate as stated (pull_request) Successful in 16s
WAI conformance / cargo-class corpora · linux (pull_request) Successful in 8m41s
WAI conformance / delivery telemetry · CMCD and CMSD against an independent library (pull_request) Successful in 10s
WAI conformance / cross-arch byte-equality · linux (pull_request) Successful in 28s
WAI conformance / C2PA manifest emission · signed, read back, validated (pull_request) Successful in 50s
WAI energy receipts / energy receipts · linux (pull_request) Successful in 1m56s
WAI HTTP delivery / WAI HTTP · does this change touch it (pull_request) Successful in 5s
WAI JPEG-AI whole decode (pinned inputs, required) / fetch pinned JPEG-AI inputs → whole decode + pixel hash, required (pull_request) Successful in 1m8s
WAI neural decoders end-to-end / synthetic fixtures → Rust decode → reference (±4) (pull_request) Successful in 1m14s
WAI neural sink gates / model-free refusals of the neural sink (pull_request) Successful in 1m12s
wai-pack / wai-pack · does this change touch it (pull_request) Successful in 5s
WAI section references / cited sections exist (pull_request) Successful in 11s
WAI mbt2018_mean integer decode (regenerated vectors) / regenerate mbt2018_mean integer vectors → Rust decode, required (pull_request) Successful in 4m39s
wai-web reach builds / wai-web reach · does this change touch it (pull_request) Successful in 4s
WAI conformance / independent verifiers · claims, JWP, key discovery and integer payloads (Python) (pull_request) Successful in 16m42s
workflow hygiene / every workflow supersedes its own queue (pull_request) Successful in 4s
wai-web wasm32 / wai-web wasm32 build · corpus, menu and writers (pull_request) Successful in 3m10s
wai-pack / wai-pack · linux-arm64 (pull_request) Successful in 1m22s
wai-pack / wai-pack · macos (pull_request) Successful in 2m24s
WAI HTTP delivery / HTTP corpus · falsify mutations 0/3 (pull_request) Successful in 35m55s
WAI HTTP delivery / HTTP corpus · falsify mutations 1/3 (pull_request) Successful in 36m25s
WAI HTTP delivery / HTTP corpus · falsify patches 0/3 (pull_request) Successful in 4m34s
WAI HTTP delivery / HTTP corpus · falsify patches 1/3 (pull_request) Successful in 5m18s
WAI HTTP delivery / HTTP corpus · falsify patches 2/3 (pull_request) Successful in 4m34s
WAI HTTP delivery / HTTP corpus · every patch and mutation in exactly one shard (pull_request) Successful in 4s
WAI HTTP delivery / HTTP corpus · verified both ways, and the differential (pull_request) Successful in 2m19s
WAI HTTP delivery / cache rules · controls and labelled caches (pull_request) Successful in 1m13s
wai-pack / wai-pack · linux (pull_request) Successful in 1m8s
wai-pack / wai-pack · independent verifier (Python) (pull_request) Successful in 1m47s
wai-pack / wai-pack · the floor through a media prober (pull_request) Successful in 20s
wai-pack / wai-pack · dependency licences (pull_request) Successful in 1m14s
wai-web reach builds / wai-web reach builds · the package of record, every output as recorded (pull_request) Successful in 2m30s
WAI HTTP delivery / HTTP corpus · falsify mutations 2/3 (pull_request) Successful in 37m25s
wai-web reach builds / wai-web reach builds · this tree's wai-web, every rung on its engine line (pull_request) Successful in 4m6s
wai-web reach: the record leg runs the record's own demo scripts
The record leg holds the package of record (reach/record/, built from the
commit reach/manifest.json names) to the demo verify scripts. It ran this
tree's scripts, so a check added after the record's commit was run against
code that predates it: #201's verify_pack test of the writers' manifest
limit failed on every rung of the record leg (jobs 21114, 21188), because
the recorded wai_web_bg.wasm was built before the writers refused a
manifest past 2^27.

run-modern.mjs takes --demo-commit <commit>: step 3 then runs that commit's
demo scripts over that commit's corpora (its wai/, exported with git
archive). A script this tree runs that the commit lacks is reported as
SKIP, not as a pass; a commit with none of them fails the run, as does an
empty or unknown commit. The record leg fetches input.commit (the checkout
is shallow) and passes it. The tree leg is unchanged: this tree's scripts
against this tree's build, where the new verify_pack check runs and passes.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-11 03:44:06 -04:00
..
record wai-web reach: CI holds the served bytes to the manifest; the package of record kept in-tree 2026-10-09 21:46:01 -04:00
src wai-web reach: CI holds the served bytes to the manifest; the package of record kept in-tree 2026-10-09 21:46:01 -04:00
test wai-web reach: the record leg runs the record's own demo scripts 2026-10-11 03:44:06 -04:00
tools wai-web reach: CI holds the served bytes to the manifest; the package of record kept in-tree 2026-10-09 21:46:01 -04:00
build.sh wai-web reach: J's wasm2js from binaryen's WebAssembly build, so every host gives the recorded bytes 2026-10-09 22:24:11 -04:00
manifest.json wai-web reach: J's wasm2js from binaryen's WebAssembly build, so every host gives the recorded bytes 2026-10-09 22:24:11 -04:00
package-lock.json wai-web reach: J's wasm2js from binaryen's WebAssembly build, so every host gives the recorded bytes 2026-10-09 22:24:11 -04:00
package.json wai-web reach: J's wasm2js from binaryen's WebAssembly build, so every host gives the recorded bytes 2026-10-09 22:24:11 -04:00
profiles.json wai-web: reach builds — L1 legalised, L2 MVP-lowered, J wasm2js, and an ES5 loader 2026-10-09 18:36:04 -04:00
README.md wai-web reach: the record leg runs the record's own demo scripts 2026-10-11 03:44:06 -04:00

wai-web reach builds

The shipped wai-web package (wai_web.js and wai_web_bg.wasm, the wasm-bindgen web target) runs where an engine has WebAssembly with sign-extension, non-trapping float-to-int conversion and bulk memory, where a 64-bit integer crosses the JS boundary as a BigInt, and where ES modules load. The reach builds run the same code on engines without those features, and give the same outputs byte for byte. Nothing in the Rust changes: every reach build is derived from the shipped package itself.

rung files the engine needs the CI proxies it runs on
shipped ../wai_web.js, ../wai_web_bg.wasm (the package) WebAssembly with sign-extension, non-trapping float-to-int and bulk memory; i64 as BigInt; ES modules a current Node
L1 wai_web_bg.l1.wasm + wai_web.reach.js the same WebAssembly features; no BigInt at the boundary node:12, node:14
L2 wai_web_bg.l2.<profile>.wasm + wai_web.reach.js MVP WebAssembly node:8.0.0 (receipt), node:8, node:10
J wai_web.j.<profile>.js ES5 and typed arrays; no WebAssembly node:0.12, node:8.0.0 (full), and a current Node stripped to ES5
  • L1 is the shipped binary with its JS interface legalised (wasm-opt --legalize-js-interface): an i64 parameter becomes a pair of i32 halves, and an i64 result returns its low half and passes the high half through env.setTempRet0. Nothing else changes, so the code runs at the shipped build's speed.
  • L2 is the shipped binary lowered to the MVP (--signext-lowering, --llvm-nontrapping-fptoint-lowering, --llvm-memory-copy-fill-lowering) and then legalised. Lowered without legalisation it would still fail every export with an i64 in its signature, so it is never shipped alone.
  • J is the MVP-lowered module translated to JavaScript by wasm2js, in one ES5 script per profile: the runtime, the i64 table, the translation and the glue.

wai_web.reach.js and every J script carry the same ES5 parts:

  • The runtime (src/wai-reach-runtime.js) installs what an old engine lacks and the glue or the translation uses (Math.imul / fround / clz32 / trunc, TypedArray#slice, TextEncoder / TextDecoder, Array#fill, a small Map, a Symbol.iterator stand-in, and a few more), each only where it is missing. It also joins and splits i64 values at the boundary, from the table the build extracts from the binary. With BigInt a 64-bit value is a BigInt, as the shipped build gives it. Without BigInt it is a Number, exact below 2^53.
  • The glue is the shipped wai_web.js, changed in a fixed, counted set of places (tools/cli.mjs, glue; manifest.json records the counts) and compiled to ES5:
    • its WebAssembly loading is replaced by two hooks the loader calls;
    • a cached memory view is rebuilt whenever wasm.memory.buffer is a different buffer. The shipped glue rebuilt one only when its buffer was detached, which a WebAssembly memory does when it grows and the translation's memory does not;
    • Symbol.dispose is read only where Symbol exists;
    • the 64-bit helpers take the Number path without BigInt;
    • copies out of wasm memory no longer use TypedArray#slice, which early engines implement as a view.

Profiles

L2 and J come in profiles. profiles.json lists the exports each one keeps, and wasm-metadce drops what they cannot reach:

profile what it keeps
full everything
verify_decode verify, plus the integer decoders (WIV1, WIH1, WIA1, WIS1, int_synth, the JSON integer forms) and the staged-delivery sink
verify receipt, plus multi-rendition envelopes, pins, the capability registry, stage identities, session claims and the output tree
receipt envelope parsing, asset provenance (content hash, Ed25519 signature, Merkle chain) and the hashes

A page that calls a function outside its profile gets a TypeError. The shipped build and L1 are always the full module.

The loader

wai-load.js is ES5. It chooses a rung from feature probes alone, never from the user agent, and its detection never throws:

  1. no typed arrays: none, and the reason says to serve the classical floor renditions;
  2. no WebAssembly, or a WebAssembly that refuses a minimal MVP module: J;
  3. an early WebAssembly engine line (WebAssembly and Error.captureStackTrace present, no RegExp dotAll flag; the feature noDotAll): J, or the L2 receipt build when the page asks for the receipt profile. The full MVP module has crashed an engine of this line on arm64;
  4. a probe module for sign-extension, non-trapping float-to-int or bulk memory (with a data-count section) fails WebAssembly.validate: L2;
  5. a one-function module returning its i64 argument does not take and give a BigInt: L1;
  6. otherwise: shipped.
<script src="/wai/pkg/reach/wai-load.js"></script>
<script>
  WaiLoad.load({ profile: 'verify' }, function (err, r) {
    if (err) { /* r.reason says why; serve the classical floor */ return; }
    // r.api is wai-web's API; r.rung is 'shipped' | 'L1' | 'L2' | 'J'; r.reason says why.
    var p = new r.api.AssetProvenance(envelopeBytes, receiptJson);
  });
</script>
option meaning
base the folder of the reach files, ending in /; by default the folder wai-load.js came from
profile full (default), verify_decode, verify or receipt; used by L2 and J
force shipped, L1, L2 or J: load that rung whatever the probes say (testing). The global WAI_REACH_FORCE does the same
shipped { glue, wasm } URLs of the shipped package; by default base + '../wai_web.js' and base + '../wai_web_bg.wasm'
host { loadScript, fetchBytes, importModule, streams }, to load from somewhere other than the page. With streams (a browser's default), the shipped glue gets the wasm URL and compiles it while it downloads

WaiLoad.detect(opts) returns { rung, profile, reason, features } without loading anything. After a load, WaiLoad.state holds what the callback received.

Building

bash wai/wai-web/reach/build.sh <package-dir> [<dist-dir>]

<package-dir> holds wai_web.js and wai_web_bg.wasm. The output goes to <dist-dir> (default reach/dist/, which git ignores) with a manifest.json: every file's SHA-256 and its raw, gzip and brotli sizes, the input's SHA-256, the tool pins, and the counts of the glue changes.

The tools are pinned:

  • binaryen at release version_123, by the SHA-256 of each platform's archive, checked before use. The platforms are macOS arm64 and x86_64, and Linux x86_64 and aarch64;
  • TypeScript, esbuild, acorn and binaryen's WebAssembly build of the same release by package-lock.json. npm ci checks their integrity hashes. J's wasm2js is that WebAssembly build, run on Node: the x86_64 Linux archive's native wasm2js allocates the translation's locals differently from the macOS archive's, while the WebAssembly build gives the same output on every host (and the macOS archive's output). Every other step gives the same bytes from every archive.

The build refuses:

  • an input that uses any feature beyond the shipped build's;
  • a reduced module that exports anything other than its profile;
  • an L2 module whose i64 boundary differs from the shipped binary's;
  • a glue whose layout it does not recognise;
  • a script that does not parse as ECMAScript 5.

manifest.json in this folder is the build of the shipped package of record. Its input says which package: the SHA-256 of both files, the commit they were built from, and how. Given that package, build.sh refuses any output whose SHA-256 differs from the manifest's; with WAI_REACH_REQUIRE_RECORD=1 it also refuses any other package. WAI_REACH_RECORD=1 writes a new manifest.

Conformance

Every rung must give the shipped build's outputs. The runners below check this:

  • test/conformance.js (ES5, Node 0.12 onward) loads a rung through wai-load.js and runs test/workloads.js over the repository's corpora. Each workload is checked against an independent reference (a corpus's hashes, a sidecar's SHA-256, the receipt's signed fields, the registry), and its output must equal the shipped build's byte for byte. The workloads, by profile:

    • receipt: the hashes; envelope parsing; asset provenance over the vector in test/vectors/provenance/ (its README says how it is made), including a tampered copy and a forged joule total;
    • verify: the capability menu against the registry; the staged corpus's content hashes; multi-rendition envelopes; the output tree of the staged-measurement goldens;
    • verify_decode: every committed WIV1, WIH1, WIA1 and WIS1 payload against its sidecar; the JSON integer forms; the committed int_synth vectors; all of staged-conformance/ replayed through StageSink;
    • full: i64 parameters and results on both sides of 2^32.

    Under J, WebAssembly is deleted before anything loads. --strip es5 also deletes BigInt, Map, Symbol, Promise and the other post-ES5 builtins, and makes TypedArray#slice return a view.

  • test/run-modern.mjs runs on a current Node:

    • the loader must choose the shipped build, and that run is the reference;
    • every rung and profile is forced and must equal it;
    • an engine stripped to ES5 must be given J, and the same outputs;
    • the demo verify scripts (demo/verify_*.mjs: integer payloads, int_synth, the staged corpus with its guards and fuzz, the menu, the writers, the staged measurement) run against shipped, L1, L2 and J through a generated adapter module. The measurement script's written files must equal the shipped build's. They are this tree's scripts, or, with --demo-commit <commit>, that commit's scripts over that commit's corpora (its wai/, exported with git archive). A script this tree runs that the commit lacks is reported as SKIP.
node wai/wai-web/reach/test/run-modern.mjs --dist <dist> --shipped <package-dir> [--demo-commit <commit>]
node wai/wai-web/reach/test/conformance.js --dist <dist> --wai wai --rung J --profile receipt --reference <reference.json>

CI (.github/workflows/wai-web-reach.yml, on the linux runner) runs a leg per package:

  • The package of record. record/ keeps the two files the site serves under /wai/pkg/, built from the commit manifest.json names (input.commit; input.source says how). Both files must have the SHA-256 the manifest records, and build.sh then runs with WAI_REACH_REQUIRE_RECORD=1, so every output must have the manifest's SHA-256 and size: these are the bytes the site serves under /wai/pkg/reach/. The package is kept rather than rebuilt in CI: the same commit and recipe give a different wasm on a Linux x86_64 host than on the macOS arm64 host that built it (the glue is the same), so the commit's build is checked on that host (the site's build-wasm-pkg.sh recorded wai). The demo verify scripts on this leg are that commit's, over that commit's corpora (run-modern.mjs --demo-commit): a script a later commit changes checks code the package does not hold. The reach workloads and the engine images are this tree's.
  • The tree. wai-web is built from the pull request's own tree, so a change to wai-web meets the reach builds before the package of record moves to it. These outputs are not the recorded ones. The demo verify scripts are this tree's, so a check a pull request adds runs here against the code it checks.

On each leg:

  1. run-modern.mjs runs on the outputs;
  2. conformance.js runs in one Node image per engine line. On each line the loader must choose that line's rung by itself, and every output must equal the shipped build's from step 1.

What each proxy stands for

The images are proxies. Each runs one JavaScript engine line on x86_64 Linux, with the engine features below; real devices ship engines of the same lines. No real old device has been run. Real devices also differ in ways no proxy reproduces: 32-bit ARM code generation, engines with the JIT switched off, small memories, and engine families other than the proxied one.

proxy engine features rung the loader must choose device classes it stands for
node:0.12 ES5 and typed arrays; no WebAssembly J TV sets and boxes whose web engine, or system web view, is frozen at an engine with no WebAssembly, smart TVs of model years 2014–2019 among them; phones whose system web view predates WebAssembly; broadcast-application receivers (HbbTV 2.0.x, ATSC 3.0 A/344). Console browsers that run with the JIT off, and engine families other than the proxied one, are J devices that this proxy does not cover
node:8.0.0 MVP WebAssembly; no RegExp dotAll flag J, or L2 for the receipt profile TV sets and boxes whose system web view is frozen at an engine with WebAssembly but no RegExp dotAll flag. The full MVP module has crashed an engine of this line on arm64; this x86_64 proxy does not crash, so it checks the choice and the outputs, not the crash
node:8 MVP WebAssembly and the dotAll flag; no BigInt L2 TV sets and boxes whose system web view is frozen at MVP WebAssembly with no BigInt, and TV platforms of model years 2019–2020
node:10 MVP WebAssembly and BigInt; no sign-extension or bulk memory L2 TV sets and boxes whose system web view has BigInt but not the post-MVP features the shipped build uses
node:12 the shipped build's WebAssembly features; i64 does not cross the JS boundary as BigInt L1 TV platforms of model year 2021
node:14 the same L1 TV platforms and streaming sticks whose engine has the shipped build's WebAssembly features but passes no i64 as BigInt, and phones' system web views of the same line
a current Node every feature, with i64 as BigInt shipped current engines. Engine families other than the proxied one are not proxied here

Limits

  • J is slower than WebAssembly, and far slower on engines without a JIT. The receipt and verify profiles are the ones for small or slow devices.
  • Without BigInt, a 64-bit value is a Number, exact below 2^53. IntPayloadCost.macs (a 128-bit value) and the functions that return a BigInt64Array still need BigInt.
  • A trap in the WebAssembly builds is an Error('abort') in J. The translation does not check memory accesses out of bounds, which trap in WebAssembly. The Rust does not make such accesses, and the conformance runs compare every output.
  • Reduced profiles throw a TypeError for any function outside them.