### wai-web on older engines: the reach builds

- **Three builds from the shipped package, and a loader** ([wai-web/reach](wai-web/reach/README.md)).
  `reach/build.sh` derives them from `wai_web_bg.wasm` and `wai_web.js` with no change to
  the Rust:
  - **L1** legalises the binary's JS interface (`wasm-opt --legalize-js-interface`), so
    engines where i64 does not cross as `BigInt` run the full module;
  - **L2** lowers it to MVP WebAssembly and legalises it;
  - **J** translates the MVP module to ES5 JavaScript with `wasm2js`.

  L2 and J come in profiles, kept by `wasm-metadce` from the exports each needs: `full`,
  `verify_decode`, `verify` and `receipt`. An ES5 runtime supplies the missing builtins
  and joins and splits i64 values at the boundary: a `BigInt` where the engine has one, a
  `Number` (exact below 2^53) where it does not. The glue is the shipped glue compiled to
  ES5, with counted changes. One of them rebuilds a memory view whenever the memory's
  buffer changes, not only when it is detached.
- **`wai-load.js` chooses by feature probes alone.** Tiny modules passed to
  `WebAssembly.validate` (MVP, sign-extension, non-trapping float-to-int, bulk memory), one
  instantiated module that returns its i64 argument, and checks for typed arrays decide
  between shipped, L1, L2 and J. The probes never throw. An early WebAssembly engine line
  (WebAssembly and the stack-trace capture API, but no RegExp `dotAll` flag: the feature
  `noDotAll`) gets J, or the L2 receipt build, because the full MVP module has crashed an
  engine of that line on arm64. A rung can be forced, and the loader says why it chose.
- **Pinned and recorded.** binaryen `version_123` is checked against each platform
  archive's SHA-256, and TypeScript, esbuild, acorn and binaryen's WebAssembly build are
  pinned by `package-lock.json`; J's `wasm2js` is that WebAssembly build, so J comes out
  the same on every host.
  `reach/manifest.json` records the SHA-256 and sizes of every output built from the
  package the site serves, and the commit that package was built from; `reach/record/`
  keeps that package. Given it, the build refuses any output that differs.
- **Checked output for output.**
  - `reach/test/conformance.js` (ES5) runs workloads over the repository's corpora on any
    rung, and every output must equal the shipped build's. The workloads cover receipts,
    the staged corpus, the integer payloads, int_synth, the menu, the output tree and the
    i64 boundary.
  - `reach/test/run-modern.mjs` forces every rung and profile on a current Node, and runs
    the demo verify scripts against each full build.
  - CI (`wai-web-reach.yml`) runs a leg per package. On the package of record, kept in
    `reach/record/` and held to the manifest's SHA-256, every output must equal the
    manifest's; on the tree's own wai-web, the reach builds must still build and pass.
    Each leg runs the conformance on the Node images of the engine lines each rung serves
    (J on `node:0.12`, the early-engine routing on `node:8.0.0`, L2 on `node:8` and
    `node:10`, L1 on `node:12` and `node:14`) and the shipped build on a current Node.

  The images stand in for device classes, which the README names by capability. No real
  old device has been run.
- **The receipt vector's signer id is a label.** The receipt workloads verify an asset and
  its provenance receipt in `reach/test/vectors/provenance/`, signed with `wai_prov`'s
  published test seed under its label `did:example:wai-prov`. `wai_prov --carry
  <receipt.json>` measures nothing: it carries each step's figure, and its label, from an
  earlier receipt for the same lineage and signs them under this revision's id, and it
  refuses a receipt that does not verify or whose steps are not this run's. The vector
  carries the figures `wai_prov` measured when it first wrote the asset, and the
  documented command, carrying the receipt onto itself, writes both files again byte for
  byte.

